From 64bb371db70cc68257113e7d84922becd0d7df2e Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 17 Aug 2025 16:57:00 +1000 Subject: [PATCH 001/138] Add main function --- Makefile | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index 924a71e..e74677b 100644 --- a/Makefile +++ b/Makefile @@ -22,11 +22,7 @@ DEPFILE := $(OBJDIR)/$(TARGETNAME).d DFLAGS := $(DFLAGS) -preview=bitfields -preview=rvaluerefparam -preview=nosharedaccess -preview=in -ifeq ($(OS),windows) -SOURCES := $(shell dir /s /b $(SRCDIR)\\*.d) -else SOURCES := $(shell find "$(SRCDIR)" -type f -name '*.d') -endif # Set target file based on build type and OS ifeq ($(BUILD_TYPE),exe) @@ -93,30 +89,19 @@ else endif ifeq ($(CONFIG),unittest) - DFLAGS := $(DFLAGS) -unittest + DFLAGS := $(DFLAGS) -unittest -main endif -include $(DEPFILE) $(TARGET): -ifeq ($(OS),windows) - @if not exist "obj" mkdir "obj" > nul 2>&1 - @if not exist "$(subst /,\,$(OBJDIR))" mkdir "$(subst /,\,$(OBJDIR))" > nul 2>&1 - @if not exist "bin" mkdir "bin" > nul 2>&1 - @if not exist "$(subst /,\,$(TARGETDIR))" mkdir "$(subst /,\,$(TARGETDIR))" > nul 2>&1 -else mkdir -p $(OBJDIR) $(TARGETDIR) -endif ifeq ($(D_COMPILER),ldc) "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(TARGET) -od$(OBJDIR) -deps=$(DEPFILE) $(SOURCES) else ifeq ($(D_COMPILER),dmd) ifeq ($(BUILD_TYPE),lib) - "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(notdir $(TARGET)) -od$(OBJDIR) -makedeps $(SOURCES) > $(DEPFILE) -ifeq ($(OS),windows) - move "$(subst /,\,$(OBJDIR))\\$(notdir $(TARGET))" "$(subst /,\,$(TARGETDIR))" > nul -else + "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(OBJDIR)/$(notdir $(TARGET)) -od$(OBJDIR) -makedeps $(SOURCES) > $(DEPFILE) mv "$(OBJDIR)/$(notdir $(TARGET))" "$(TARGETDIR)" -endif else # exe "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(TARGET) -od$(OBJDIR) -makedeps $(SOURCES) > $(DEPFILE) endif From 084fa962c9d62892b684ddfbdb9901f6b1c90913 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 17 Aug 2025 17:25:17 +1000 Subject: [PATCH 002/138] Migrate to lower_snake case... --- src/urt/algorithm.d | 10 +- src/urt/conv.d | 134 ++++---- src/urt/crc.d | 44 +-- src/urt/dbg.d | 2 +- src/urt/digest/md5.d | 28 +- src/urt/digest/sha.d | 34 +- src/urt/endian.d | 8 +- src/urt/file.d | 128 ++++---- src/urt/format/json.d | 44 +-- src/urt/hash.d | 8 +- src/urt/inet.d | 38 +-- src/urt/mem/alloc.d | 16 +- src/urt/mem/allocator.d | 6 +- src/urt/mem/scratchpad.d | 44 +-- src/urt/mem/string.d | 4 +- src/urt/mem/temp.d | 48 +-- src/urt/meta/package.d | 54 +-- src/urt/meta/tuple.d | 10 +- src/urt/package.d | 6 +- src/urt/rand.d | 2 +- src/urt/range/package.d | 14 +- src/urt/result.d | 55 ++-- src/urt/socket.d | 632 ++++++++++++++++++------------------ src/urt/string/ansi.d | 14 +- src/urt/string/format.d | 28 +- src/urt/string/string.d | 6 +- src/urt/string/tailstring.d | 4 +- src/urt/system.d | 28 +- src/urt/time.d | 22 +- src/urt/util.d | 8 +- src/urt/variant.d | 12 +- src/urt/zip.d | 156 +++++---- 32 files changed, 812 insertions(+), 835 deletions(-) diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index 14ecc1f..a742393 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -48,7 +48,7 @@ auto compare(T, U)(auto ref T a, auto ref U b) return a < b ? -1 : (a > b ? 1 : 0); } -size_t binarySearch(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmpArgs) +size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmpArgs) { T* p = arr.ptr; size_t low = 0; @@ -165,12 +165,12 @@ unittest assert(s.x == arr[i]); // test binary search, not that they're sorted... - assert(binarySearch(arr, -1) == 0); - assert(binarySearch!(s => s.x < 30 ? -1 : s.x > 30 ? 1 : 0)(arr2) == 3); - assert(binarySearch(arr, 0) == arr.length); + assert(binary_search(arr, -1) == 0); + assert(binary_search!(s => s.x < 30 ? -1 : s.x > 30 ? 1 : 0)(arr2) == 3); + assert(binary_search(arr, 0) == arr.length); int[10] rep = [1, 10, 10, 10, 10, 10, 10, 10, 10, 100]; - assert(binarySearch(rep, 10) == 1); + assert(binary_search(rep, 10) == 1); } diff --git a/src/urt/conv.d b/src/urt/conv.d index 91f39ab..ebce388 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -8,7 +8,7 @@ nothrow @nogc: // on error or not-a-number cases, bytesTaken will contain 0 -long parseInt(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure +long parse_int(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure { size_t i = 0; bool neg = false; @@ -21,13 +21,13 @@ long parseInt(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure i++; } - ulong value = str.ptr[i .. str.length].parseUint(bytesTaken, base); + ulong value = str.ptr[i .. str.length].parse_uint(bytesTaken, base); if (bytesTaken && *bytesTaken != 0) *bytesTaken += i; return neg ? -cast(long)value : cast(long)value; } -long parseIntWithDecimal(const(char)[] str, out ulong fixedPointDivisor, size_t* bytesTaken = null, int base = 10) pure +long parse_int_with_decimal(const(char)[] str, out ulong fixedPointDivisor, size_t* bytesTaken = null, int base = 10) pure { size_t i = 0; bool neg = false; @@ -40,13 +40,13 @@ long parseIntWithDecimal(const(char)[] str, out ulong fixedPointDivisor, size_t* i++; } - ulong value = str[i .. str.length].parseUintWithDecimal(fixedPointDivisor, bytesTaken, base); + ulong value = str[i .. str.length].parse_uint_with_decimal(fixedPointDivisor, bytesTaken, base); if (bytesTaken && *bytesTaken != 0) *bytesTaken += i; return neg ? -cast(long)value : cast(long)value; } -ulong parseUint(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure +ulong parse_uint(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure { assert(base > 1 && base <= 36, "Invalid base"); @@ -69,7 +69,7 @@ ulong parseUint(const(char)[] str, size_t* bytesTaken = null, int base = 10) pur { for (; s < e; ++s) { - uint digit = getDigit(*s); + uint digit = get_digit(*s); if (digit >= base) break; value = value*base + digit; @@ -81,7 +81,7 @@ ulong parseUint(const(char)[] str, size_t* bytesTaken = null, int base = 10) pur return value; } -ulong parseUintWithDecimal(const(char)[] str, out ulong fixedPointDivisor, size_t* bytesTaken = null, int base = 10) pure +ulong parse_uint_with_decimal(const(char)[] str, out ulong fixedPointDivisor, size_t* bytesTaken = null, int base = 10) pure { assert(base > 1 && base <= 36, "Invalid base"); @@ -103,7 +103,7 @@ ulong parseUintWithDecimal(const(char)[] str, out ulong fixedPointDivisor, size_ goto parse_decimal; } - uint digit = getDigit(c); + uint digit = get_digit(c); if (digit >= base) break; value = value*base + digit; @@ -113,7 +113,7 @@ ulong parseUintWithDecimal(const(char)[] str, out ulong fixedPointDivisor, size_ parse_decimal: for (; s < e; ++s) { - uint digit = getDigit(*s); + uint digit = get_digit(*s); if (digit >= base) { // if i == 1, then the first char was a '.' and the next was not numeric, so this is not a number! @@ -132,11 +132,11 @@ done: return value; } -ulong parseUintWithBase(const(char)[] str, size_t* bytesTaken = null) pure +ulong parse_uint_with_base(const(char)[] str, size_t* bytesTaken = null) pure { const(char)* p = str.ptr; - int base = parseBasePrefix(str); - ulong i = parseUint(str, bytesTaken, base); + int base = parse_base_prefix(str); + ulong i = parse_uint(str, bytesTaken, base); if (bytesTaken && *bytesTaken != 0) *bytesTaken += str.ptr - p; return i; @@ -147,21 +147,21 @@ unittest { size_t taken; ulong divisor; - assert(parseUint("123") == 123); - assert(parseInt("+123.456") == 123); - assert(parseInt("-123.456", null, 10) == -123); - assert(parseUintWithDecimal("123.456", divisor, null, 10) == 123456 && divisor == 1000); - assert(parseIntWithDecimal("123.456.789", divisor, &taken, 16) == 1193046 && taken == 7 && divisor == 4096); - assert(parseInt("11001", null, 2) == 25); - assert(parseIntWithDecimal("-AbCdE.f", divisor, null, 16) == -11259375 && divisor == 16); - assert(parseInt("123abc", &taken, 10) == 123 && taken == 3); - assert(parseInt("!!!", &taken, 10) == 0 && taken == 0); - assert(parseInt("-!!!", &taken, 10) == 0 && taken == 0); - assert(parseInt("Wow", &taken, 36) == 42368 && taken == 3); - assert(parseUintWithBase("0x100", &taken) == 0x100 && taken == 5); + assert(parse_uint("123") == 123); + assert(parse_int("+123.456") == 123); + assert(parse_int("-123.456", null, 10) == -123); + assert(parse_uint_with_decimal("123.456", divisor, null, 10) == 123456 && divisor == 1000); + assert(parse_int_with_decimal("123.456.789", divisor, &taken, 16) == 1193046 && taken == 7 && divisor == 4096); + assert(parse_int("11001", null, 2) == 25); + assert(parse_int_with_decimal("-AbCdE.f", divisor, null, 16) == -11259375 && divisor == 16); + assert(parse_int("123abc", &taken, 10) == 123 && taken == 3); + assert(parse_int("!!!", &taken, 10) == 0 && taken == 0); + assert(parse_int("-!!!", &taken, 10) == 0 && taken == 0); + assert(parse_int("Wow", &taken, 36) == 42368 && taken == 3); + assert(parse_uint_with_base("0x100", &taken) == 0x100 && taken == 5); } -int parseIntFast(ref const(char)[] text, out bool success) pure +int parse_int_fast(ref const(char)[] text, out bool success) pure { if (!text.length) return 0; @@ -210,25 +210,25 @@ unittest { bool success; const(char)[] text = "123"; - assert(parseIntFast(text, success) == 123 && success == true && text.empty); + assert(parse_int_fast(text, success) == 123 && success == true && text.empty); text = "-2147483648abc"; - assert(parseIntFast(text, success) == -2147483648 && success == true && text.length == 3); + assert(parse_int_fast(text, success) == -2147483648 && success == true && text.length == 3); text = "2147483648"; - assert(parseIntFast(text, success) == 0 && success == false); + assert(parse_int_fast(text, success) == 0 && success == false); text = "-2147483649"; - assert(parseIntFast(text, success) == 0 && success == false); + assert(parse_int_fast(text, success) == 0 && success == false); text = "2147483650"; - assert(parseIntFast(text, success) == 0 && success == false); + assert(parse_int_fast(text, success) == 0 && success == false); } // on error or not-a-number, result will be nan and bytesTaken will contain 0 -double parseFloat(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure +double parse_float(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure { // TODO: E-notation... size_t taken = void; ulong div = void; - long value = str.parseIntWithDecimal(div, &taken, base); + long value = str.parse_int_with_decimal(div, &taken, base); if (bytesTaken) *bytesTaken = taken; if (taken == 0) @@ -245,15 +245,15 @@ unittest } size_t taken; - assert(fcmp(parseFloat("123.456"), 123.456)); - assert(fcmp(parseFloat("+123.456"), 123.456)); - assert(fcmp(parseFloat("-123.456.789"), -123.456)); - assert(fcmp(parseFloat("1101.11", &taken, 2), 13.75) && taken == 7); - assert(parseFloat("xyz", &taken) is double.nan && taken == 0); + assert(fcmp(parse_float("123.456"), 123.456)); + assert(fcmp(parse_float("+123.456"), 123.456)); + assert(fcmp(parse_float("-123.456.789"), -123.456)); + assert(fcmp(parse_float("1101.11", &taken, 2), 13.75) && taken == 7); + assert(parse_float("xyz", &taken) is double.nan && taken == 0); } -ptrdiff_t formatInt(long value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ', bool showSign = false) pure +ptrdiff_t format_int(long value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ', bool showSign = false) pure { const bool neg = value < 0; showSign |= neg; @@ -263,7 +263,7 @@ ptrdiff_t formatInt(long value, char[] buffer, uint base = 10, uint width = 0, c ulong i = neg ? -value : value; - ptrdiff_t r = formatUint(i, buffer.ptr ? buffer.ptr[(width == 0 ? showSign : 0) .. buffer.length] : null, base, width, fill); + ptrdiff_t r = format_uint(i, buffer.ptr ? buffer.ptr[(width == 0 ? showSign : 0) .. buffer.length] : null, base, width, fill); if (r < 0 || !showSign) return r; @@ -309,7 +309,7 @@ ptrdiff_t formatInt(long value, char[] buffer, uint base = 10, uint width = 0, c return r + 1; } -ptrdiff_t formatUint(ulong value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ') pure +ptrdiff_t format_uint(ulong value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ') pure { import urt.util : max; @@ -360,36 +360,36 @@ ptrdiff_t formatUint(ulong value, char[] buffer, uint base = 10, uint width = 0, unittest { char[64] buffer; - assert(formatInt(0, null) == 1); - assert(formatInt(14, null) == 2); - assert(formatInt(14, null, 16) == 1); - assert(formatInt(-14, null) == 3); - assert(formatInt(-14, null, 16) == 2); - assert(formatInt(-14, null, 16, 3, '0') == 3); - assert(formatInt(-123, null, 10, 6) == 6); - assert(formatInt(-123, null, 10, 3) == 4); - assert(formatInt(-123, null, 10, 2) == 4); - - size_t len = formatInt(0, buffer); + assert(format_int(0, null) == 1); + assert(format_int(14, null) == 2); + assert(format_int(14, null, 16) == 1); + assert(format_int(-14, null) == 3); + assert(format_int(-14, null, 16) == 2); + assert(format_int(-14, null, 16, 3, '0') == 3); + assert(format_int(-123, null, 10, 6) == 6); + assert(format_int(-123, null, 10, 3) == 4); + assert(format_int(-123, null, 10, 2) == 4); + + size_t len = format_int(0, buffer); assert(buffer[0 .. len] == "0"); - len = formatInt(14, buffer); + len = format_int(14, buffer); assert(buffer[0 .. len] == "14"); - len = formatInt(14, buffer, 2); + len = format_int(14, buffer, 2); assert(buffer[0 .. len] == "1110"); - len = formatInt(14, buffer, 8, 3); + len = format_int(14, buffer, 8, 3); assert(buffer[0 .. len] == " 16"); - len = formatInt(14, buffer, 16, 4, '0'); + len = format_int(14, buffer, 16, 4, '0'); assert(buffer[0 .. len] == "000E"); - len = formatInt(-14, buffer, 16, 3, '0'); + len = format_int(-14, buffer, 16, 3, '0'); assert(buffer[0 .. len] == "-0E"); - len = formatInt(12345, buffer, 10, 3); + len = format_int(12345, buffer, 10, 3); assert(buffer[0 .. len] == "12345"); - len = formatInt(-123, buffer, 10, 6); + len = format_int(-123, buffer, 10, 6); assert(buffer[0 .. len] == " -123"); } -ptrdiff_t formatFloat(double value, char[] buffer, const(char)[] format = null) // pure +ptrdiff_t format_float(double value, char[] buffer, const(char)[] format = null) // pure { // TODO: this function should be oblitereated and implemented natively... // CRT call can't CTFE, which is a shame @@ -424,9 +424,9 @@ template to(T) { long to(const(char)[] str) { - int base = parseBasePrefix(str); + int base = parse_base_prefix(str); size_t taken; - long r = parseInt(str, &taken, base); + long r = parse_int(str, &taken, base); assert(taken == str.length, "String is not numeric"); return r; } @@ -435,9 +435,9 @@ template to(T) { double to(const(char)[] str) { - int base = parseBasePrefix(str); + int base = parse_base_prefix(str); size_t taken; - double r = parseFloat(str, &taken, base); + double r = parse_float(str, &taken, base); assert(taken == str.length, "String is not numeric"); return r; } @@ -479,7 +479,7 @@ template to(T) private: -uint getDigit(char c) pure +uint get_digit(char c) pure { uint zeroBase = c - '0'; if (zeroBase < 10) @@ -493,7 +493,7 @@ uint getDigit(char c) pure return -1; } -int parseBasePrefix(ref const(char)[] str) pure +int parse_base_prefix(ref const(char)[] str) pure { int base = 10; if (str.length >= 2) @@ -510,7 +510,7 @@ int parseBasePrefix(ref const(char)[] str) pure /+ -size_t formatStruct(T)(ref T value, char[] buffer) nothrow @nogc +size_t format_struct(T)(ref T value, char[] buffer) nothrow @nogc { import urt.string.format; @@ -530,7 +530,7 @@ unittest Packet p; char[1024] buffer; - size_t len = formatStruct(p, buffer); + size_t len = format_struct(p, buffer); assert(buffer[0 .. len] == "Packet()"); } diff --git a/src/urt/crc.d b/src/urt/crc.d index 5010e3f..74f266b 100644 --- a/src/urt/crc.d +++ b/src/urt/crc.d @@ -1,6 +1,6 @@ module urt.crc; -import urt.meta : intForWidth; +import urt.meta : IntForWidth; import urt.traits : isUnsignedInt; nothrow @nogc: @@ -41,10 +41,10 @@ struct CRCParams } alias CRCTable(Algorithm algo) = CRCTable!(paramTable[algo].width, paramTable[algo].poly, paramTable[algo].reflect); -alias CRCType(Algorithm algo) = intForWidth!(paramTable[algo].width); +alias CRCType(Algorithm algo) = IntForWidth!(paramTable[algo].width); // compute a CRC with runtime parameters -T calculateCRC(T = uint)(const void[] data, ref const CRCParams params, ref const T[256] table) pure +T calculate_crc(T = uint)(const void[] data, ref const CRCParams params, ref const T[256] table) pure if (isUnsignedInt!T) { assert(params.width <= T.sizeof*8, "T is too small for the CRC width"); @@ -68,7 +68,7 @@ T calculateCRC(T = uint)(const void[] data, ref const CRCParams params, ref cons } // compute a CRC with hard-coded parameters -T calculateCRC(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = cast(T)paramTable[algo].initial^paramTable[algo].finalXor) pure +T calculate_crc(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = cast(T)paramTable[algo].initial^paramTable[algo].finalXor) pure if (isUnsignedInt!T) { enum CRCParams params = paramTable[algo]; @@ -98,7 +98,7 @@ T calculateCRC(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = } // computes 2 CRC's for 2 points in the data stream... -T calculateCRC_2(Algorithm algo, T = intForWidth!(paramTable[algo].width*2))(const void[] data, uint earlyOffset) pure +T calculate_crc_2(Algorithm algo, T = IntForWidth!(paramTable[algo].width*2))(const void[] data, uint earlyOffset) pure if (isUnsignedInt!T) { enum CRCParams params = paramTable[algo]; @@ -151,7 +151,7 @@ done: } -T[256] generateCRCTable(T)(ref const CRCParams params) pure +T[256] generate_crc_table(T)(ref const CRCParams params) pure if (isUnsignedInt!T) { enum typeWidth = T.sizeof * 8; @@ -189,23 +189,23 @@ unittest { immutable ubyte[9] checkData = ['1','2','3','4','5','6','7','8','9']; - assert(calculateCRC!(Algorithm.CRC16_MODBUS)(checkData[]) == paramTable[Algorithm.CRC16_MODBUS].check); - assert(calculateCRC!(Algorithm.CRC16_EZSP)(checkData[]) == paramTable[Algorithm.CRC16_EZSP].check); - assert(calculateCRC!(Algorithm.CRC16_KERMIT)(checkData[]) == paramTable[Algorithm.CRC16_KERMIT].check); - assert(calculateCRC!(Algorithm.CRC16_USB)(checkData[]) == paramTable[Algorithm.CRC16_USB].check); - assert(calculateCRC!(Algorithm.CRC16_XMODEM)(checkData[]) == paramTable[Algorithm.CRC16_XMODEM].check); - assert(calculateCRC!(Algorithm.CRC16_ISO_HDLC)(checkData[]) == paramTable[Algorithm.CRC16_ISO_HDLC].check); - assert(calculateCRC!(Algorithm.CRC16_DNP)(checkData[]) == paramTable[Algorithm.CRC16_DNP].check); - assert(calculateCRC!(Algorithm.CRC32_ISO_HDLC)(checkData[]) == paramTable[Algorithm.CRC32_ISO_HDLC].check); - assert(calculateCRC!(Algorithm.CRC32_CASTAGNOLI)(checkData[]) == paramTable[Algorithm.CRC32_CASTAGNOLI].check); + assert(calculate_crc!(Algorithm.CRC16_MODBUS)(checkData[]) == paramTable[Algorithm.CRC16_MODBUS].check); + assert(calculate_crc!(Algorithm.CRC16_EZSP)(checkData[]) == paramTable[Algorithm.CRC16_EZSP].check); + assert(calculate_crc!(Algorithm.CRC16_KERMIT)(checkData[]) == paramTable[Algorithm.CRC16_KERMIT].check); + assert(calculate_crc!(Algorithm.CRC16_USB)(checkData[]) == paramTable[Algorithm.CRC16_USB].check); + assert(calculate_crc!(Algorithm.CRC16_XMODEM)(checkData[]) == paramTable[Algorithm.CRC16_XMODEM].check); + assert(calculate_crc!(Algorithm.CRC16_ISO_HDLC)(checkData[]) == paramTable[Algorithm.CRC16_ISO_HDLC].check); + assert(calculate_crc!(Algorithm.CRC16_DNP)(checkData[]) == paramTable[Algorithm.CRC16_DNP].check); + assert(calculate_crc!(Algorithm.CRC32_ISO_HDLC)(checkData[]) == paramTable[Algorithm.CRC32_ISO_HDLC].check); + assert(calculate_crc!(Algorithm.CRC32_CASTAGNOLI)(checkData[]) == paramTable[Algorithm.CRC32_CASTAGNOLI].check); // check that rolling CRC works... - ushort crc = calculateCRC!(Algorithm.CRC16_MODBUS)(checkData[0 .. 5]); - assert(calculateCRC!(Algorithm.CRC16_MODBUS)(checkData[5 .. 9], crc) == paramTable[Algorithm.CRC16_MODBUS].check); - crc = calculateCRC!(Algorithm.CRC16_ISO_HDLC)(checkData[0 .. 5]); - assert(calculateCRC!(Algorithm.CRC16_ISO_HDLC)(checkData[5 .. 9], crc) == paramTable[Algorithm.CRC16_ISO_HDLC].check); - uint crc32 = calculateCRC!(Algorithm.CRC32_ISO_HDLC)(checkData[0 .. 5]); - assert(calculateCRC!(Algorithm.CRC32_ISO_HDLC)(checkData[5 .. 9], crc32) == paramTable[Algorithm.CRC32_ISO_HDLC].check); + ushort crc = calculate_crc!(Algorithm.CRC16_MODBUS)(checkData[0 .. 5]); + assert(calculate_crc!(Algorithm.CRC16_MODBUS)(checkData[5 .. 9], crc) == paramTable[Algorithm.CRC16_MODBUS].check); + crc = calculate_crc!(Algorithm.CRC16_ISO_HDLC)(checkData[0 .. 5]); + assert(calculate_crc!(Algorithm.CRC16_ISO_HDLC)(checkData[5 .. 9], crc) == paramTable[Algorithm.CRC16_ISO_HDLC].check); + uint crc32 = calculate_crc!(Algorithm.CRC32_ISO_HDLC)(checkData[0 .. 5]); + assert(calculate_crc!(Algorithm.CRC32_ISO_HDLC)(checkData[5 .. 9], crc32) == paramTable[Algorithm.CRC32_ISO_HDLC].check); } @@ -238,5 +238,5 @@ T reflect(T)(T value, ubyte bits) // this minimises the number of table instantiations template CRCTable(uint width, uint poly, bool reflect) { - __gshared immutable CRCTable = generateCRCTable!(intForWidth!width)(CRCParams(width, reflect, poly, 0, 0, 0)); + __gshared immutable CRCTable = generate_crc_table!(IntForWidth!width)(CRCParams(width, reflect, poly, 0, 0, 0)); } diff --git a/src/urt/dbg.d b/src/urt/dbg.d index 8cc13d1..189100d 100644 --- a/src/urt/dbg.d +++ b/src/urt/dbg.d @@ -61,7 +61,7 @@ else private: -package(urt) void setupAssertHandler() +package(urt) void setup_assert_handler() { import core.exception : assertHandler; assertHandler = &urt_assert; diff --git a/src/urt/digest/md5.d b/src/urt/digest/md5.d index ef75d98..ba1c4c7 100644 --- a/src/urt/digest/md5.d +++ b/src/urt/digest/md5.d @@ -14,13 +14,13 @@ struct MD5Context enum uint[4] initState = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 ]; } -void md5Init(ref MD5Context ctx) +void md5_init(ref MD5Context ctx) { ctx.size = 0; ctx.buffer = MD5Context.initState; } -void md5Update(ref MD5Context ctx, const void[] input) +void md5_update(ref MD5Context ctx, const void[] input) { size_t offset = ctx.size % 64; ctx.size += input.length; @@ -41,7 +41,7 @@ void md5Update(ref MD5Context ctx, const void[] input) uint[16] tmp = void; foreach (uint j; 0 .. 16) tmp[j] = loadLittleEndian(cast(uint*)ctx.input.ptr + j); - md5Step(ctx.buffer, tmp); + md5_step(ctx.buffer, tmp); size_t tail = input.length - i; if (tail < 64) @@ -54,7 +54,7 @@ void md5Update(ref MD5Context ctx, const void[] input) } } -ubyte[16] md5Finalise(ref MD5Context ctx) +ubyte[16] md5_finalise(ref MD5Context ctx) { uint[16] tmp = void; uint offset = ctx.size % 64; @@ -67,7 +67,7 @@ ubyte[16] md5Finalise(ref MD5Context ctx) PADDING[1 .. padding_length] = 0; // Fill in the padding and undo the changes to size that resulted from the update - md5Update(ctx, PADDING[0 .. padding_length]); + md5_update(ctx, PADDING[0 .. padding_length]); ctx.size -= cast(ulong)padding_length; // Do a final update (internal to this function) @@ -78,7 +78,7 @@ ubyte[16] md5Finalise(ref MD5Context ctx) tmp[14] = cast(uint)(ctx.size*8); tmp[15] = (ctx.size*8) >> 32; - md5Step(ctx.buffer, tmp); + md5_step(ctx.buffer, tmp); uint[4] digest = void; foreach (uint k; 0 .. 4) @@ -91,13 +91,13 @@ unittest import urt.encoding; MD5Context ctx; - md5Init(ctx); - auto digest = md5Finalise(ctx); + md5_init(ctx); + auto digest = md5_finalise(ctx); assert(digest == Hex!"d41d8cd98f00b204e9800998ecf8427e"); - md5Init(ctx); - md5Update(ctx, "Hello, World!"); - digest = md5Finalise(ctx); + md5_init(ctx); + md5_update(ctx, "Hello, World!"); + digest = md5_finalise(ctx); assert(digest == Hex!"65a8e27d8879283831b664bd8b7f0ad4"); } @@ -131,11 +131,11 @@ __gshared immutable uint[] K = [ ]; // rotates a 32-bit word left by n bits -uint rotateLeft(uint x, uint n) +uint rotate_left(uint x, uint n) => (x << n) | (x >> (32 - n)); // step on 512 bits of input with the main MD5 algorithm -void md5Step(ref uint[4] buffer, ref const uint[16] input) +void md5_step(ref uint[4] buffer, ref const uint[16] input) { uint AA = buffer[0]; uint BB = buffer[1]; @@ -171,7 +171,7 @@ void md5Step(ref uint[4] buffer, ref const uint[16] input) uint temp = DD; DD = CC; CC = BB; - BB = BB + rotateLeft(AA + E + K[i] + input[j], S[i]); + BB = BB + rotate_left(AA + E + K[i] + input[j], S[i]); AA = temp; } diff --git a/src/urt/digest/sha.d b/src/urt/digest/sha.d index 196c835..fc64067 100644 --- a/src/urt/digest/sha.d +++ b/src/urt/digest/sha.d @@ -17,7 +17,7 @@ struct SHA1Context uint datalen; uint[DigestElements] state; - alias transform = sha1Transform; + alias transform = sha1_transform; enum uint[DigestElements] initState = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 ]; @@ -36,7 +36,7 @@ struct SHA256Context uint datalen; uint[DigestElements] state; - alias transform = sha256Transform; + alias transform = sha256_transform; enum uint[DigestElements] initState = [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ]; @@ -54,14 +54,14 @@ struct SHA256Context } -void shaInit(Context)(ref Context ctx) +void sha_init(Context)(ref Context ctx) { ctx.datalen = 0; ctx.bitlen = 0; ctx.state = Context.initState; } -void shaUpdate(Context)(ref Context ctx, const void[] input) +void sha_update(Context)(ref Context ctx, const void[] input) { const(ubyte)[] data = cast(ubyte[])input; @@ -82,7 +82,7 @@ void shaUpdate(Context)(ref Context ctx, const void[] input) } } -ubyte[Context.DigestLen] shaFinalise(Context)(ref Context ctx) +ubyte[Context.DigestLen] sha_finalise(Context)(ref Context ctx) { uint i = ctx.datalen; @@ -115,23 +115,23 @@ unittest import urt.encoding; SHA1Context ctx; - shaInit(ctx); - auto digest = shaFinalise(ctx); + sha_init(ctx); + auto digest = sha_finalise(ctx); assert(digest == Hex!"da39a3ee5e6b4b0d3255bfef95601890afd80709"); - shaInit(ctx); - shaUpdate(ctx, "Hello, World!"); - digest = shaFinalise(ctx); + sha_init(ctx); + sha_update(ctx, "Hello, World!"); + digest = sha_finalise(ctx); assert(digest == Hex!"0a0a9f2a6772942557ab5355d76af442f8f65e01"); SHA256Context ctx2; - shaInit(ctx2); - auto digest2 = shaFinalise(ctx2); + sha_init(ctx2); + auto digest2 = sha_finalise(ctx2); assert(digest2 == Hex!"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); - shaInit(ctx2); - shaUpdate(ctx2, "Hello, World!"); - digest2 = shaFinalise(ctx2); + sha_init(ctx2); + sha_update(ctx2, "Hello, World!"); + digest2 = sha_finalise(ctx2); assert(digest2 == Hex!"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"); } @@ -141,7 +141,7 @@ private: uint ROTLEFT(uint a, uint b) => (a << b) | (a >> (32 - b)); uint ROTRIGHT(uint a, uint b) => (a >> b) | (a << (32 - b)); -void sha1Transform(ref SHA1Context ctx, const ubyte[] data) +void sha1_transform(ref SHA1Context ctx, const ubyte[] data) { uint a, b, c, d, e, i, j, t; uint[80] m = void; @@ -204,7 +204,7 @@ void sha1Transform(ref SHA1Context ctx, const ubyte[] data) ctx.state[4] += e; } -void sha256Transform(ref SHA256Context ctx, const ubyte[] data) +void sha256_transform(ref SHA256Context ctx, const ubyte[] data) { static uint CH(uint x, uint y, uint z) => (x & y) ^ (~x & z); static uint MAJ(uint x, uint y, uint z) => (x & y) ^ (x & z) ^ (y & z); diff --git a/src/urt/endian.d b/src/urt/endian.d index e76f671..879b666 100644 --- a/src/urt/endian.d +++ b/src/urt/endian.d @@ -82,8 +82,8 @@ ulong endianToNative(T, bool little)(ref const ubyte[8] bytes) pragma(inline, true) T endianToNative(T, bool little)(ref const ubyte[T.sizeof] bytes) if (!isIntegral!T && !is(T == struct) && !is(T == U[N], U, size_t N)) { - import urt.meta : intForWidth; - alias U = intForWidth!(T.sizeof*8); + import urt.meta : IntForWidth; + alias U = IntForWidth!(T.sizeof*8); U u = endianToNative!(U, little)(bytes); return *cast(T*)&u; } @@ -206,8 +206,8 @@ ubyte[8] nativeToEndian(bool little)(ulong u) pragma(inline, true) auto nativeToEndian(bool little, T)(T val) if (!isIntegral!T && !is(T == struct) && !is(T == U[N], U, size_t N)) { - import urt.meta : intForWidth; - alias U = intForWidth!(T.sizeof*8); + import urt.meta : IntForWidth; + alias U = IntForWidth!(T.sizeof*8); U r = nativeToEndian!little(*cast(U*)&val); return *cast(T*)&r; } diff --git a/src/urt/file.d b/src/urt/file.d index e7ec4d3..8d3995c 100644 --- a/src/urt/file.d +++ b/src/urt/file.d @@ -133,17 +133,17 @@ Result delete_file(const(char)[] path) version (Windows) { if (!DeleteFileW(path.twstringz)) - return Win32Result(GetLastError()); + return getlasterror_result(); } else version (Posix) { if (unlink(path.tstringz) == -1) - return PosixResult(errno); + return errno_result(); } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result rename_file(const(char)[] oldPath, const(char)[] newPath) @@ -151,18 +151,18 @@ Result rename_file(const(char)[] oldPath, const(char)[] newPath) version (Windows) { if (!MoveFileW(oldPath.twstringz, newPath.twstringz)) - return Win32Result(GetLastError()); + return getlasterror_result(); } else version (Posix) { import core.sys.posix.stdio; if (int result = rename(oldPath.tstringz, newPath.tstringz)!= 0) - return PosixResult(result); + return posix_result(result); } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result copy_file(const(char)[] oldPath, const(char)[] newPath, bool overwriteExisting = false) @@ -170,7 +170,7 @@ Result copy_file(const(char)[] oldPath, const(char)[] newPath, bool overwriteExi version (Windows) { if (!CopyFileW(oldPath.twstringz, newPath.twstringz, !overwriteExisting)) - return Win32Result(GetLastError()); + return getlasterror_result(); } else version (Posix) { @@ -180,7 +180,7 @@ Result copy_file(const(char)[] oldPath, const(char)[] newPath, bool overwriteExi else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result get_path(ref const File file, ref char[] buffer) @@ -193,11 +193,11 @@ Result get_path(ref const File file, ref char[] buffer) DWORD dwPathLen = tmp.length - 1; DWORD result = GetFinalPathNameByHandleW(cast(HANDLE)file.handle, tmp.ptr, dwPathLen, FILE_NAME_OPENED); if (result == 0 || result > dwPathLen) - return Win32Result(GetLastError()); + return getlasterror_result(); size_t pathLen = tmp[0..result].uniConvert(buffer); if (!pathLen) - return InternalResult(InternalCode.BufferTooSmall); + return InternalResult.buffer_too_small; if (buffer.length >= 4 && buffer[0..4] == `\\?\`) buffer = buffer[4..pathLen]; else @@ -210,10 +210,10 @@ Result get_path(ref const File file, ref char[] buffer) char[PATH_MAX] src = void; int r = fcntl(file.fd, F_GETPATH, src.ptr); if (r == -1) - return PosixResult(errno); + return errno_result(); size_t l = strlen(src.ptr); if (l > buffer.length) - return InternalResult(InternalCode.BufferTooSmall); + return InternalResult.buffer_too_small; buffer[0..l] = src[0..l]; buffer = buffer[0..l]; } @@ -221,18 +221,18 @@ Result get_path(ref const File file, ref char[] buffer) { ptrdiff_t r = readlink(tconcat("/proc/self/fd/", file.fd, '\0').ptr, buffer.ptr, buffer.length); if (r == -1) - return PosixResult(errno); + return errno_result(); if (r == buffer.length) { // TODO: if r == buffer.length, truncation MAY have occurred, but also maybe not... // is there any way to fix this? for now, we'll just assume it did and return an error - return InternalResult(InternalCode.BufferTooSmall); + return InternalResult.buffer_too_small; } buffer = buffer[0..r]; } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result set_file_times(ref File file, const SystemTime* createTime, const SystemTime* accessTime, const SystemTime* writeTime); @@ -243,7 +243,7 @@ Result get_file_attributes(const(char)[] path, out FileAttributes outAttributes) { WIN32_FILE_ATTRIBUTE_DATA attrData = void; if (!GetFileAttributesExW(path.twstringz, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &attrData)) - return Win32Result(GetLastError()); + return getlasterror_result(); outAttributes.attributes = FileAttributeFlag.None; if ((attrData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) == FILE_ATTRIBUTE_HIDDEN) @@ -270,7 +270,7 @@ Result get_file_attributes(const(char)[] path, out FileAttributes outAttributes) else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result get_attributes(ref const File file, out FileAttributes outAttributes) @@ -282,9 +282,9 @@ Result get_attributes(ref const File file, out FileAttributes outAttributes) FILE_BASIC_INFO basicInfo = void; FILE_STANDARD_INFO standardInfo = void; if (!GetFileInformationByHandleEx(cast(HANDLE)file.handle, FILE_INFO_BY_HANDLE_CLASS.FileBasicInfo, &basicInfo, FILE_BASIC_INFO.sizeof)) - return Win32Result(GetLastError()); + return getlasterror_result(); if (!GetFileInformationByHandleEx(cast(HANDLE)file.handle, FILE_INFO_BY_HANDLE_CLASS.FileStandardInfo, &standardInfo, FILE_STANDARD_INFO.sizeof)) - return Win32Result(GetLastError()); + return getlasterror_result(); outAttributes.attributes = FileAttributeFlag.None; if ((basicInfo.FileAttributes & FILE_ATTRIBUTE_HIDDEN) == FILE_ATTRIBUTE_HIDDEN) @@ -303,7 +303,7 @@ Result get_attributes(ref const File file, out FileAttributes outAttributes) outAttributes.accessTime = SysTime(basicInfo.LastAccessTime.QuadPart); outAttributes.writeTime = SysTime(basicInfo.LastWriteTime.QuadPart); - return Result.Success; + return Result.success; +/ } else version (Posix) @@ -314,14 +314,14 @@ Result get_attributes(ref const File file, out FileAttributes outAttributes) else static assert(0, "Not implemented"); - return InternalResult(InternalCode.Unsupported); + return InternalResult.unsupported; } void[] load_file(const(char)[] path, NoGCAllocator allocator = defaultAllocator()) { File f; Result r = f.open(path, FileOpenMode.ReadExisting); - if (!r && r.get_FileResult == FileResult.NotFound) + if (!r && r.file_result == FileResult.NotFound) return null; assert(r, "TODO: handle error"); ulong size = f.get_size(); @@ -379,7 +379,7 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags dwCreationDisposition = OPEN_ALWAYS; break; default: - return InternalResult(InternalCode.InvalidParameter); + return InternalResult.invalid_parameter; } uint dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL; @@ -392,7 +392,7 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags file.handle = CreateFileW(path.twstringz, dwDesiredAccess, dwShareMode, null, dwCreationDisposition, dwFlagsAndAttributes, null); if (file.handle == INVALID_HANDLE_VALUE) - return Win32Result(GetLastError()); + return getlasterror_result(); if (mode == FileOpenMode.WriteAppend || mode == FileOpenMode.ReadWriteAppend) SetFilePointer(file.handle, 0, null, FILE_END); @@ -429,7 +429,7 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags flags = O_RDWR | O_APPEND | O_CREAT; break; default: - return InternalResult(InternalCode.InvalidParameter); + return InternalResult.invalid_parameter; } flags |= O_CLOEXEC; @@ -441,7 +441,7 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags int fd = core.sys.posix.fcntl.open(path.tstringz, flags, 0b110_110_110); if (fd < 0) - return PosixResult(errno); + return errno_result(); file.fd = fd; version (Darwin) { @@ -467,7 +467,7 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } bool is_open(ref const File file) @@ -532,7 +532,7 @@ Result set_size(ref File file, ulong size) if (size > curFileSize) { if (!file.set_pos(curFileSize)) - return Win32Result(GetLastError()); + return getlasterror_result(); // zero-fill char[4096] buf = void; @@ -555,19 +555,19 @@ Result set_size(ref File file, ulong size) else { if (!file.set_pos(size)) - return Win32Result(GetLastError()); + return getlasterror_result(); if (!SetEndOfFile(file.handle)) - return Win32Result(GetLastError()); + return getlasterror_result(); } } else version (Posix) { if (ftruncate(file.fd, size)) - return PosixResult(errno); + return errno_result(); } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } ulong get_pos(ref const File file) @@ -593,17 +593,17 @@ Result set_pos(ref File file, ulong offset) LARGE_INTEGER liDistanceToMove = void; liDistanceToMove.QuadPart = offset; if (!SetFilePointerEx(file.handle, liDistanceToMove, null, FILE_BEGIN)) - return Win32Result(GetLastError()); + return getlasterror_result(); } else version (Posix) { off_t rc = lseek(file.fd, offset, SEEK_SET); if (rc < 0) - return PosixResult(errno); + return errno_result(); } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result read(ref File file, void[] buffer, out size_t bytesRead) @@ -616,7 +616,7 @@ Result read(ref File file, void[] buffer, out size_t bytesRead) if (!ReadFile(file.handle, buffer.ptr, cast(uint)min(buffer.length, uint.max), &dwBytesRead, null)) { DWORD lastError = GetLastError(); - return (lastError == ERROR_BROKEN_PIPE) ? Result.Success : Win32Result(lastError); + return (lastError == ERROR_BROKEN_PIPE) ? Result.success : win32_result(lastError); } bytesRead = dwBytesRead; } @@ -624,12 +624,12 @@ Result read(ref File file, void[] buffer, out size_t bytesRead) { ptrdiff_t n = core.sys.posix.unistd.read(file.fd, buffer.ptr, buffer.length); if (n < 0) - return PosixResult(errno); + return errno_result(); bytesRead = n; } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result read_at(ref File file, void[] buffer, ulong offset, out size_t bytesRead) @@ -637,7 +637,7 @@ Result read_at(ref File file, void[] buffer, ulong offset, out size_t bytesRead) version (Windows) { if (buffer.length > DWORD.max) - return InternalResult(InternalCode.InvalidParameter); + return InternalResult.invalid_parameter; OVERLAPPED o; o.Offset = cast(DWORD)offset; @@ -646,8 +646,9 @@ Result read_at(ref File file, void[] buffer, ulong offset, out size_t bytesRead) DWORD dwBytesRead; if (!ReadFile(file.handle, buffer.ptr, cast(DWORD)buffer.length, &dwBytesRead, &o)) { - if (GetLastError() != ERROR_HANDLE_EOF) - return Win32Result(GetLastError()); + Result error = getlasterror_result(); + if (error.systemCode != ERROR_HANDLE_EOF) + return error; } bytesRead = dwBytesRead; } @@ -655,12 +656,12 @@ Result read_at(ref File file, void[] buffer, ulong offset, out size_t bytesRead) { ssize_t n = pread(file.fd, buffer.ptr, buffer.length, offset); if (n < 0) - return PosixResult(errno); + return errno_result(); bytesRead = n; } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result write(ref File file, const(void)[] data, out size_t bytesWritten) @@ -669,19 +670,19 @@ Result write(ref File file, const(void)[] data, out size_t bytesWritten) { DWORD dwBytesWritten; if (!WriteFile(file.handle, data.ptr, cast(uint)data.length, &dwBytesWritten, null)) - return Win32Result(GetLastError()); + return getlasterror_result(); bytesWritten = dwBytesWritten; } else version (Posix) { ptrdiff_t n = core.sys.posix.unistd.write(file.fd, data.ptr, data.length); if (n < 0) - return PosixResult(errno); + return errno_result(); bytesWritten = n; } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result write_at(ref File file, const(void)[] data, ulong offset, out size_t bytesWritten) @@ -689,7 +690,7 @@ Result write_at(ref File file, const(void)[] data, ulong offset, out size_t byte version (Windows) { if (data.length > DWORD.max) - return InternalResult(InternalCode.InvalidParameter); + return InternalResult.invalid_parameter; OVERLAPPED o; o.Offset = cast(DWORD)offset; @@ -697,19 +698,19 @@ Result write_at(ref File file, const(void)[] data, ulong offset, out size_t byte DWORD dwBytesWritten; if (!WriteFile(file.handle, data.ptr, cast(DWORD)data.length, &dwBytesWritten, &o)) - return Win32Result(GetLastError()); + return getlasterror_result(); bytesWritten = dwBytesWritten; } else version (Posix) { ptrdiff_t n = pwrite(file.fd, data.ptr, data.length, offset); if (n < 0) - return PosixResult(errno); + return errno_result(); bytesWritten = n; } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } Result flush(ref File file) @@ -717,19 +718,19 @@ Result flush(ref File file) version (Windows) { if (!FlushFileBuffers(file.handle)) - return Win32Result(GetLastError()); + return getlasterror_result(); } else version (Posix) { if (fsync(file.fd)) - return PosixResult(errno); + return errno_result(); } else static assert(0, "Not implemented"); - return Result.Success; + return Result.success; } -FileResult get_FileResult(Result result) +FileResult file_result(Result result) { version (Windows) { @@ -771,13 +772,13 @@ Result get_temp_filename(ref char[] buffer, const(char)[] dstDir, const(char)[] wchar[MAX_PATH] tmp = void; if (!GetTempFileNameW(dstDir.twstringz, prefix.twstringz, 0, tmp.ptr)) - return Win32Result(GetLastError()); + return getlasterror_result(); size_t resLen = wcslen(tmp.ptr); resLen = tmp[((dstDir.length == 0 && tmp[0] == '\\') ? 1 : 0)..resLen].uniConvert(buffer); if (resLen == 0) { DeleteFileW(tmp.ptr); - return InternalResult(InternalCode.BufferTooSmall); + return InternalResult.buffer_too_small; } buffer = buffer[0 .. resLen]; } @@ -788,25 +789,14 @@ Result get_temp_filename(ref char[] buffer, const(char)[] dstDir, const(char)[] File file; file.fd = mkstemp(fn.ptr); if (file.fd == -1) - return PosixResult(errno); + return errno_result(); Result r = get_path(file, buffer); core.sys.posix.unistd.close(file.fd); return r; } else static assert(0, "Not implemented"); - return Result.Success; -} - -version (Windows) -{ - Result Win32Result(uint err) - => Result(err); -} -else version (Posix) -{ - Result PosixResult(int err) - => Result(err); + return Result.success; } diff --git a/src/urt/format/json.d b/src/urt/format/json.d index 544fbdf..997b9e1 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -13,12 +13,12 @@ public import urt.variant; nothrow @nogc: -Variant parseJson(const(char)[] text) +Variant parse_json(const(char)[] text) { - return parseNode(text); + return parse_node(text); } -ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, uint level = 0, uint indent = 2) +ptrdiff_t write_json(ref const Variant val, char[] buffer, bool dense = false, uint level = 0, uint indent = 2) { final switch (val.type) { @@ -76,9 +76,9 @@ ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, ui int inc = val.type == Variant.Type.Map ? 2 : 1; for (uint i = 0; i < val.count; i += inc) { - len += writeJson(val.value.n[i], null, dense, level + indent, indent); + len += write_json(val.value.n[i], null, dense, level + indent, indent); if (val.type == Variant.Type.Map) - len += writeJson(val.value.n[i + 1], null, dense, level + indent, indent); + len += write_json(val.value.n[i + 1], null, dense, level + indent, indent); } return len; } @@ -99,7 +99,7 @@ ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, ui if (!buffer.newline(written, level + indent)) return -1; } - ptrdiff_t len = writeJson(val.value.n[i], buffer[written .. $], dense, level + indent, indent); + ptrdiff_t len = write_json(val.value.n[i], buffer[written .. $], dense, level + indent, indent); if (len < 0) return -1; written += len; @@ -107,7 +107,7 @@ ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, ui { if (!buffer.append(written, ':') || (!dense && !buffer.append(written, ' '))) return -1; - len = writeJson(val.value.n[i + 1], buffer[written .. $], dense, level + indent, indent); + len = write_json(val.value.n[i + 1], buffer[written .. $], dense, level + indent, indent); if (len < 0) return -1; written += len; @@ -138,14 +138,14 @@ ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, ui assert(false, "TODO: implement quantity formatting for JSON"); if (val.isDouble()) - return val.asDouble().formatFloat(buffer); + return val.asDouble().format_float(buffer); // TODO: parse args? //format if (val.isUlong()) - return val.asUlong().formatUint(buffer); - return val.asLong().formatInt(buffer); + return val.asUlong().format_uint(buffer); + return val.asLong().format_int(buffer); case Variant.Type.User: // in order to text-ify a user type, we probably need a hash table of text-ify functions, which @@ -179,7 +179,7 @@ unittest ] }`; - Variant root = parseJson(doc); + Variant root = parse_json(doc); // check the data was parsed correctly... assert(root["nothing"].isNull); @@ -197,18 +197,18 @@ unittest char[1024] buffer = void; // check the dense writer... - assert(root["children"].writeJson(null, true) == 61); - assert(root["children"].writeJson(buffer, true) == 61); + assert(root["children"].write_json(null, true) == 61); + assert(root["children"].write_json(buffer, true) == 61); assert(buffer[0 .. 61] == `[{"name":"Jane Doe", "age":12}, {"name":"Jack Doe", "age":8}]`); // check the expanded writer - assert(root["children"].writeJson(null, false, 0, 1) == 83); - assert(root["children"].writeJson(buffer, false, 0, 1) == 83); + assert(root["children"].write_json(null, false, 0, 1) == 83); + assert(root["children"].write_json(buffer, false, 0, 1) == 83); assert(buffer[0 .. 83] == "[\n {\n \"name\": \"Jane Doe\",\n \"age\": 12\n },\n {\n \"name\": \"Jack Doe\",\n \"age\": 8\n }\n]"); // check indentation works properly - assert(root["children"].writeJson(null, false, 0, 2) == 95); - assert(root["children"].writeJson(buffer, false, 0, 2) == 95); + assert(root["children"].write_json(null, false, 0, 2) == 95); + assert(root["children"].write_json(buffer, false, 0, 2) == 95); assert(buffer[0 .. 95] == "[\n {\n \"name\": \"Jane Doe\",\n \"age\": 12\n },\n {\n \"name\": \"Jack Doe\",\n \"age\": 8\n }\n]"); // fabricate a JSON object @@ -220,7 +220,7 @@ unittest assert(write[0].asInt == 42); assert(write[1]["wow"].isTrue); assert(write[1]["bogus"].asBool == false); - assert(write.writeJson(buffer, true) == 33); + assert(write.write_json(buffer, true) == 33); assert(buffer[0 .. 33] == "[42, {\"wow\":true, \"bogus\":false}]"); } @@ -244,7 +244,7 @@ ptrdiff_t newline(char[] buffer, ref ptrdiff_t offset, int level) return true; } -Variant parseNode(ref const(char)[] text) +Variant parse_node(ref const(char)[] text) { text = text.trimFront(); @@ -307,7 +307,7 @@ Variant parseNode(ref const(char)[] text) else expectComma = true; - tmp ~= parseNode(text); + tmp ~= parse_node(text); if (!isArray) { assert(tmp.back().isString()); @@ -315,7 +315,7 @@ Variant parseNode(ref const(char)[] text) text = text.trimFront; assert(text.length > 0 && text[0] == ':'); text = text[1 .. $].trimFront; - tmp ~= parseNode(text); + tmp ~= parse_node(text); } } assert(text.length > 0); @@ -331,7 +331,7 @@ Variant parseNode(ref const(char)[] text) bool neg = text[0] == '-'; size_t taken = void; ulong div = void; - ulong value = text[neg .. $].parseUintWithDecimal(div, &taken, 10); + ulong value = text[neg .. $].parse_uint_with_decimal(div, &taken, 10); assert(taken > 0); text = text[taken + neg .. $]; diff --git a/src/urt/hash.d b/src/urt/hash.d index 7c068e6..0068baa 100644 --- a/src/urt/hash.d +++ b/src/urt/hash.d @@ -6,10 +6,10 @@ version = BranchIsFasterThanMod; nothrow @nogc: -alias fnv1aHash = fnv1Hash!(uint, true); -alias fnv1aHash64 = fnv1Hash!(ulong, true); +alias fnv1a = fnv1!(uint, true); +alias fnv1a64 = fnv1!(ulong, true); -T fnv1Hash(T, bool alternate)(const ubyte[] s) pure nothrow @nogc +T fnv1(T, bool alternate)(const ubyte[] s) pure nothrow @nogc if (is(T == ushort) || is(T == uint) || is(T == ulong)) { static if (is(T == ushort)) @@ -47,7 +47,7 @@ T fnv1Hash(T, bool alternate)(const ubyte[] s) pure nothrow @nogc unittest { - enum hash = fnv1aHash(cast(ubyte[])"hello world"); + enum hash = fnv1a(cast(ubyte[])"hello world"); static assert(hash == 0xD58B3FA7); } diff --git a/src/urt/inet.d b/src/urt/inet.d index 969def7..1c2e019 100644 --- a/src/urt/inet.d +++ b/src/urt/inet.d @@ -97,11 +97,11 @@ nothrow @nogc: size_t toHash() const pure { - import urt.hash : fnv1aHash, fnv1aHash64; + import urt.hash : fnv1a, fnv1a64; static if (size_t.sizeof > 4) - return fnv1aHash64(b[]); + return fnv1a64(b[]); else - return fnv1aHash(b[]); + return fnv1a(b[]); } ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure @@ -113,7 +113,7 @@ nothrow @nogc: { if (i > 0) tmp[offset++] = '.'; - offset += b[i].formatInt(tmp[offset..$]); + offset += b[i].format_int(tmp[offset..$]); } if (buffer.ptr && tmp.ptr == stackBuffer.ptr) @@ -129,22 +129,22 @@ nothrow @nogc: { ubyte[4] t; size_t offset = 0, len; - ulong i = s[offset..$].parseInt(&len); + ulong i = s[offset..$].parse_int(&len); offset += len; if (len == 0 || i > 255 || s.length < offset + 1 || s[offset++] != '.') return -1; t[0] = cast(ubyte)i; - i = s[offset..$].parseInt(&len); + i = s[offset..$].parse_int(&len); offset += len; if (len == 0 || i > 255 || s.length < offset + 1 || s[offset++] != '.') return -1; t[1] = cast(ubyte)i; - i = s[offset..$].parseInt(&len); + i = s[offset..$].parse_int(&len); offset += len; if (len == 0 || i > 255 || s.length < offset + 1 || s[offset++] != '.') return -1; t[2] = cast(ubyte)i; - i = s[offset..$].parseInt(&len); + i = s[offset..$].parse_int(&len); offset += len; if (len == 0 || i > 255) return -1; @@ -224,11 +224,11 @@ nothrow @nogc: size_t toHash() const pure { - import urt.hash : fnv1aHash, fnv1aHash64; + import urt.hash : fnv1a, fnv1a64; static if (size_t.sizeof > 4) - return fnv1aHash64(cast(ubyte[])s[]); + return fnv1a64(cast(ubyte[])s[]); else - return fnv1aHash(cast(ubyte[])s[]); + return fnv1a(cast(ubyte[])s[]); } ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure @@ -275,7 +275,7 @@ nothrow @nogc: tmp[offset++] = ':'; continue; } - offset += s[i].formatInt(tmp[offset..$], 16); + offset += s[i].format_int(tmp[offset..$], 16); ++i; } @@ -360,7 +360,7 @@ nothrow @nogc: size_t offset = addr.toString(tmp, null, null); tmp[offset++] = '/'; - offset += prefixLen.formatInt(tmp[offset..$]); + offset += prefixLen.format_int(tmp[offset..$]); if (buffer.ptr && tmp.ptr == stackBuffer.ptr) { @@ -378,7 +378,7 @@ nothrow @nogc: if (taken < 0 || s.length <= taken + 1 || s[taken++] != '/') return -1; size_t t; - ulong plen = s[taken..$].parseInt(&t); + ulong plen = s[taken..$].parse_int(&t); if (t == 0 || plen > 32) return -1; addr = a; @@ -449,7 +449,7 @@ nothrow @nogc: size_t offset = addr.toString(tmp, null, null); tmp[offset++] = '/'; - offset += prefixLen.formatInt(tmp[offset..$]); + offset += prefixLen.format_int(tmp[offset..$]); if (buffer.ptr && tmp.ptr == stackBuffer.ptr) { @@ -467,7 +467,7 @@ nothrow @nogc: if (taken < 0 || s.length <= taken + 1 || s[taken++] != '/') return -1; size_t t; - ulong plen = s[taken..$].parseInt(&t); + ulong plen = s[taken..$].parse_int(&t); if (t == 0 || plen > 32) return -1; addr = a; @@ -547,7 +547,7 @@ nothrow @nogc: { offset = _a.ipv4.addr.toString(tmp, null, null); tmp[offset++] = ':'; - offset += _a.ipv4.port.formatInt(tmp[offset..$]); + offset += _a.ipv4.port.format_int(tmp[offset..$]); } else { @@ -555,7 +555,7 @@ nothrow @nogc: offset = 1 + _a.ipv6.addr.toString(tmp[1 .. $], null, null); tmp[offset++] = ']'; tmp[offset++] = ':'; - offset += _a.ipv6.port.formatInt(tmp[offset..$]); + offset += _a.ipv6.port.format_int(tmp[offset..$]); } if (buffer.ptr && tmp.ptr == stackBuffer.ptr) @@ -602,7 +602,7 @@ nothrow @nogc: if (s.length > taken && s[taken] == ':') { size_t t; - ulong p = s[++taken..$].parseInt(&t); + ulong p = s[++taken..$].parse_int(&t); if (t == 0 || p > 0xFFFF) return -1; taken += t; diff --git a/src/urt/mem/alloc.d b/src/urt/mem/alloc.d index 7408808..5ec148a 100644 --- a/src/urt/mem/alloc.d +++ b/src/urt/mem/alloc.d @@ -11,7 +11,7 @@ void[] alloc(size_t size) nothrow @nogc return malloc(size)[0 .. size]; } -void[] allocAligned(size_t size, size_t alignment) nothrow @nogc +void[] alloc_aligned(size_t size, size_t alignment) nothrow @nogc { import urt.util : isPowerOf2, max; alignment = max(alignment, (void*).sizeof); @@ -60,24 +60,24 @@ void[] realloc(void[] mem, size_t newSize) nothrow @nogc return core.stdc.stdlib.realloc(mem.ptr, newSize)[0 .. newSize]; } -void[] reallocAligned(void[] mem, size_t newSize, size_t alignment) nothrow @nogc +void[] realloc_aligned(void[] mem, size_t newSize, size_t alignment) nothrow @nogc { import urt.util : isPowerOf2, min, max; alignment = max(alignment, (void*).sizeof); assert(isPowerOf2(alignment), "Alignment must be a power of two!"); - void[] newAlloc = newSize > 0 ? allocAligned(newSize, alignment) : null; + void[] newAlloc = newSize > 0 ? alloc_aligned(newSize, alignment) : null; if (newAlloc !is null && mem !is null) { size_t toCopy = min(mem.length, newSize); newAlloc[0 .. toCopy] = mem[0 .. toCopy]; } - freeAligned(mem); + free_aligned(mem); return newAlloc; } -// NOTE: This function is only compatible with allocAligned! +// NOTE: This function is only compatible with alloc_aligned! void[] expand(void[] mem, size_t newSize) nothrow @nogc { version (Windows) @@ -107,7 +107,7 @@ void free(void[] mem) nothrow @nogc core.stdc.stdlib.free(mem.ptr); } -void freeAligned(void[] mem) nothrow @nogc +void free_aligned(void[] mem) nothrow @nogc { version (Windows) { @@ -140,11 +140,11 @@ size_t memsize(void* ptr) nothrow @nogc unittest { - void[] mem = allocAligned(16, 8); + void[] mem = alloc_aligned(16, 8); size_t s = memsize(mem.ptr); mem = expand(mem, 8); mem = expand(mem, 16); - freeAligned(mem); + free_aligned(mem); } diff --git a/src/urt/mem/allocator.d b/src/urt/mem/allocator.d index 75cadff..2d3f295 100644 --- a/src/urt/mem/allocator.d +++ b/src/urt/mem/allocator.d @@ -356,12 +356,12 @@ class Mallocator : NoGCAllocator override void[] alloc(size_t size, size_t alignment = DefaultAlign) nothrow @nogc { - return urt.mem.alloc.allocAligned(size, alignment); + return urt.mem.alloc.alloc_aligned(size, alignment); } override void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) nothrow @nogc { - return urt.mem.alloc.reallocAligned(mem, newSize, alignment); + return urt.mem.alloc.realloc_aligned(mem, newSize, alignment); } override void[] expand(void[] mem, size_t newSize) nothrow @@ -371,7 +371,7 @@ class Mallocator : NoGCAllocator override void free(void[] mem) nothrow @nogc { - urt.mem.alloc.freeAligned(mem); + urt.mem.alloc.free_aligned(mem); } private: diff --git a/src/urt/mem/scratchpad.d b/src/urt/mem/scratchpad.d index 83a3abb..dcc681b 100644 --- a/src/urt/mem/scratchpad.d +++ b/src/urt/mem/scratchpad.d @@ -11,7 +11,7 @@ enum size_t NumScratchBuffers = 4; static assert(MaxScratchpadSize.isPowerOf2, "Scratchpad size must be a power of 2"); -void[] allocScratchpad(size_t size = MaxScratchpadSize) +void[] alloc_scratchpad(size_t size = MaxScratchpadSize) { if (size > MaxScratchpadSize) { @@ -23,13 +23,13 @@ void[] allocScratchpad(size_t size = MaxScratchpadSize) size_t maskBits = size / WindowSize; size_t mask = (1 << maskBits) - 1; - for (size_t page = 0; page < scratchpadAlloc.length; ++page) + for (size_t page = 0; page < scratchpad_alloc.length; ++page) { for (size_t window = 0; window < 8; window += maskBits) { - if ((scratchpadAlloc[page] & (mask << window)) == 0) + if ((scratchpad_alloc[page] & (mask << window)) == 0) { - scratchpadAlloc[page] |= mask << window; + scratchpad_alloc[page] |= mask << window; return scratchpad[page*MaxScratchpadSize + window*WindowSize .. page*MaxScratchpadSize + window*WindowSize + size]; } } @@ -40,7 +40,7 @@ void[] allocScratchpad(size_t size = MaxScratchpadSize) return null; } -void freeScratchpad(void[] mem) +void free_scratchpad(void[] mem) { size_t page = (cast(size_t)mem.ptr - cast(size_t)scratchpad.ptr) / MaxScratchpadSize; size_t window = (cast(size_t)mem.ptr - cast(size_t)scratchpad.ptr) % MaxScratchpadSize / WindowSize; @@ -48,8 +48,8 @@ void freeScratchpad(void[] mem) size_t maskBits = mem.length / WindowSize; size_t mask = (1 << maskBits) - 1; - assert((scratchpadAlloc[page] & (mask << window)) == (mask << window), "Freeing unallocated scratchpad memory!"); - scratchpadAlloc[page] &= ~(mask << window); + assert((scratchpad_alloc[page] & (mask << window)) == (mask << window), "Freeing unallocated scratchpad memory!"); + scratchpad_alloc[page] &= ~(mask << window); } private: @@ -57,31 +57,31 @@ private: enum WindowSize = MaxScratchpadSize / 8; __gshared ubyte[MaxScratchpadSize*NumScratchBuffers] scratchpad; -__gshared ubyte[NumScratchBuffers] scratchpadAlloc; +__gshared ubyte[NumScratchBuffers] scratchpad_alloc; unittest { - void[] t = allocScratchpad(MaxScratchpadSize); + void[] t = alloc_scratchpad(MaxScratchpadSize); assert(t.length == MaxScratchpadSize); - void[] t2 = allocScratchpad(MaxScratchpadSize / 2); + void[] t2 = alloc_scratchpad(MaxScratchpadSize / 2); assert(t2.length == MaxScratchpadSize / 2); - void[] t3 = allocScratchpad(MaxScratchpadSize / 2); + void[] t3 = alloc_scratchpad(MaxScratchpadSize / 2); assert(t3.length == MaxScratchpadSize / 2); - void[] t4 = allocScratchpad(MaxScratchpadSize / 4); + void[] t4 = alloc_scratchpad(MaxScratchpadSize / 4); assert(t4.length == MaxScratchpadSize / 4); - void[] t5 = allocScratchpad(MaxScratchpadSize / 8); + void[] t5 = alloc_scratchpad(MaxScratchpadSize / 8); assert(t5.length == MaxScratchpadSize / 8); - void[] t6 = allocScratchpad(MaxScratchpadSize / 4); + void[] t6 = alloc_scratchpad(MaxScratchpadSize / 4); assert(t6.length == MaxScratchpadSize / 4); - void[] t7 = allocScratchpad(MaxScratchpadSize / 8); + void[] t7 = alloc_scratchpad(MaxScratchpadSize / 8); assert(t7.length == MaxScratchpadSize / 8); - freeScratchpad(t); - freeScratchpad(t7); - freeScratchpad(t5); - freeScratchpad(t4); - freeScratchpad(t6); - freeScratchpad(t2); - freeScratchpad(t3); + free_scratchpad(t); + free_scratchpad(t7); + free_scratchpad(t5); + free_scratchpad(t4); + free_scratchpad(t6); + free_scratchpad(t2); + free_scratchpad(t3); } diff --git a/src/urt/mem/string.d b/src/urt/mem/string.d index a04f5e9..b00618a 100644 --- a/src/urt/mem/string.d +++ b/src/urt/mem/string.d @@ -56,9 +56,9 @@ struct CacheString import urt.hash; static if (size_t.sizeof == 4) - return fnv1aHash(cast(ubyte[])toString()); + return fnv1a(cast(ubyte[])toString()); else - return fnv1aHash64(cast(ubyte[])toString()); + return fnv1a64(cast(ubyte[])toString()); } private: diff --git a/src/urt/mem/temp.d b/src/urt/mem/temp.d index 6f2d28b..aabe847 100644 --- a/src/urt/mem/temp.d +++ b/src/urt/mem/temp.d @@ -22,16 +22,16 @@ void[] talloc(size_t size) nothrow @nogc return null; } - if (allocOffset + size > TempMemSize) - allocOffset = 0; + if (alloc_offset + size > TempMemSize) + alloc_offset = 0; - void[] mem = tempMem[allocOffset .. allocOffset + size]; - allocOffset += size; + void[] mem = tempMem[alloc_offset .. alloc_offset + size]; + alloc_offset += size; return mem; } -void[] tallocAligned(size_t size, size_t alignment) nothrow @nogc +void[] talloc_aligned(size_t size, size_t alignment) nothrow @nogc { assert(false); } @@ -50,19 +50,19 @@ void[] trealloc(void[] mem, size_t newSize) nothrow @nogc return r; } -void[] treallocAligned(void[] mem, size_t newSize, size_t alignment) nothrow @nogc +void[] trealloc_aligned(void[] mem, size_t newSize, size_t alignment) nothrow @nogc { assert(false); } void[] texpand(void[] mem, size_t newSize) nothrow @nogc { - if (mem.ptr + mem.length != tempMem.ptr + allocOffset) + if (mem.ptr + mem.length != tempMem.ptr + alloc_offset) return null; ptrdiff_t grow = newSize - mem.length; - if (cast(size_t)(allocOffset + grow) > TempMemSize) + if (cast(size_t)(alloc_offset + grow) > TempMemSize) return null; - allocOffset += grow; + alloc_offset += grow; return mem.ptr[0 .. newSize]; } @@ -77,23 +77,23 @@ char* tstringz(const(char)[] str) nothrow @nogc return null; size_t len = str.length; - if (allocOffset + len + 1 > TempMemSize) - allocOffset = 0; + if (alloc_offset + len + 1 > TempMemSize) + alloc_offset = 0; - char* r = cast(char*)tempMem.ptr + allocOffset; + char* r = cast(char*)tempMem.ptr + alloc_offset; r[0 .. len] = str[]; r[len] = '\0'; - allocOffset += len + 1; + alloc_offset += len + 1; return r; } char[] tstring(T)(auto ref T value) { import urt.string.format : toString; - ptrdiff_t r = toString(value, cast(char[])tempMem[allocOffset..$]); + ptrdiff_t r = toString(value, cast(char[])tempMem[alloc_offset..$]); if (r < 0) { - allocOffset = 0; + alloc_offset = 0; r = toString(value, cast(char[])tempMem[0..TempMemSize / 2]); if (r < 0) { @@ -101,34 +101,34 @@ char[] tstring(T)(auto ref T value) return null; } } - char[] result = cast(char[])tempMem[allocOffset .. allocOffset + r]; - allocOffset += r; + char[] result = cast(char[])tempMem[alloc_offset .. alloc_offset + r]; + alloc_offset += r; return result; } char[] tconcat(Args...)(ref Args args) { import urt.string.format : concat; - char[] r = concat(cast(char[])tempMem[allocOffset..$], args); + char[] r = concat(cast(char[])tempMem[alloc_offset..$], args); if (!r) { - allocOffset = 0; + alloc_offset = 0; r = concat(cast(char[])tempMem[0..TempMemSize / 2], args); } - allocOffset += r.length; + alloc_offset += r.length; return r; } char[] tformat(Args...)(const(char)[] fmt, ref Args args) { import urt.string.format : format; - char[] r = format(cast(char[])tempMem[allocOffset..$], fmt, args); + char[] r = format(cast(char[])tempMem[alloc_offset..$], fmt, args); if (!r) { - allocOffset = 0; + alloc_offset = 0; r = format(cast(char[])tempMem[0..TempMemSize / 2], fmt, args); } - allocOffset += r.length; + alloc_offset += r.length; return r; } @@ -170,4 +170,4 @@ private: private: static void[TempMemSize] tempMem; -static ushort allocOffset = 0; +static ushort alloc_offset = 0; diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index faa2941..bec2f81 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -6,39 +6,47 @@ alias Alias(T) = T; alias AliasSeq(TList...) = TList; -template intForWidth(size_t width, bool signed = false) +template IntForWidth(size_t width, bool signed = false) { static if (width <= 8 && !signed) - alias intForWidth = ubyte; + alias IntForWidth = ubyte; else static if (width <= 8 && signed) - alias intForWidth = byte; + alias IntForWidth = byte; else static if (width <= 16 && !signed) - alias intForWidth = ushort; + alias IntForWidth = ushort; else static if (width <= 16 && signed) - alias intForWidth = short; + alias IntForWidth = short; else static if (width <= 32 && !signed) - alias intForWidth = uint; + alias IntForWidth = uint; else static if (width <= 32 && signed) - alias intForWidth = int; + alias IntForWidth = int; else static if (width <= 64 && !signed) - alias intForWidth = ulong; + alias IntForWidth = ulong; else static if (width <= 64 && signed) - alias intForWidth = long; + alias IntForWidth = long; } -template staticMap(alias fun, args...) +template STATIC_MAP(alias fun, args...) { - alias staticMap = AliasSeq!(); + alias STATIC_MAP = AliasSeq!(); static foreach (arg; args) - staticMap = AliasSeq!(staticMap, fun!arg); + STATIC_MAP = AliasSeq!(STATIC_MAP, fun!arg); } -template staticIndexOf(args...) +template STATIC_FILTER(alias filter, args...) +{ + alias STATIC_FILTER = AliasSeq!(); + static foreach (arg; args) + static if (filter!arg) + STATIC_FILTER = AliasSeq!(STATIC_FILTER, arg); +} + +template static_index_of(args...) if (args.length >= 1) { - enum staticIndexOf = { + enum static_index_of = { static foreach (idx, arg; args[1 .. $]) - static if (isSame!(args[0], arg)) + static if (is_same!(args[0], arg)) // `if (__ctfe)` is redundant here but avoids the "Unreachable code" warning. if (__ctfe) return idx; return -1; @@ -63,8 +71,8 @@ template EnumKeys(E) private alias EnumStrings = __traits(allMembers, E); } -E enumFromString(E)(const(char)[] key) -if (is(E == enum)) +E enum_from_string(E)(const(char)[] key) + if (is(E == enum)) { foreach (i, k; EnumKeys!E) if (key[] == k[]) @@ -75,16 +83,16 @@ if (is(E == enum)) private: -template isSame(alias a, alias b) +template is_same(alias a, alias b) { static if (!is(typeof(&a && &b)) // at least one is an rvalue - && __traits(compiles, { enum isSame = a == b; })) // c-t comparable - enum isSame = a == b; + && __traits(compiles, { enum is_same = a == b; })) // c-t comparable + enum is_same = a == b; else - enum isSame = __traits(isSame, a, b); + enum is_same = __traits(isSame, a, b); } // TODO: remove after https://github.com/dlang/dmd/pull/11320 and https://issues.dlang.org/show_bug.cgi?id=21889 are fixed -template isSame(A, B) +template is_same(A, B) { - enum isSame = is(A == B); + enum is_same = is(A == B); } diff --git a/src/urt/meta/tuple.d b/src/urt/meta/tuple.d index a2fe66f..f29e758 100644 --- a/src/urt/meta/tuple.d +++ b/src/urt/meta/tuple.d @@ -64,20 +64,20 @@ template Tuple(Specs...) // Returns Specs for a subtuple this[from .. to] preserving field // names if any. - alias sliceSpecs(size_t from, size_t to) = staticMap!(expandSpec, fieldSpecs[from .. to]); + alias sliceSpecs(size_t from, size_t to) = STATIC_MAP!(expandSpec, fieldSpecs[from .. to]); struct Tuple { nothrow @nogc: - alias Types = staticMap!(extractType, fieldSpecs); + alias Types = STATIC_MAP!(extractType, fieldSpecs); private alias _Fields = Specs; /** * The names of the `Tuple`'s components. Unnamed fields have empty names. */ - alias fieldNames = staticMap!(extractName, fieldSpecs); + alias fieldNames = STATIC_MAP!(extractName, fieldSpecs); /** * Use `t.expand` for a `Tuple` `t` to expand it into its @@ -369,7 +369,7 @@ template Tuple(Specs...) } import std.range : roundRobin, iota; - alias NewTupleT = Tuple!(staticMap!(GetItem, aliasSeqOf!( + alias NewTupleT = Tuple!(STATIC_MAP!(GetItem, aliasSeqOf!( roundRobin(iota(nT), iota(nT, 2*nT))))); return *(() @trusted => cast(NewTupleT*)&this)(); } @@ -545,7 +545,7 @@ template Tuple(Specs...) } else { - formattedWrite(sink, fmt.nested, staticMap!(sharedToString, this.expand)); + formattedWrite(sink, fmt.nested, STATIC_MAP!(sharedToString, this.expand)); } } else if (fmt.spec == 's') diff --git a/src/urt/package.d b/src/urt/package.d index 354632b..6e57129 100644 --- a/src/urt/package.d +++ b/src/urt/package.d @@ -25,10 +25,10 @@ void crt_bootup() initClock(); import urt.rand; - initRand(); + init_rand(); - import urt.dbg : setupAssertHandler; - setupAssertHandler(); + import urt.dbg : setup_assert_handler; + setup_assert_handler(); import urt.string.string : initStringAllocators; initStringAllocators(); diff --git a/src/urt/rand.d b/src/urt/rand.d index b4b0f1e..c54cd25 100644 --- a/src/urt/rand.d +++ b/src/urt/rand.d @@ -55,7 +55,7 @@ private: rng.state = rng.state * PCG_DEFAULT_MULTIPLIER_64 + rng.inc; } - package void initRand() + package void init_rand() { import urt.time; srand(getTime().ticks, cast(size_t)&globalRand); diff --git a/src/urt/range/package.d b/src/urt/range/package.d index 06de7f0..4d69c46 100644 --- a/src/urt/range/package.d +++ b/src/urt/range/package.d @@ -16,15 +16,15 @@ template map(fun...) auto map(Range)(Range r) if (isInputRange!(Unqual!Range)) { - import urt.meta : AliasSeq, staticMap; + import urt.meta : AliasSeq, STATIC_MAP; alias RE = ElementType!(Range); static if (fun.length > 1) { import std.functional : adjoin; - import urt.meta : staticIndexOf; + import urt.meta : static_index_of; - alias _funs = staticMap!(unaryFun, fun); + alias _funs = STATIC_MAP!(unaryFun, fun); alias _fun = adjoin!_funs; // Once https://issues.dlang.org/show_bug.cgi?id=5710 is fixed @@ -160,9 +160,9 @@ private struct MapResult(alias fun, Range) template reduce(fun...) if (fun.length >= 1) { - import urt.meta : staticMap; + import urt.meta : STATIC_MAP; - alias binfuns = staticMap!(binaryFun, fun); + alias binfuns = STATIC_MAP!(binaryFun, fun); static if (fun.length > 1) import urt.meta.tuple : tuple, isTuple; @@ -190,7 +190,7 @@ template reduce(fun...) { import std.exception : enforce; alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); - alias Args = staticMap!(ReduceSeedType!E, binfuns); + alias Args = STATIC_MAP!(ReduceSeedType!E, binfuns); static if (isInputRange!R) { @@ -245,7 +245,7 @@ template reduce(fun...) private auto reducePreImpl(R, Args...)(R r, ref Args args) { - alias Result = staticMap!(Unqual, Args); + alias Result = STATIC_MAP!(Unqual, Args); static if (is(Result == Args)) alias result = args; else diff --git a/src/urt/result.d b/src/urt/result.d index 564b15c..3769e8b 100644 --- a/src/urt/result.d +++ b/src/urt/result.d @@ -3,18 +3,10 @@ module urt.result; nothrow @nogc: -enum InternalCode -{ - Success = 0, - BufferTooSmall, - InvalidParameter, - Unsupported -} - struct Result { nothrow @nogc: - enum Success = Result(); + enum success = Result(); uint systemCode = 0; @@ -32,44 +24,35 @@ version (Windows) { import core.sys.windows.windows; - Result InternalResult(InternalCode code) + enum InternalResult : Result { - switch (code) - { - case InternalCode.Success: - return Result(); - case InternalCode.BufferTooSmall: - return Result(ERROR_INSUFFICIENT_BUFFER); - case InternalCode.InvalidParameter: - return Result(ERROR_INVALID_PARAMETER); - default: - return Result(ERROR_INVALID_FUNCTION); // InternalCode.Unsupported - } + success = Result.success, + buffer_too_small = Result(ERROR_INSUFFICIENT_BUFFER), + invalid_parameter = Result(ERROR_INVALID_PARAMETER), + data_error = Result(ERROR_INVALID_DATA), + unsupported = Result(ERROR_INVALID_FUNCTION), } - Result Win32Result(uint err) + Result win32_result(uint err) => Result(err); + Result getlasterror_result() + => Result(GetLastError()); } else version (Posix) { import core.stdc.errno; - Result InternalResult(InternalCode code) + + enum InternalResult : Result { - switch (code) - { - case InternalCode.Success: - return Result(); - case InternalCode.BufferTooSmall: - return Result(ERANGE); - case InternalCode.InvalidParameter: - return Result(EINVAL); - default: - return Result(ENOTSUP); // InternalCode.Unsupported - } + success = Result.success, + buffer_too_small = Result(ERANGE), + invalid_parameter = Result(EINVAL), + data_error = Result(EILSEQ), + unsupported = Result(ENOTSUP), } - Result PosixResult(int err) + Result posix_result(int err) => Result(err); - Result ErrnoResult() + Result errno_result() => Result(errno); } diff --git a/src/urt/socket.d b/src/urt/socket.d index 800f72d..e61e29c 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -57,119 +57,119 @@ nothrow @nogc: enum SocketResult { - Success, - Failure, - WouldBlock, - NoBuffer, - NetworkDown, - ConnectionRefused, - ConnectionReset, - ConnectionAborted, - ConnectionClosed, - Interrupted, - InvalidSocket, - InvalidArgument, + success, + failure, + would_block, + no_buffer, + network_down, + connection_refused, + connection_reset, + connection_aborted, + connection_closed, + interrupted, + invalid_socket, + invalid_argument, } enum SocketType : byte { - Unknown = -1, - Stream = 0, - Datagram, - Raw, + unknown = -1, + stream = 0, + datagram, + raw, } enum Protocol : byte { - Unknown = -1, - TCP = 0, - UDP, - IP, - ICMP, - Raw, + unknown = -1, + tcp = 0, + udp, + ip, + icmp, + raw, } enum SocketShutdownMode : ubyte { - Read, - Write, - ReadWrite + read, + write, + read_write } enum SocketOption : ubyte { // not traditionally a 'socket option', but this is way more convenient - NonBlocking, + non_blocking, // Socket options - KeepAlive, - Linger, - RandomizePort, - SendBufferLength, - RecvBufferLength, - ReuseAddress, - NoSigPipe, - Error, + keep_alive, + linger, + randomize_port, + send_buffer_length, + recv_buffer_length, + reuse_address, + no_sig_pPipe, + error, // IP options - FirstIpOption, - Multicast = FirstIpOption, - MulticastLoopback, - MulticastTTL, + first_ip_option, + multicast = first_ip_option, + multicast_loopback, + multicast_ttl, // IPv6 options - FirstIpv6Option, + first_ipv6_option, // ICMP options - FirstIcmpOption = FirstIpv6Option, + first_icmp_option = first_ipv6_option, // ICMPv6 options - FirstIcmpv6Option = FirstIcmpOption, + first_icmpv6_option = first_icmp_option, // TCP options - FirstTcpOption = FirstIcmpv6Option, - TCP_KeepIdle = FirstTcpOption, - TCP_KeepIntvl, - TCP_KeepCnt, - TCP_KeepAlive, // Apple: similar to KeepIdle - TCP_NoDelay, + first_tcp_option = first_icmpv6_option, + tcp_keep_idle = first_tcp_option, + tcp_keep_intvl, + tcp_keep_cnt, + tcp_keep_alive, // Apple: similar to KeepIdle + tcp_no_delay, // UDP options - FirstUdpOption, + first_udp_option, } enum MsgFlags : ubyte { - None = 0, - OOB = 1 << 0, - Peek = 1 << 1, - Confirm = 1 << 2, - NoSig = 1 << 3, + none = 0, + oob = 1 << 0, + peek = 1 << 1, + confirm = 1 << 2, + no_sig = 1 << 3, //... } enum AddressInfoFlags : ubyte { - None = 0, - Passive = 1 << 0, - CanonName = 1 << 1, - NumericHost = 1 << 2, - NumericServ = 1 << 3, - All = 1 << 4, - AddrConfig = 1 << 5, - V4Mapped = 1 << 6, - FQDN = 1 << 7, + none = 0, + passive = 1 << 0, + canon_name = 1 << 1, + numeric_host = 1 << 2, + numeric_serv = 1 << 3, + all = 1 << 4, + addr_config = 1 << 5, + v4_mapped = 1 << 6, + fqdn = 1 << 7, } enum PollEvents : ubyte { - None = 0, - Read = 1 << 0, - Write = 1 << 1, - Error = 1 << 2, - HangUp = 1 << 3, - Invalid = 1 << 4, + none = 0, + read = 1 << 0, + write = 1 << 1, + error = 1 << 2, + hangup = 1 << 3, + invalid = 1 << 4, } @@ -195,7 +195,7 @@ Result create_socket(AddressFamily af, SocketType type, Protocol proto, out Sock socket.handle = .socket(s_addressFamily[af], s_socketType[type], s_protocol[proto]); if (socket == Socket.invalid) return socket_getlasterror(); - return Result.Success; + return Result.success; } Result close(Socket socket) @@ -214,7 +214,7 @@ Result close(Socket socket) // s_noSignal.Erase(socket); // } - return Result.Success; + return Result.success; } Result shutdown(Socket socket, SocketShutdownMode how) @@ -224,15 +224,15 @@ Result shutdown(Socket socket, SocketShutdownMode how) { version (Windows) { - case SocketShutdownMode.Read: t = SD_RECEIVE; break; - case SocketShutdownMode.Write: t = SD_SEND; break; - case SocketShutdownMode.ReadWrite: t = SD_BOTH; break; + case SocketShutdownMode.read: t = SD_RECEIVE; break; + case SocketShutdownMode.write: t = SD_SEND; break; + case SocketShutdownMode.read_write: t = SD_BOTH; break; } else version (Posix) { - case SocketShutdownMode.Read: t = SHUT_RD; break; - case SocketShutdownMode.Write: t = SHUT_WR; break; - case SocketShutdownMode.ReadWrite: t = SHUT_RDWR; break; + case SocketShutdownMode.read: t = SHUT_RD; break; + case SocketShutdownMode.write: t = SHUT_WR; break; + case SocketShutdownMode.read_write: t = SHUT_RDWR; break; } default: assert(false, "Invalid `how`"); @@ -240,7 +240,7 @@ Result shutdown(Socket socket, SocketShutdownMode how) if (_shutdown(socket.handle, t) < 0) return socket_getlasterror(); - return Result.Success; + return Result.success; } Result bind(Socket socket, ref const InetAddress address) @@ -252,14 +252,14 @@ Result bind(Socket socket, ref const InetAddress address) if (_bind(socket.handle, sockAddr, cast(int)addrLen) < 0) return socket_getlasterror(); - return Result.Success; + return Result.success; } Result listen(Socket socket, uint backlog = -1) { if (_listen(socket.handle, int(backlog & 0x7FFFFFFF)) < 0) return socket_getlasterror(); - return Result.Success; + return Result.success; } Result connect(Socket socket, ref const InetAddress address) @@ -271,7 +271,7 @@ Result connect(Socket socket, ref const InetAddress address) if (_connect(socket.handle, sockAddr, cast(int)addrLen) < 0) return socket_getlasterror(); - return Result.Success; + return Result.success; } Result accept(Socket socket, out Socket connection, InetAddress* connectingSocketAddress = null) @@ -285,12 +285,12 @@ Result accept(Socket socket, out Socket connection, InetAddress* connectingSocke return socket_getlasterror(); else if (connectingSocketAddress) *connectingSocketAddress = make_InetAddress(addr); - return Result.Success; + return Result.success; } -Result send(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.None, size_t* bytesSent = null) +Result send(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.none, size_t* bytesSent = null) { - Result r = Result.Success; + Result r = Result.success; ptrdiff_t sent = _send(socket.handle, message.ptr, cast(int)message.length, map_message_flags(flags)); if (sent < 0) @@ -303,7 +303,7 @@ Result send(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.None return r; } -Result sendto(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.None, const InetAddress* address = null, size_t* bytesSent = null) +Result sendto(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.none, const InetAddress* address = null, size_t* bytesSent = null) { ubyte[sockaddr_storage.sizeof] tmp = void; size_t addrLen; @@ -314,7 +314,7 @@ Result sendto(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.No assert(sockAddr, "Invalid socket address"); } - Result r = Result.Success; + Result r = Result.success; ptrdiff_t sent = _sendto(socket.handle, message.ptr, cast(int)message.length, map_message_flags(flags), sockAddr, cast(int)addrLen); if (sent < 0) { @@ -326,9 +326,9 @@ Result sendto(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.No return r; } -Result recv(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.None, size_t* bytesReceived) +Result recv(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.none, size_t* bytesReceived) { - Result r = Result.Success; + Result r = Result.success; ptrdiff_t bytes = _recv(socket.handle, buffer.ptr, cast(int)buffer.length, map_message_flags(flags)); if (bytes > 0) *bytesReceived = bytes; @@ -351,21 +351,21 @@ Result recv(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.None, size_t Result error = socket_getlasterror(); // TODO: Do we want a better way to distinguish between receiving a 0-length packet vs no-data (which looks like an error)? // Is a zero-length packet possible to detect in TCP streams? Makes more sense for recvfrom... - SocketResult sr = get_SocketResult(error); - if (sr != SocketResult.WouldBlock) + SocketResult sr = socket_result(error); + if (sr != SocketResult.would_block) r = error; } } return r; } -Result recvfrom(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.None, InetAddress* senderAddress = null, size_t* bytesReceived) +Result recvfrom(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.none, InetAddress* senderAddress = null, size_t* bytesReceived) { char[sockaddr_storage.sizeof] addrBuffer = void; sockaddr* addr = cast(sockaddr*)addrBuffer.ptr; socklen_t size = addrBuffer.sizeof; - Result r = Result.Success; + Result r = Result.success; ptrdiff_t bytes = _recvfrom(socket.handle, buffer.ptr, cast(int)buffer.length, map_message_flags(flags), addr, &size); if (bytes >= 0) *bytesReceived = bytes; @@ -374,10 +374,10 @@ Result recvfrom(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.None, In *bytesReceived = 0; Result error = socket_getlasterror(); - SocketResult sockRes = get_SocketResult(error); - if (sockRes != SocketResult.NoBuffer && // buffers full - sockRes != SocketResult.ConnectionRefused && // posix error - sockRes != SocketResult.ConnectionReset) // !!! windows may report this error, but it appears to mean something different on posix + SocketResult sockRes = socket_result(error); + if (sockRes != SocketResult.no_buffer && // buffers full + sockRes != SocketResult.connection_refused && // posix error + sockRes != SocketResult.connection_reset) // !!! windows may report this error, but it appears to mean something different on posix r = error; } if (r && senderAddress) @@ -387,16 +387,16 @@ Result recvfrom(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.None, In Result set_socket_option(Socket socket, SocketOption option, const(void)* optval, size_t optlen) { - Result r = Result.Success; + Result r = Result.success; // check the option appears to be the proper datatype const OptInfo* optInfo = &s_socketOptions[option]; - assert(optInfo.rtType != OptType.Unsupported, "Socket option is unsupported on this platform!"); - assert(optlen == s_optTypeRtSize[optInfo.rtType], "Socket option has incorrect payload size!"); + assert(optInfo.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); + assert(optlen == s_optTypeRtSize[optInfo.rt_type], "Socket option has incorrect payload size!"); // special case for non-blocking // this is not strictly a 'socket option', but this rather simplifies our API - if (option == SocketOption.NonBlocking) + if (option == SocketOption.non_blocking) { bool value = *cast(const(bool)*)optval; version (Windows) @@ -420,7 +420,7 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval // LockGuard!SharedMutex lock(s_noSignalMut); // s_noSignal.InsertOrAssign(socket.handle, *cast(const(bool)*)optval); // -// if (optInfo.platformType == OptType.Unsupported) +// if (optInfo.platform_type == OptType.unsupported) // return r; // } @@ -433,17 +433,17 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval const(void)* arg = optval; int itmp = void; linger ling = void; - if (optInfo.rtType != optInfo.platformType) + if (optInfo.rt_type != optInfo.platform_type) { - switch (optInfo.rtType) + switch (optInfo.rt_type) { // TODO: there are more converstions necessary as options/platforms are added - case OptType.Bool: + case OptType.bool_: { const bool value = *cast(const(bool)*)optval; - switch (optInfo.platformType) + switch (optInfo.platform_type) { - case OptType.Int: + case OptType.int_: itmp = value ? 1 : 0; arg = &itmp; break; @@ -451,20 +451,20 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval } break; } - case OptType.Duration: + case OptType.duration: { const Duration value = *cast(const(Duration)*)optval; - switch (optInfo.platformType) + switch (optInfo.platform_type) { - case OptType.Seconds: + case OptType.seconds: itmp = cast(int)value.as!"seconds"; arg = &itmp; break; - case OptType.Milliseconds: + case OptType.milliseconds: itmp = cast(int)value.as!"msecs"; arg = &itmp; break; - case OptType.Linger: + case OptType.linger: itmp = cast(int)value.as!"seconds"; ling = linger(!!itmp, cast(ushort)itmp); arg = &ling; @@ -479,7 +479,7 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval } // set the option - r.systemCode = setsockopt(socket.handle, s_sockOptLevel[level], optInfo.option, cast(const(char)*)arg, s_optTypePlatformSize[optInfo.platformType]); + r.systemCode = setsockopt(socket.handle, s_sockOptLevel[level], optInfo.option, cast(const(char)*)arg, s_optTypePlatformSize[optInfo.platform_type]); return r; } @@ -487,80 +487,80 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval Result set_socket_option(Socket socket, SocketOption option, bool value) { const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Bool, "Incorrect value type for option"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.rt_type == OptType.bool_, "Incorrect value type for option"); return set_socket_option(socket, option, &value, bool.sizeof); } Result set_socket_option(Socket socket, SocketOption option, int value) { const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Int, "Incorrect value type for option"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.rt_type == OptType.int_, "Incorrect value type for option"); return set_socket_option(socket, option, &value, int.sizeof); } Result set_socket_option(Socket socket, SocketOption option, Duration value) { const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Duration, "Incorrect value type for option"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.rt_type == OptType.duration, "Incorrect value type for option"); return set_socket_option(socket, option, &value, Duration.sizeof); } Result set_socket_option(Socket socket, SocketOption option, IPAddr value) { const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.INAddress, "Incorrect value type for option"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.rt_type == OptType.inet_addr, "Incorrect value type for option"); return set_socket_option(socket, option, &value, IPAddr.sizeof); } Result set_socket_option(Socket socket, SocketOption option, ref MulticastGroup value) { const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.MulticastGroup, "Incorrect value type for option"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.rt_type == OptType.multicast_group, "Incorrect value type for option"); return set_socket_option(socket, option, &value, MulticastGroup.sizeof); } Result get_socket_option(Socket socket, SocketOption option, void* output, size_t outputlen) { - Result r = Result.Success; + Result r = Result.success; // check the option appears to be the proper datatype const OptInfo* optInfo = &s_socketOptions[option]; - assert(optInfo.rtType != OptType.Unsupported, "Socket option is unsupported on this platform!"); - assert(outputlen == s_optTypeRtSize[optInfo.rtType], "Socket option has incorrect payload size!"); + assert(optInfo.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); + assert(outputlen == s_optTypeRtSize[optInfo.rt_type], "Socket option has incorrect payload size!"); - assert(option != SocketOption.NonBlocking, "Socket option NonBlocking cannot be get"); + assert(option != SocketOption.non_blocking, "Socket option NonBlocking cannot be get"); // determine the option 'level' OptLevel level = get_optlevel(option); version (HasIPv6) - assert(level != OptLevel.IPv6 && level != OptLevel.ICMPv6, "Platform does not support IPv6!"); + assert(level != OptLevel.ipv6 && level != OptLevel.icmpv6, "Platform does not support IPv6!"); // platforms don't all agree on option data formats! void* arg = output; int itmp = 0; linger ling = { 0, 0 }; - if (optInfo.rtType != optInfo.platformType) + if (optInfo.rt_type != optInfo.platform_type) { - switch (optInfo.platformType) + switch (optInfo.platform_type) { - case OptType.Int: - case OptType.Seconds: - case OptType.Milliseconds: + case OptType.int_: + case OptType.seconds: + case OptType.milliseconds: { arg = &itmp; break; } - case OptType.Linger: + case OptType.linger: { arg = &ling; break; @@ -570,39 +570,39 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ } } - socklen_t writtenLen = s_optTypePlatformSize[optInfo.platformType]; + socklen_t writtenLen = s_optTypePlatformSize[optInfo.platform_type]; // get the option r.systemCode = getsockopt(socket.handle, s_sockOptLevel[level], optInfo.option, cast(char*)arg, &writtenLen); - if (optInfo.rtType != optInfo.platformType) + if (optInfo.rt_type != optInfo.platform_type) { - switch (optInfo.rtType) + switch (optInfo.rt_type) { // TODO: there are more converstions necessary as options/platforms are added - case OptType.Bool: + case OptType.bool_: { bool* value = cast(bool*)output; - switch (optInfo.platformType) + switch (optInfo.platform_type) { - case OptType.Int: + case OptType.int_: *value = !!itmp; break; default: assert(false, "Unexpected"); } break; } - case OptType.Duration: + case OptType.duration: { Duration* value = cast(Duration*)output; - switch (optInfo.platformType) + switch (optInfo.platform_type) { - case OptType.Seconds: + case OptType.seconds: *value = seconds(itmp); break; - case OptType.Milliseconds: + case OptType.milliseconds: *value = msecs(itmp); break; - case OptType.Linger: + case OptType.linger: *value = seconds(ling.l_linger); break; default: assert(false, "Unexpected"); @@ -614,10 +614,10 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ } } - assert(optInfo.rtType != OptType.INAddress, "TODO: uncomment this block... for some reason, this block causes DMD to do a bad codegen!"); + assert(optInfo.rt_type != OptType.inet_addr, "TODO: uncomment this block... for some reason, this block causes DMD to do a bad codegen!"); /+ // Options expected in network-byte order - switch (optInfo.rtType) + switch (optInfo.rt_type) { case OptType.INAddress: { @@ -635,36 +635,36 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ Result get_socket_option(Socket socket, SocketOption option, out bool output) { const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Bool, "Incorrect value type for option"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.rt_type == OptType.bool_, "Incorrect value type for option"); return get_socket_option(socket, option, &output, bool.sizeof); } Result get_socket_option(Socket socket, SocketOption option, out int output) { const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Int, "Incorrect value type for option"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.rt_type == OptType.int_, "Incorrect value type for option"); return get_socket_option(socket, option, &output, int.sizeof); } Result get_socket_option(Socket socket, SocketOption option, out Duration output) { const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Duration, "Incorrect value type for option"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.rt_type == OptType.duration, "Incorrect value type for option"); return get_socket_option(socket, option, &output, Duration.sizeof); } Result get_socket_option(Socket socket, SocketOption option, out IPAddr output) { const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.INAddress, "Incorrect value type for option"); + if (optInfo.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(optInfo.rt_type == OptType.inet_addr, "Incorrect value type for option"); return get_socket_option(socket, option, &output, IPAddr.sizeof); } @@ -680,27 +680,27 @@ Result set_keepalive(Socket socket, bool enable, Duration keepIdle, Duration kee uint bytesReturned = 0; if (WSAIoctl(socket.handle, SIO_KEEPALIVE_VALS, &alive, alive.sizeof, null, 0, &bytesReturned, null, null) < 0) return socket_getlasterror(); - return Result.Success; + return Result.success; } else { - Result res = set_socket_option(socket, SocketOption.KeepAlive, enable); - if (!enable || res != Result.Success) + Result res = set_socket_option(socket, SocketOption.keep_alive, enable); + if (!enable || res != Result.success) return res; version (Darwin) { // OSX doesn't support setting keep-alive interval and probe count. - return set_socket_option(socket, SocketOption.TCP_KeepAlive, keepIdle); + return set_socket_option(socket, SocketOption.tcp_keep_alive, keepIdle); } else { - res = set_socket_option(socket, SocketOption.TCP_KeepIdle, keepIdle); - if (res != Result.Success) + res = set_socket_option(socket, SocketOption.tcp_keep_idle, keepIdle); + if (res != Result.success) return res; - res = set_socket_option(socket, SocketOption.TCP_KeepIntvl, keepInterval); - if (res != Result.Success) + res = set_socket_option(socket, SocketOption.tcp_keep_intvl, keepInterval); + if (res != Result.success) return res; - return set_socket_option(socket, SocketOption.TCP_KeepCnt, keepCount); + return set_socket_option(socket, SocketOption.tcp_keep_cnt, keepCount); } } } @@ -716,7 +716,7 @@ Result get_peer_name(Socket socket, out InetAddress name) name = make_InetAddress(addr); else return socket_getlasterror(); - return Result.Success; + return Result.success; } Result get_socket_name(Socket socket, out InetAddress name) @@ -730,7 +730,7 @@ Result get_socket_name(Socket socket, out InetAddress name) name = make_InetAddress(addr); else return socket_getlasterror(); - return Result.Success; + return Result.success; } Result get_hostname(char* name, size_t len) @@ -738,7 +738,7 @@ Result get_hostname(char* name, size_t len) int fail = gethostname(name, cast(int)len); if (fail) return socket_getlasterror(); - return Result.Success; + return Result.success; } Result get_address_info(const(char)[] nodeName, const(char)[] service, AddressInfo* hints, out AddressInfoResolver result) @@ -760,9 +760,9 @@ Result get_address_info(const(char)[] nodeName, const(char)[] service, AddressIn // translate hints... tmpHints.ai_flags = map_addrinfo_flags(hints.flags); tmpHints.ai_family = s_addressFamily[hints.family]; - tmpHints.ai_socktype = s_socketType[hints.sockType]; + tmpHints.ai_socktype = s_socketType[hints.sock_type]; tmpHints.ai_protocol = s_protocol[hints.protocol]; - tmpHints.ai_canonname = cast(char*)hints.canonName; // HAX! + tmpHints.ai_canonname = cast(char*)hints.canon_name; // HAX! tmpHints.ai_addrlen = 0; tmpHints.ai_addr = null; tmpHints.ai_next = null; @@ -780,7 +780,7 @@ Result get_address_info(const(char)[] nodeName, const(char)[] service, AddressIn result.m_internal[0] = res; result.m_internal[1] = res; - return Result.Success; + return Result.success; } Result poll(PollFd[] pollFds, Duration timeout, out uint numEvents) @@ -795,8 +795,8 @@ Result poll(PollFd[] pollFds, Duration timeout, out uint numEvents) { fds[i].fd = pollFds[i].socket.handle; fds[i].revents = 0; - fds[i].events = ((pollFds[i].requestEvents & PollEvents.Read) ? POLLRDNORM : 0) | - ((pollFds[i].requestEvents & PollEvents.Write) ? POLLWRNORM : 0); + fds[i].events = ((pollFds[i].request_events & PollEvents.read) ? POLLRDNORM : 0) | + ((pollFds[i].request_events & PollEvents.write) ? POLLWRNORM : 0); } version (Windows) int r = WSAPoll(fds.ptr, cast(uint)pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); @@ -812,14 +812,14 @@ Result poll(PollFd[] pollFds, Duration timeout, out uint numEvents) numEvents = r; for (size_t i = 0; i < pollFds.length; ++i) { - pollFds[i].returnEvents = cast(PollEvents)( - ((fds[i].revents & POLLRDNORM) ? PollEvents.Read : 0) | - ((fds[i].revents & POLLWRNORM) ? PollEvents.Write : 0) | - ((fds[i].revents & POLLERR) ? PollEvents.Error : 0) | - ((fds[i].revents & POLLHUP) ? PollEvents.HangUp : 0) | - ((fds[i].revents & POLLNVAL) ? PollEvents.Invalid : 0)); + pollFds[i].return_events = cast(PollEvents)( + ((fds[i].revents & POLLRDNORM) ? PollEvents.read : 0) | + ((fds[i].revents & POLLWRNORM) ? PollEvents.write : 0) | + ((fds[i].revents & POLLERR) ? PollEvents.error : 0) | + ((fds[i].revents & POLLHUP) ? PollEvents.hangup : 0) | + ((fds[i].revents & POLLNVAL) ? PollEvents.invalid : 0)); } - return Result.Success; + return Result.success; } Result poll(ref PollFd pollFd, Duration timeout, out uint numEvents) @@ -831,9 +831,9 @@ struct AddressInfo { AddressInfoFlags flags; AddressFamily family; - SocketType sockType; + SocketType sock_type; Protocol protocol; - const(char)* canonName; // Note: this memory is valid until the next call to `next_address`, or until `AddressInfoResolver` is destroyed + const(char)* canon_name; // Note: this memory is valid until the next call to `next_address`, or until `AddressInfoResolver` is destroyed InetAddress address; } @@ -878,11 +878,11 @@ nothrow @nogc: addrinfo* info = cast(addrinfo*)(m_internal[1]); m_internal[1] = info.ai_next; - addressInfo.flags = AddressInfoFlags.None; // info.ai_flags is only used for 'hints' + addressInfo.flags = AddressInfoFlags.none; // info.ai_flags is only used for 'hints' addressInfo.family = map_address_family(info.ai_family); - addressInfo.sockType = cast(int)info.ai_socktype ? map_socket_type(info.ai_socktype) : SocketType.Unknown; + addressInfo.sock_type = cast(int)info.ai_socktype ? map_socket_type(info.ai_socktype) : SocketType.unknown; addressInfo.protocol = map_protocol(info.ai_protocol); - addressInfo.canonName = info.ai_canonname; + addressInfo.canon_name = info.ai_canonname; addressInfo.address = make_InetAddress(info.ai_addr); return true; } @@ -893,9 +893,9 @@ nothrow @nogc: struct PollFd { Socket socket; - PollEvents requestEvents; - PollEvents returnEvents; - void* userData; + PollEvents request_events; + PollEvents return_events; + void* user_data; } @@ -920,56 +920,56 @@ Result get_socket_error(Socket socket) // TODO: !!! enum Result ConnectionClosedResult = Result(-12345); -SocketResult get_SocketResult(Result result) +SocketResult socket_result(Result result) { if (result) - return SocketResult.Success; + return SocketResult.success; if (result.systemCode == ConnectionClosedResult.systemCode) - return SocketResult.ConnectionClosed; + return SocketResult.connection_closed; version (Windows) { if (result.systemCode == WSAEWOULDBLOCK) - return SocketResult.WouldBlock; + return SocketResult.would_block; if (result.systemCode == WSAEINPROGRESS) - return SocketResult.WouldBlock; + return SocketResult.would_block; if (result.systemCode == WSAENOBUFS) - return SocketResult.NoBuffer; + return SocketResult.no_buffer; if (result.systemCode == WSAENETDOWN) - return SocketResult.NetworkDown; + return SocketResult.network_down; if (result.systemCode == WSAECONNREFUSED) - return SocketResult.ConnectionRefused; + return SocketResult.connection_refused; if (result.systemCode == WSAECONNRESET) - return SocketResult.ConnectionReset; + return SocketResult.connection_reset; if (result.systemCode == WSAEINTR) - return SocketResult.Interrupted; + return SocketResult.interrupted; if (result.systemCode == WSAENOTSOCK) - return SocketResult.InvalidSocket; + return SocketResult.invalid_socket; if (result.systemCode == WSAEINVAL) - return SocketResult.InvalidArgument; + return SocketResult.invalid_argument; } else version (Posix) { static if (EAGAIN != EWOULDBLOCK) if (result.systemCode == EAGAIN) - return SocketResult.WouldBlock; + return SocketResult.would_block; if (result.systemCode == EWOULDBLOCK) - return SocketResult.WouldBlock; + return SocketResult.would_block; if (result.systemCode == EINPROGRESS) - return SocketResult.WouldBlock; + return SocketResult.would_block; if (result.systemCode == ENOMEM) - return SocketResult.NoBuffer; + return SocketResult.no_buffer; if (result.systemCode == ENETDOWN) - return SocketResult.NetworkDown; + return SocketResult.network_down; if (result.systemCode == ECONNREFUSED) - return SocketResult.ConnectionRefused; + return SocketResult.connection_refused; if (result.systemCode == ECONNRESET) - return SocketResult.ConnectionReset; + return SocketResult.connection_reset; if (result.systemCode == EINTR) - return SocketResult.Interrupted; + return SocketResult.interrupted; if (result.systemCode == EINVAL) - return SocketResult.InvalidArgument; + return SocketResult.invalid_argument; } - return SocketResult.Failure; + return SocketResult.failure; } @@ -1136,28 +1136,28 @@ private: enum OptLevel : ubyte { - Socket, - IP, - IPv6, - ICMP, - ICMPv6, - TCP, - UDP, + socket, + ip, + ipv6, + icmp, + icmpv6, + tcp, + udp, } enum OptType : ubyte { - Unsupported, - Bool, - Int, - Seconds, - Milliseconds, - Duration, - INAddress, // IPAddr + in_addr - //IN6Address, // IPv6Addr + in6_addr - MulticastGroup, // MulticastGroup + ip_mreq - //MulticastGroupIPv6, // MulticastGroupIPv6? + ipv6_mreq - Linger, + unsupported, + bool_, + int_, + seconds, + milliseconds, + duration, + inet_addr, // IPAddr + in_addr + //inet6_addr, // IPv6Addr + in6_addr + multicast_group, // MulticastGroup + ip_mreq + //multicast_group_ipv6, // MulticastGroupIPv6? + ipv6_mreq + linger, // etc... } @@ -1169,8 +1169,8 @@ __gshared immutable ubyte[] s_optTypePlatformSize = [ 0, 0, int.sizeof, int.size struct OptInfo { short option; - OptType rtType; - OptType platformType; + OptType rt_type; + OptType platform_type; } __gshared immutable ushort[AddressFamily.max+1] s_addressFamily = [ @@ -1201,13 +1201,13 @@ __gshared immutable int[SocketType.max+1] s_socketType = [ SocketType map_socket_type(int sockType) { if (sockType == SOCK_STREAM) - return SocketType.Stream; + return SocketType.stream; else if (sockType == SOCK_DGRAM) - return SocketType.Datagram; + return SocketType.datagram; else if (sockType == SOCK_RAW) - return SocketType.Raw; + return SocketType.raw; assert(false, "Unsupported socket type"); - return SocketType.Unknown; + return SocketType.unknown; } __gshared immutable int[Protocol.max+1] s_protocol = [ @@ -1220,17 +1220,17 @@ __gshared immutable int[Protocol.max+1] s_protocol = [ Protocol map_protocol(int protocol) { if (protocol == IPPROTO_TCP) - return Protocol.TCP; + return Protocol.tcp; else if (protocol == IPPROTO_UDP) - return Protocol.UDP; + return Protocol.udp; else if (protocol == IPPROTO_IP) - return Protocol.IP; + return Protocol.ip; else if (protocol == IPPROTO_ICMP) - return Protocol.ICMP; + return Protocol.icmp; else if (protocol == IPPROTO_RAW) - return Protocol.Raw; + return Protocol.raw; assert(false, "Unsupported protocol"); - return Protocol.Unknown; + return Protocol.unknown; } version (linux) @@ -1261,68 +1261,68 @@ else version (Windows) // BS_NETWORK_WINDOWS_VERSION >= _WIN32_WINNT_VISTA { __gshared immutable OptInfo[SocketOption.max] s_socketOptions = [ - OptInfo( -1, OptType.Bool, OptType.Bool ), // NonBlocking - OptInfo( SO_KEEPALIVE, OptType.Bool, OptType.Int ), - OptInfo( SO_LINGER, OptType.Duration, OptType.Linger ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), -// OptInfo( SO_RANDOMIZE_PORT, OptType.Bool, OptType.Int ), // TODO: BS_NETWORK_WINDOWS_VERSION >= _WIN32_WINNT_VISTA - OptInfo( SO_SNDBUF, OptType.Int, OptType.Int ), - OptInfo( SO_RCVBUF, OptType.Int, OptType.Int ), - OptInfo( SO_REUSEADDR, OptType.Bool, OptType.Int ), - OptInfo( -1, OptType.Bool, OptType.Unsupported ), // NoSignalPipe - OptInfo( SO_ERROR, OptType.Int, OptType.Int ), - OptInfo( IP_ADD_MEMBERSHIP, OptType.MulticastGroup, OptType.MulticastGroup ), - OptInfo( IP_MULTICAST_LOOP, OptType.Bool, OptType.Int ), - OptInfo( IP_MULTICAST_TTL, OptType.Int, OptType.Int ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( TCP_NODELAY, OptType.Bool, OptType.Int ), + OptInfo( -1, OptType.bool_, OptType.bool_ ), // NonBlocking + OptInfo( SO_KEEPALIVE, OptType.bool_, OptType.int_ ), + OptInfo( SO_LINGER, OptType.duration, OptType.linger ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), +// OptInfo( SO_RANDOMIZE_PORT, OptType.bool_, OptType.int_ ), // TODO: BS_NETWORK_WINDOWS_VERSION >= _WIN32_WINNT_VISTA + OptInfo( SO_SNDBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_RCVBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_REUSEADDR, OptType.bool_, OptType.int_ ), + OptInfo( -1, OptType.bool_, OptType.unsupported ), // NoSignalPipe + OptInfo( SO_ERROR, OptType.int_, OptType.int_ ), + OptInfo( IP_ADD_MEMBERSHIP, OptType.multicast_group, OptType.multicast_group ), + OptInfo( IP_MULTICAST_LOOP, OptType.bool_, OptType.int_ ), + OptInfo( IP_MULTICAST_TTL, OptType.int_, OptType.int_ ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( TCP_NODELAY, OptType.bool_, OptType.int_ ), ]; } else version (linux) // BS_NETWORK_WINDOWS_VERSION >= _WIN32_WINNT_VISTA { __gshared immutable OptInfo[SocketOption.max] s_socketOptions = [ - OptInfo( -1, OptType.Bool, OptType.Bool ), // NonBlocking - OptInfo( SO_KEEPALIVE, OptType.Bool, OptType.Int ), - OptInfo( SO_LINGER, OptType.Duration, OptType.Linger ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( SO_SNDBUF, OptType.Int, OptType.Int ), - OptInfo( SO_RCVBUF, OptType.Int, OptType.Int ), - OptInfo( SO_REUSEADDR, OptType.Bool, OptType.Int ), - OptInfo( -1, OptType.Bool, OptType.Unsupported ), // NoSignalPipe - OptInfo( SO_ERROR, OptType.Int, OptType.Int ), - OptInfo( IP_ADD_MEMBERSHIP, OptType.MulticastGroup, OptType.MulticastGroup ), - OptInfo( IP_MULTICAST_LOOP, OptType.Bool, OptType.Int ), - OptInfo( IP_MULTICAST_TTL, OptType.Int, OptType.Int ), - OptInfo( TCP_KEEPIDLE, OptType.Duration, OptType.Seconds ), - OptInfo( TCP_KEEPINTVL, OptType.Duration, OptType.Seconds ), - OptInfo( TCP_KEEPCNT, OptType.Int, OptType.Int ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( TCP_NODELAY, OptType.Bool, OptType.Int ), + OptInfo( -1, OptType.bool_, OptType.bool_ ), // NonBlocking + OptInfo( SO_KEEPALIVE, OptType.bool_, OptType.int_ ), + OptInfo( SO_LINGER, OptType.duration, OptType.linger ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( SO_SNDBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_RCVBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_REUSEADDR, OptType.bool_, OptType.int_ ), + OptInfo( -1, OptType.bool_, OptType.unsupported ), // NoSignalPipe + OptInfo( SO_ERROR, OptType.int_, OptType.int_ ), + OptInfo( IP_ADD_MEMBERSHIP, OptType.multicast_group, OptType.multicast_group ), + OptInfo( IP_MULTICAST_LOOP, OptType.bool_, OptType.int_ ), + OptInfo( IP_MULTICAST_TTL, OptType.int_, OptType.int_ ), + OptInfo( TCP_KEEPIDLE, OptType.duration, OptType.seconds ), + OptInfo( TCP_KEEPINTVL, OptType.duration, OptType.seconds ), + OptInfo( TCP_KEEPCNT, OptType.int_, OptType.int_ ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( TCP_NODELAY, OptType.bool_, OptType.int_ ), ]; } else version (Darwin) { __gshared immutable OptInfo[SocketOption.max] s_socketOptions = [ - OptInfo( -1, OptType.Bool, OptType.Bool ), // NonBlocking - OptInfo( SO_KEEPALIVE, OptType.Bool, OptType.Int ), + OptInfo( -1, OptType.bool_, OptType.bool_ ), // NonBlocking + OptInfo( SO_KEEPALIVE, OptType.bool_, OptType.int_ ), OptInfo( SO_LINGER, OptType.Duration, OptType.Linger ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( SO_SNDBUF, OptType.Int, OptType.Int ), - OptInfo( SO_RCVBUF, OptType.Int, OptType.Int ), - OptInfo( SO_REUSEADDR, OptType.Bool, OptType.Int ), - OptInfo( SO_NOSIGPIPE, OptType.Bool, OptType.Int ), - OptInfo( SO_ERROR, OptType.Int, OptType.Int ), - OptInfo( IP_ADD_MEMBERSHIP, OptType.MulticastGroup, OptType.MulticastGroup ), - OptInfo( IP_MULTICAST_LOOP, OptType.Bool, OptType.Int ), - OptInfo( IP_MULTICAST_TTL, OptType.Int, OptType.Int ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( TCP_KEEPALIVE, OptType.Duration, OptType.Seconds ), - OptInfo( TCP_NODELAY, OptType.Bool, OptType.Int ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( SO_SNDBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_RCVBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_REUSEADDR, OptType.bool_, OptType.int_ ), + OptInfo( SO_NOSIGPIPE, OptType.bool_, OptType.int_ ), + OptInfo( SO_ERROR, OptType.int_, OptType.int_ ), + OptInfo( IP_ADD_MEMBERSHIP, OptType.multicast_group, OptType.multicast_group ), + OptInfo( IP_MULTICAST_LOOP, OptType.bool_, OptType.int_ ), + OptInfo( IP_MULTICAST_TTL, OptType.int_, OptType.int_ ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( TCP_KEEPALIVE, OptType.duration, OptType.seconds ), + OptInfo( TCP_NODELAY, OptType.bool_, OptType.int_ ), ]; } else @@ -1331,12 +1331,12 @@ else int map_message_flags(MsgFlags flags) { int r = 0; - if (flags & MsgFlags.OOB) r |= MSG_OOB; - if (flags & MsgFlags.Peek) r |= MSG_PEEK; + if (flags & MsgFlags.oob) r |= MSG_OOB; + if (flags & MsgFlags.peek) r |= MSG_PEEK; version (linux) { - if (flags & MsgFlags.Confirm) r |= MSG_CONFIRM; - if (flags & MsgFlags.NoSig) r |= MSG_NOSIGNAL; + if (flags & MsgFlags.confirm) r |= MSG_CONFIRM; + if (flags & MsgFlags.no_sig) r |= MSG_NOSIGNAL; } return r; } @@ -1344,27 +1344,27 @@ int map_message_flags(MsgFlags flags) int map_addrinfo_flags(AddressInfoFlags flags) { int r = 0; - if (flags & AddressInfoFlags.Passive) r |= AI_PASSIVE; - if (flags & AddressInfoFlags.CanonName) r |= AI_CANONNAME; - if (flags & AddressInfoFlags.NumericHost) r |= AI_NUMERICHOST; - if (flags & AddressInfoFlags.NumericServ) r |= AI_NUMERICSERV; - if (flags & AddressInfoFlags.All) r |= AI_ALL; - if (flags & AddressInfoFlags.AddrConfig) r |= AI_ADDRCONFIG; - if (flags & AddressInfoFlags.V4Mapped) r |= AI_V4MAPPED; + if (flags & AddressInfoFlags.passive) r |= AI_PASSIVE; + if (flags & AddressInfoFlags.canon_name) r |= AI_CANONNAME; + if (flags & AddressInfoFlags.numeric_host) r |= AI_NUMERICHOST; + if (flags & AddressInfoFlags.numeric_serv) r |= AI_NUMERICSERV; + if (flags & AddressInfoFlags.all) r |= AI_ALL; + if (flags & AddressInfoFlags.addr_config) r |= AI_ADDRCONFIG; + if (flags & AddressInfoFlags.v4_mapped) r |= AI_V4MAPPED; version (Windows) - if (flags & AddressInfoFlags.FQDN) r |= AI_FQDN; + if (flags & AddressInfoFlags.fqdn) r |= AI_FQDN; return r; } OptLevel get_optlevel(SocketOption opt) { - if (opt < SocketOption.FirstIpOption) return OptLevel.Socket; - else if (opt < SocketOption.FirstIpv6Option) return OptLevel.IP; - else if (opt < SocketOption.FirstIcmpOption) return OptLevel.IPv6; - else if (opt < SocketOption.FirstIcmpv6Option) return OptLevel.ICMP; - else if (opt < SocketOption.FirstTcpOption) return OptLevel.ICMPv6; - else if (opt < SocketOption.FirstUdpOption) return OptLevel.TCP; - else return OptLevel.UDP; + if (opt < SocketOption.first_ip_option) return OptLevel.socket; + else if (opt < SocketOption.first_ipv6_option) return OptLevel.ip; + else if (opt < SocketOption.first_icmp_option) return OptLevel.ipv6; + else if (opt < SocketOption.first_icmpv6_option) return OptLevel.icmp; + else if (opt < SocketOption.first_tcp_option) return OptLevel.icmpv6; + else if (opt < SocketOption.first_udp_option) return OptLevel.tcp; + else return OptLevel.udp; } diff --git a/src/urt/string/ansi.d b/src/urt/string/ansi.d index eac1150..e17b489 100644 --- a/src/urt/string/ansi.d +++ b/src/urt/string/ansi.d @@ -68,7 +68,7 @@ enum ANSI_RESET = "\x1b[0m"; nothrow @nogc: -size_t parseANSICode(const(char)[] text) +size_t parse_ansi_code(const(char)[] text) { import urt.string.ascii : isNumeric; @@ -84,17 +84,17 @@ size_t parseANSICode(const(char)[] text) return i + 1; } -char[] stripDecoration(char[] text) pure +char[] strip_decoration(char[] text) pure { - return stripDecoration(text, text); + return strip_decoration(text, text); } -char[] stripDecoration(const(char)[] text, char[] buffer) pure +char[] strip_decoration(const(char)[] text, char[] buffer) pure { size_t len = text.length, outLen = 0; char* dst = buffer.ptr; const(char)* src = text.ptr; - bool writeOutput = text.ptr != buffer.ptr; + bool write_output = text.ptr != buffer.ptr; for (size_t i = 0; i < len;) { char c = src[i]; @@ -106,11 +106,11 @@ char[] stripDecoration(const(char)[] text, char[] buffer) pure if (j < len && src[j] == 'm') { i = j + 1; - writeOutput = true; + write_output = true; continue; } } - if (BranchMoreExpensiveThanStore || writeOutput) + if (BranchMoreExpensiveThanStore || write_output) dst[outLen] = c; // skip stores where unnecessary (probably the common case) ++outLen; } diff --git a/src/urt/string/format.d b/src/urt/string/format.d index 6016615..8175c42 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -1,6 +1,6 @@ module urt.string.format; -import urt.conv : parseIntFast; +import urt.conv : parse_int_fast; import urt.string; import urt.traits; import urt.util; @@ -269,24 +269,24 @@ struct DefFormat(T) } else static if (is(T == double) || is(T == float)) { - import urt.conv : formatFloat, formatInt; + import urt.conv : format_float, format_int; char[16] tmp = void; if (format.length && format[0] == '*') { bool success; - size_t arg = format[1..$].parseIntFast(success); + size_t arg = format[1..$].parse_int_fast(success); if (!success || !formatArgs[arg].canInt) return -2; size_t width = formatArgs[arg].getInt; - size_t len = width.formatInt(tmp); + size_t len = width.format_int(tmp); format = tmp[0..len]; } - return formatFloat(value, buffer, format); + return format_float(value, buffer, format); } else static if (is(T == ulong) || is(T == long)) { - import urt.conv : formatInt, formatUint; + import urt.conv : format_int, format_uint; // TODO: what formats are interesting for ints? @@ -318,7 +318,7 @@ struct DefFormat(T) if (format.length && format[0].isNumeric) { bool success; - padding = format.parseIntFast(success); + padding = format.parse_int_fast(success); if (varLen) { if (padding < 0 || !formatArgs[padding].canInt) @@ -344,9 +344,9 @@ struct DefFormat(T) } static if (is(T == long)) - size_t len = formatInt(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', showSign); + size_t len = format_int(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', showSign); else - size_t len = formatUint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); + size_t len = format_uint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); if (toLower && len > 0) { @@ -397,7 +397,7 @@ struct DefFormat(T) if (format.length && format[0].isNumeric) { bool success; - width = format.parseIntFast(success); + width = format.parse_int_fast(success); if (varLen) { if (width < 0 || !formatArgs[width].canInt) @@ -453,12 +453,12 @@ struct DefFormat(T) if (format.length && format[0].isNumeric) { bool success; - grp1 = cast(int)format.parseIntFast(success); + grp1 = cast(int)format.parse_int_fast(success); if (success && format.length > 0 && format[0] == ':' && format.length > 1 && format[1].isNumeric) { format.popFront(); - grp2 = cast(int)format.parseIntFast(success); + grp2 = cast(int)format.parse_int_fast(success); } if (!success) return -2; @@ -744,7 +744,7 @@ ptrdiff_t parseFormat(ref const(char)[] format, ref char[] buffer, const(FormatA // get the arg index bool success; - arg = format.parseIntFast(success); + arg = format.parse_int_fast(success); if (!success) { assert(false, "Invalid format string: Number expected!"); @@ -807,7 +807,7 @@ ptrdiff_t parseFormat(ref const(char)[] format, ref char[] buffer, const(FormatA // } bool success; - ptrdiff_t index = formatSpec.parseIntFast(success); + ptrdiff_t index = formatSpec.parse_int_fast(success); // if (varRef) // { // if (arg < 0 || !args[arg].canInt) diff --git a/src/urt/string/string.d b/src/urt/string/string.d index b1281cd..f2747fa 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -3,7 +3,7 @@ module urt.string.string; import urt.lifetime : forward, move; import urt.mem; import urt.mem.string : CacheString; -import urt.hash : fnv1aHash, fnv1aHash64; +import urt.hash : fnv1a, fnv1a64; import urt.string.tailstring : TailString; public import urt.array : Alloc_T, Alloc, Reserve_T, Reserve, Concat_T, Concat; @@ -240,9 +240,9 @@ nothrow @nogc: if (!ptr) return 0; static if (size_t.sizeof == 4) - return fnv1aHash(cast(ubyte[])ptr[0 .. length]); + return fnv1a(cast(ubyte[])ptr[0 .. length]); else - return fnv1aHash64(cast(ubyte[])ptr[0 .. length]); + return fnv1a64(cast(ubyte[])ptr[0 .. length]); } const(char)[] opIndex() const pure diff --git a/src/urt/string/tailstring.d b/src/urt/string/tailstring.d index 3664342..c9d8751 100644 --- a/src/urt/string/tailstring.d +++ b/src/urt/string/tailstring.d @@ -81,9 +81,9 @@ struct TailString(T) import urt.hash; static if (size_t.sizeof == 4) - return fnv1aHash(cast(ubyte[])toString()); + return fnv1a(cast(ubyte[])toString()); else - return fnv1aHash64(cast(ubyte[])toString()); + return fnv1a64(cast(ubyte[])toString()); } private: diff --git a/src/urt/system.d b/src/urt/system.d index 84f97a1..2bb58ec 100644 --- a/src/urt/system.d +++ b/src/urt/system.d @@ -39,17 +39,17 @@ void sleep(Duration duration) struct SystemInfo { - string osName; + string os_name; string processor; - ulong totalMemory; - ulong availableMemory; + ulong total_memory; + ulong available_memory; Duration uptime; } -SystemInfo getSysInfo() +SystemInfo get_sysinfo() { SystemInfo r; - r.osName = Platform; + r.os_name = Platform; r.processor = ProcessorFamily; version (Windows) { @@ -57,8 +57,8 @@ SystemInfo getSysInfo() mem.dwLength = MEMORYSTATUSEX.sizeof; if (GlobalMemoryStatusEx(&mem)) { - r.totalMemory = mem.ullTotalPhys; - r.availableMemory = mem.ullAvailPhys; + r.total_memory = mem.ullTotalPhys; + r.available_memory = mem.ullAvailPhys; } r.uptime = msecs(GetTickCount64()); } @@ -70,8 +70,8 @@ SystemInfo getSysInfo() if (sysinfo(&info) < 0) assert(false, "sysinfo() failed!"); - r.totalMemory = cast(ulong)info.totalram * info.mem_unit; - r.availableMemory = cast(ulong)info.freeram * info.mem_unit; + r.total_memory = cast(ulong)info.totalram * info.mem_unit; + r.available_memory = cast(ulong)info.freeram * info.mem_unit; r.uptime = seconds(info.uptime); } else version (Posix) @@ -83,13 +83,13 @@ SystemInfo getSysInfo() assert(pages >= 0 && page_size >= 0, "sysconf() failed!"); - r.totalMemory = cast(ulong)pages * page_size; - static assert(false, "TODO: need `availableMemory`"); + r.total_memory = cast(ulong)pages * page_size; + static assert(false, "TODO: need `available_memory`"); } return r; } -void setSystemIdleParams(IdleParams params) +void set_system_idle_params(IdleParams params) { version (Windows) { @@ -112,11 +112,11 @@ void setSystemIdleParams(IdleParams params) unittest { - SystemInfo info = getSysInfo(); + SystemInfo info = get_sysinfo(); assert(info.uptime > Duration.zero); import urt.io; - writelnf("System info: {0} - {1}, mem: {2}kb ({3}kb)", info.osName, info.processor, info.totalMemory / (1024), info.availableMemory / (1024)); + writelnf("System info: {0} - {1}, mem: {2}kb ({3}kb)", info.os_name, info.processor, info.total_memory / (1024), info.available_memory / (1024)); } diff --git a/src/urt/time.d b/src/urt/time.d index 646af39..33a7693 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -279,7 +279,7 @@ pure nothrow @nogc: import urt.string.format : FormatArg; ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const { - import urt.conv : formatInt; + import urt.conv : format_int; size_t offset = 0; uint y = year; @@ -291,37 +291,37 @@ pure nothrow @nogc: buffer[0 .. 3] = "BC "; offset += 3; } - ptrdiff_t len = year.formatInt(buffer[offset..$]); + ptrdiff_t len = year.format_int(buffer[offset..$]); if (len < 0 || len == buffer.length) return -1; offset += len; buffer[offset++] = '-'; - len = month.formatInt(buffer[offset..$]); + len = month.format_int(buffer[offset..$]); if (len < 0 || len == buffer.length) return -1; offset += len; buffer[offset++] = '-'; - len = day.formatInt(buffer[offset..$]); + len = day.format_int(buffer[offset..$]); if (len < 0 || len == buffer.length) return -1; offset += len; buffer[offset++] = ' '; - len = hour.formatInt(buffer[offset..$], 10, 2, '0'); + len = hour.format_int(buffer[offset..$], 10, 2, '0'); if (len < 0 || len == buffer.length) return -1; offset += len; buffer[offset++] = ':'; - len = minute.formatInt(buffer[offset..$], 10, 2, '0'); + len = minute.format_int(buffer[offset..$], 10, 2, '0'); if (len < 0 || len == buffer.length) return -1; offset += len; buffer[offset++] = ':'; - len = second.formatInt(buffer[offset..$], 10, 2, '0'); + len = second.format_int(buffer[offset..$], 10, 2, '0'); if (len < 0 || len == buffer.length) return -1; offset += len; buffer[offset++] = '.'; - len = (ns / 1_000_000).formatInt(buffer[offset..$], 10, 3, '0'); + len = (ns / 1_000_000).format_int(buffer[offset..$], 10, 3, '0'); if (len < 0) return len; return offset + len; @@ -511,14 +511,14 @@ package(urt) void initClock() ptrdiff_t timeToString(long ms, char[] buffer) pure { - import urt.conv : formatInt; + import urt.conv : format_int; long hr = ms / 3_600_000; if (!buffer.ptr) - return hr.formatInt(null, 10, 2, '0') + 10; + return hr.format_int(null, 10, 2, '0') + 10; - ptrdiff_t len = hr.formatInt(buffer, 10, 2, '0'); + ptrdiff_t len = hr.format_int(buffer, 10, 2, '0'); if (len < 0 || buffer.length < len + 10) return -1; diff --git a/src/urt/util.d b/src/urt/util.d index 044f47f..6eb457d 100644 --- a/src/urt/util.d +++ b/src/urt/util.d @@ -98,13 +98,15 @@ T alignUp(T)(T value, size_t alignment) bool isAligned(size_t alignment, T)(T value) if (isSomeInt!T || is(T == U*, U)) { - static assert(IsPowerOf2!alignment, "Alignment must be a power of two!"); + static assert(IsPowerOf2!alignment, "Alignment must be a power of two"); + static assert(T.sizeof <= size_t.sizeof, "TODO"); return (cast(size_t)value & (alignment - 1)) == 0; } bool isAligned(T)(T value, size_t alignment) if (isSomeInt!T || is(T == U*, U)) { + static assert(T.sizeof <= size_t.sizeof, "TODO"); return (cast(size_t)value & (alignment - 1)) == 0; } @@ -442,8 +444,8 @@ ulong byteReverse(ulong v) pragma(inline, true) T byteReverse(T)(T val) if (!isIntegral!T) { - import urt.meta : intForWidth; - alias U = intForWidth!(T.sizeof*8); + import urt.meta : IntForWidth; + alias U = IntForWidth!(T.sizeof*8); U r = byteReverse(*cast(U*)&val); return *cast(T*)&r; } diff --git a/src/urt/variant.d b/src/urt/variant.d index 9f5fe2c..8567b46 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -510,14 +510,14 @@ nothrow @nogc: assert(false, "TODO: implement quantity formatting for JSON"); if (isDouble()) - return asDouble().formatFloat(buffer); + return asDouble().format_float(buffer); // TODO: parse args? //format if (flags & Flags.Uint64Flag) - return asUlong().formatUint(buffer); - return asLong().formatInt(buffer); + return asUlong().format_uint(buffer); + return asLong().format_int(buffer); case Variant.Type.String: const char[] s = asString(); @@ -536,7 +536,7 @@ nothrow @nogc: import urt.format.json; // should we just format this like JSON or something? - return writeJson(this, buffer); + return write_json(this, buffer); case Variant.Type.User: if (flags & Flags.Embedded) @@ -691,12 +691,12 @@ unittest private: -import urt.hash : fnv1aHash; +import urt.hash : fnv1a; static assert(Variant.sizeof == 16); static assert(Variant.Type.max <= Variant.Flags.TypeMask); -enum uint UserTypeId(T) = fnv1aHash(cast(const(ubyte)[])T.stringof); // maybe this isn't a good enough hash? +enum uint UserTypeId(T) = fnv1a(cast(const(ubyte)[])T.stringof); // maybe this isn't a good enough hash? enum uint UserTypeShortId(T) = cast(ushort)UserTypeId!T ^ (UserTypeId!T >> 16); enum bool EmbedUserType(T) = is(T == struct) && T.sizeof <= Variant.embed.sizeof - 2 && T.alignof <= Variant.alignof; enum bool UserTypeReturnByRef(T) = is(T == struct); diff --git a/src/urt/zip.d b/src/urt/zip.d index 4bf2521..62f4fda 100644 --- a/src/urt/zip.d +++ b/src/urt/zip.d @@ -4,21 +4,15 @@ import urt.crc; import urt.endian; import urt.hash; import urt.mem.allocator; +import urt.result; -alias zlib_crc = calculateCRC!(Algorithm.CRC32_ISO_HDLC); +alias zlib_crc = calculate_crc!(Algorithm.CRC32_ISO_HDLC); nothrow @nogc: // this is a port of tinflate (tiny inflate) -enum error_code : int -{ - OK = 0, /**< Success */ - DATA_ERROR = -3, /**< Input error */ - BUF_ERROR = -5 /**< Not enough room for output */ -} - enum gzip_flag : ubyte { FTEXT = 1, @@ -28,91 +22,91 @@ enum gzip_flag : ubyte FCOMMENT = 16 } -error_code zlib_uncompress(const(void)[] source, void[] dest, out size_t destLen) +Result zlib_uncompress(const(void)[] source, void[] dest, out size_t destLen) { const ubyte* src = cast(const ubyte*)source.ptr; uint sourceLen = cast(uint)source.length; if (sourceLen < 6) - return error_code.DATA_ERROR; + return InternalResult.data_error; ubyte cmf = src[0]; ubyte flg = src[1]; // check checksum if ((256 * cmf + flg) % 31) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check method is deflate if ((cmf & 0x0F) != 8) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check window size is valid if ((cmf >> 4) > 7) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check there is no preset dictionary if (flg & 0x20) - return error_code.DATA_ERROR; + return InternalResult.data_error; - if (uncompress((src + 2)[0 .. sourceLen - 6], dest, destLen) != error_code.OK) - return error_code.DATA_ERROR; + if (!uncompress((src + 2)[0 .. sourceLen - 6], dest, destLen)) + return InternalResult.data_error; if (adler32(dest[0 .. destLen]) != loadBigEndian!uint(cast(uint*)&src[sourceLen - 4])) - return error_code.DATA_ERROR; + return InternalResult.data_error; - return error_code.OK; + return InternalResult.success; } -error_code gzip_uncompressed_length(const(void)[] source, out size_t destLen) +Result gzip_uncompressed_length(const(void)[] source, out size_t destLen) { const ubyte* src = cast(const ubyte*)source.ptr; uint sourceLen = cast(uint)source.length; if (sourceLen < 18) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check id bytes if (src[0] != 0x1F || src[1] != 0x8B) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check method is deflate if (src[2] != 8) - return error_code.DATA_ERROR; + return InternalResult.data_error; ubyte flg = src[3]; // check that reserved bits are zero if (flg & 0xE0) - return error_code.DATA_ERROR; + return InternalResult.data_error; // get decompressed length destLen = loadLittleEndian!uint(cast(uint*)(src + sourceLen - 4)); - return error_code.OK; + return InternalResult.success; } -error_code gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen) +Result gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen) { const ubyte* src = cast(const ubyte*)source.ptr; uint sourceLen = cast(uint)source.length; if (sourceLen < 18) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check id bytes if (src[0] != 0x1F || src[1] != 0x8B) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check method is deflate if (src[2] != 8) - return error_code.DATA_ERROR; + return InternalResult.data_error; ubyte flg = src[3]; // check that reserved bits are zero if (flg & 0xE0) - return error_code.DATA_ERROR; + return InternalResult.data_error; // skip base header of 10 bytes const(ubyte)* start = src + 10; @@ -123,7 +117,7 @@ error_code gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen uint xlen = loadLittleEndian!ushort(cast(ushort*)start); if (xlen > sourceLen - 12) - return error_code.DATA_ERROR; + return InternalResult.data_error; start += xlen + 2; } @@ -134,7 +128,7 @@ error_code gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen do { if (start - src >= sourceLen) - return error_code.DATA_ERROR; + return InternalResult.data_error; } while (*start++); } @@ -145,7 +139,7 @@ error_code gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen do { if (start - src >= sourceLen) - return error_code.DATA_ERROR; + return InternalResult.data_error; } while (*start++); } @@ -156,12 +150,12 @@ error_code gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen uint hcrc; if (start - src > sourceLen - 2) - return error_code.DATA_ERROR; + return InternalResult.data_error; hcrc = loadLittleEndian!ushort(cast(ushort*)start); if (hcrc != (zlib_crc(src[0 .. start - src]) & 0x0000FFFF)) - return error_code.DATA_ERROR; + return InternalResult.data_error; start += 2; } @@ -169,25 +163,25 @@ error_code gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen // get decompressed length uint dlen = loadLittleEndian!uint(cast(uint*)(src + sourceLen - 4)); if (dlen > dest.length) - return error_code.BUF_ERROR; + return InternalResult.buffer_too_small; if ((src + sourceLen) - start < 8) - return error_code.DATA_ERROR; + return InternalResult.data_error; - if (uncompress(start[0 .. (src + sourceLen) - start - 8], dest, destLen) != error_code.OK) - return error_code.DATA_ERROR; + if (!uncompress(start[0 .. (src + sourceLen) - start - 8], dest, destLen)) + return InternalResult.data_error; if (destLen != dlen) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check CRC32 checksum if (zlib_crc(dest[0..dlen]) != loadLittleEndian!uint(cast(uint*)(src + sourceLen - 8))) - return error_code.DATA_ERROR; + return InternalResult.data_error; - return error_code.OK; + return InternalResult.success; } -error_code uncompress(const(void)[] source, void[] dest, out size_t destLen) +Result uncompress(const(void)[] source, void[] dest, out size_t destLen) { data d; @@ -211,7 +205,7 @@ error_code uncompress(const(void)[] source, void[] dest, out size_t destLen) uint btype = getbits(&d, 2); // Decompress block - error_code res; + Result res; switch (btype) { case 0: @@ -227,20 +221,20 @@ error_code uncompress(const(void)[] source, void[] dest, out size_t destLen) res = inflate_dynamic_block(&d); break; default: - res = error_code.DATA_ERROR; + res = InternalResult.data_error; break; } - if (res != error_code.OK) + if (!res) return res; } while (!bfinal); if (d.overflow) - return error_code.DATA_ERROR; + return InternalResult.data_error; destLen = d.dest - d.dest_start; - return error_code.OK; + return InternalResult.success; } @@ -606,7 +600,7 @@ void build_fixed_trees(tree *lt, tree *dt) } /* Given an array of code lengths, build a tree */ -error_code build_tree(tree *t, const(ubyte)* lengths, ushort num) +Result build_tree(tree *t, const(ubyte)* lengths, ushort num) { ushort[16] offs = void; uint available; @@ -638,7 +632,7 @@ error_code build_tree(tree *t, const(ubyte)* lengths, ushort num) /* Check length contains no more codes than available */ if (used > available) - return error_code.DATA_ERROR; + return InternalResult.data_error; available = 2 * (available - used); offs[i] = num_codes; @@ -650,7 +644,7 @@ error_code build_tree(tree *t, const(ubyte)* lengths, ushort num) * code that it has length 1 */ if ((num_codes > 1 && available > 0) || (num_codes == 1 && t.counts[1] != 1)) - return error_code.DATA_ERROR; + return InternalResult.data_error; /* Fill in symbols sorted by code */ for (i = 0; i < num; ++i) @@ -669,7 +663,7 @@ error_code build_tree(tree *t, const(ubyte)* lengths, ushort num) t.symbols[1] = cast(ushort)(t.max_sym + 1); } - return error_code.OK; + return InternalResult.success; } /* -- Decode functions -- */ @@ -749,7 +743,7 @@ int decode_symbol(data *d, const tree *t) return t.symbols[base + offs]; } -error_code decode_trees(data *d, tree *lt, tree *dt) +Result decode_trees(data *d, tree *lt, tree *dt) { /* Special ordering of code length codes */ __gshared immutable ubyte[19] clcidx = [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]; @@ -776,7 +770,7 @@ error_code decode_trees(data *d, tree *lt, tree *dt) * See also: https://github.com/madler/zlib/issues/82 */ if (hlit > 286 || hdist > 30) - return error_code.DATA_ERROR; + return InternalResult.data_error; for (i = 0; i < 19; ++i) lengths[i] = 0; @@ -791,13 +785,13 @@ error_code decode_trees(data *d, tree *lt, tree *dt) } /* Build code length tree (in literal/length tree to save space) */ - error_code res = build_tree(lt, lengths.ptr, 19); - if (res != error_code.OK) + Result res = build_tree(lt, lengths.ptr, 19); + if (res != InternalResult.success) return res; /* Check code length tree is not empty */ if (lt.max_sym == -1) - return error_code.DATA_ERROR; + return InternalResult.data_error; /* Decode code lengths for the dynamic trees */ for (num = 0; num < hlit + hdist; ) @@ -805,14 +799,14 @@ error_code decode_trees(data *d, tree *lt, tree *dt) int sym = decode_symbol(d, lt); if (sym > lt.max_sym) - return error_code.DATA_ERROR; + return InternalResult.data_error; switch (sym) { case 16: /* Copy previous code length 3-6 times (read 2 bits) */ if (num == 0) { - return error_code.DATA_ERROR; + return InternalResult.data_error; } sym = lengths[num - 1]; length = getbits_base(d, 2, 3); @@ -834,7 +828,7 @@ error_code decode_trees(data *d, tree *lt, tree *dt) } if (length > hlit + hdist - num) - return error_code.DATA_ERROR; + return InternalResult.data_error; while (length--) lengths[num++] = cast(ubyte)sym; @@ -842,26 +836,26 @@ error_code decode_trees(data *d, tree *lt, tree *dt) /* Check EOB symbol is present */ if (lengths[256] == 0) - return error_code.DATA_ERROR; + return InternalResult.data_error; /* Build dynamic trees */ res = build_tree(lt, lengths.ptr, hlit); - if (res != error_code.OK) + if (res != InternalResult.success) return res; res = build_tree(dt, lengths.ptr + hlit, hdist); - if (res != error_code.OK) + if (res != InternalResult.success) return res; - return error_code.OK; + return InternalResult.success; } /* -- Block inflate functions -- */ /* Given a stream and two trees, inflate a block of data */ -error_code inflate_block_data(data *d, tree *lt, tree *dt) +Result inflate_block_data(data *d, tree *lt, tree *dt) { /* Extra bits and base tables for length codes */ __gshared immutable ubyte[30] length_bits = [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 127 ]; @@ -877,12 +871,12 @@ error_code inflate_block_data(data *d, tree *lt, tree *dt) /* Check for overflow in bit reader */ if (d.overflow) - return error_code.DATA_ERROR; + return InternalResult.data_error; if (sym < 256) { if (d.dest == d.dest_end) - return error_code.BUF_ERROR; + return InternalResult.buffer_too_small; *d.dest++ = cast(ubyte)sym; } else @@ -892,11 +886,11 @@ error_code inflate_block_data(data *d, tree *lt, tree *dt) /* Check for end of block */ if (sym == 256) - return error_code.OK; + return InternalResult.success; /* Check sym is within range and distance tree is not empty */ if (sym > lt.max_sym || sym - 257 > 28 || dt.max_sym == -1) - return error_code.DATA_ERROR; + return InternalResult.data_error; sym -= 257; @@ -907,16 +901,16 @@ error_code inflate_block_data(data *d, tree *lt, tree *dt) /* Check dist is within range */ if (dist > dt.max_sym || dist > 29) - return error_code.DATA_ERROR; + return InternalResult.data_error; /* Possibly get more bits from distance code */ offs = getbits_base(d, dist_bits[dist], dist_base[dist]); if (offs > d.dest - d.dest_start) - return error_code.DATA_ERROR; + return InternalResult.data_error; if (d.dest_end - d.dest < length) - return error_code.BUF_ERROR; + return InternalResult.buffer_too_small; /* Copy match */ for (i = 0; i < length; ++i) @@ -928,10 +922,10 @@ error_code inflate_block_data(data *d, tree *lt, tree *dt) } /* Inflate an uncompressed block of data */ -error_code inflate_uncompressed_block(data *d) +Result inflate_uncompressed_block(data *d) { if (d.source_end - d.source < 4) - return error_code.DATA_ERROR; + return InternalResult.data_error; /* Get length */ uint length = loadLittleEndian!ushort(cast(ushort*)d.source); @@ -941,15 +935,15 @@ error_code inflate_uncompressed_block(data *d) /* Check length */ if (length != (~invlength & 0x0000FFFF)) - return error_code.DATA_ERROR; + return InternalResult.data_error; d.source += 4; if (d.source_end - d.source < length) - return error_code.DATA_ERROR; + return InternalResult.data_error; if (d.dest_end - d.dest < length) - return error_code.BUF_ERROR; + return InternalResult.buffer_too_small; /* Copy block */ while (length--) @@ -959,19 +953,19 @@ error_code inflate_uncompressed_block(data *d) d.tag = 0; d.bitcount = 0; - return error_code.OK; + return InternalResult.success; } -error_code inflate_fixed_block(data *d) +Result inflate_fixed_block(data *d) { build_fixed_trees(&d.ltree, &d.dtree); return inflate_block_data(d, &d.ltree, &d.dtree); } -error_code inflate_dynamic_block(data *d) +Result inflate_dynamic_block(data *d) { - error_code res = decode_trees(d, &d.ltree, &d.dtree); - if (res != error_code.OK) + Result res = decode_trees(d, &d.ltree, &d.dtree); + if (res != InternalResult.success) return res; return inflate_block_data(d, &d.ltree, &d.dtree); } From e683e62aefe684b1211d113dfbe0dfb79eedd5d7 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 21 Aug 2025 15:48:17 +1000 Subject: [PATCH 003/138] More snake_case migration... --- src/urt/algorithm.d | 38 +++---- src/urt/async.d | 2 +- src/urt/conv.d | 104 ++++++++--------- src/urt/crc.d | 164 +++++++++++++-------------- src/urt/digest/sha.d | 2 +- src/urt/encoding.d | 28 ++--- src/urt/endian.d | 42 +++---- src/urt/fibre.d | 20 ++-- src/urt/file.d | 4 +- src/urt/format/json.d | 2 +- src/urt/inet.d | 2 +- src/urt/mem/alloc.d | 12 +- src/urt/mem/region.d | 4 +- src/urt/mem/scratchpad.d | 4 +- src/urt/meta/nullable.d | 100 ++++++++--------- src/urt/meta/package.d | 18 +-- src/urt/range/package.d | 18 +-- src/urt/range/primitives.d | 38 +++---- src/urt/string/ansi.d | 4 +- src/urt/string/ascii.d | 50 ++++----- src/urt/string/format.d | 30 ++--- src/urt/string/package.d | 16 +-- src/urt/string/string.d | 8 +- src/urt/string/uni.d | 74 ++++++------ src/urt/time.d | 4 +- src/urt/traits.d | 112 +++++++++---------- src/urt/util.d | 224 ++++++++++++++++++------------------- src/urt/zip.d | 22 ++-- 28 files changed, 573 insertions(+), 573 deletions(-) diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index a742393..9191f28 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -1,6 +1,6 @@ module urt.algorithm; -import urt.traits : lvalueOf; +import urt.traits : lvalue_of; import urt.util : swap; version = SmallSize; @@ -10,18 +10,18 @@ nothrow @nogc: auto compare(T, U)(auto ref T a, auto ref U b) { - static if (__traits(compiles, lvalueOf!T.opCmp(lvalueOf!U))) + static if (__traits(compiles, lvalue_of!T.opCmp(lvalue_of!U))) return a.opCmp(b); - else static if (__traits(compiles, lvalueOf!U.opCmp(lvalueOf!T))) + else static if (__traits(compiles, lvalue_of!U.opCmp(lvalue_of!T))) return -b.opCmp(a); else static if (is(T : A[], A)) { - import urt.traits : isPrimitive; + import urt.traits : is_primitive; auto ai = a.ptr; auto bi = b.ptr; size_t len = a.length < b.length ? a.length : b.length; - static if (isPrimitive!A) + static if (is_primitive!A) { // compare strings foreach (i; 0 .. len) @@ -48,7 +48,7 @@ auto compare(T, U)(auto ref T a, auto ref U b) return a < b ? -1 : (a > b ? 1 : 0); } -size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmpArgs) +size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmp_args) { T* p = arr.ptr; size_t low = 0; @@ -59,7 +59,7 @@ size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmpArgs static if (is(pred == void)) { // should we chase the first in a sequence of same values? - if (p[mid] < cmpArgs[0]) + if (p[mid] < cmp_args[0]) low = mid + 1; else high = mid; @@ -67,7 +67,7 @@ size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmpArgs else { // should we chase the first in a sequence of same values? - int cmp = pred(p[mid], cmpArgs); + int cmp = pred(p[mid], cmp_args); if (cmp < 0) low = mid + 1; else @@ -76,12 +76,12 @@ size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmpArgs } static if (is(pred == void)) { - if (p[low] == cmpArgs[0]) + if (p[low] == cmp_args[0]) return low; } else { - if (pred(p[low], cmpArgs) == 0) + if (pred(p[low], cmp_args) == 0) return low; } return arr.length; @@ -93,7 +93,7 @@ void qsort(alias pred = void, T)(T[] arr) version (SmallSize) { static if (is(pred == void)) - static if (__traits(compiles, lvalueOf!T.opCmp(lvalueOf!T))) + static if (__traits(compiles, lvalue_of!T.opCmp(lvalue_of!T))) static int compare(const void* a, const void* b) nothrow @nogc => (*cast(const T*)a).opCmp(*cast(const T*)b); else @@ -181,34 +181,34 @@ version (SmallSize) // just one generic implementation to minimise the code... // kinda slow though... look at all those multiplies! // maybe there's some way to make this faster :/ - void qsort(void[] arr, size_t elementSize, int function(const void* a, const void* b) nothrow @nogc compare, void function(void* a, void* b) nothrow @nogc swap) + void qsort(void[] arr, size_t element_size, int function(const void* a, const void* b) nothrow @nogc compare, void function(void* a, void* b) nothrow @nogc swap) { void* p = arr.ptr; - size_t length = arr.length / elementSize; + size_t length = arr.length / element_size; if (length > 1) { size_t pivotIndex = length / 2; - void* pivot = p + pivotIndex*elementSize; + void* pivot = p + pivotIndex*element_size; size_t i = 0; size_t j = length - 1; while (i <= j) { - while (compare(p + i*elementSize, pivot) < 0) i++; - while (compare(p + j*elementSize, pivot) > 0) j--; + while (compare(p + i*element_size, pivot) < 0) i++; + while (compare(p + j*element_size, pivot) > 0) j--; if (i <= j) { - swap(p + i*elementSize, p + j*elementSize); + swap(p + i*element_size, p + j*element_size); i++; j--; } } if (j > 0) - qsort(p[0 .. (j + 1)*elementSize], elementSize, compare, swap); + qsort(p[0 .. (j + 1)*element_size], element_size, compare, swap); if (i < length) - qsort(p[i*elementSize .. length*elementSize], elementSize, compare, swap); + qsort(p[i*element_size .. length*element_size], element_size, compare, swap); } } } diff --git a/src/urt/async.d b/src/urt/async.d index fee575f..5da7e61 100644 --- a/src/urt/async.d +++ b/src/urt/async.d @@ -20,7 +20,7 @@ Promise!(ReturnType!Fun)* async(alias Fun, size_t stackSize = DefaultStackSize, // TODO: nice to rework this; maybe make stackSize a not-template-arg, and receive a function call/closure object which stores the args Promise!(ReturnType!Fun)* async(size_t stackSize = DefaultStackSize, Fun, Args...)(Fun fun, auto ref Args args) - if (isSomeFunction!Fun) + if (is_some_function!Fun) { alias Result = ReturnType!Fun; Promise!Result* r = cast(Promise!Result*)defaultAllocator().alloc(Promise!Result.sizeof, Promise!Result.alignof); diff --git a/src/urt/conv.d b/src/urt/conv.d index ebce388..a20b6ee 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -7,8 +7,8 @@ public import urt.string.format : toString; nothrow @nogc: -// on error or not-a-number cases, bytesTaken will contain 0 -long parse_int(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure +// on error or not-a-number cases, bytes_taken will contain 0 +long parse_int(const(char)[] str, size_t* bytes_taken = null, int base = 10) pure { size_t i = 0; bool neg = false; @@ -21,13 +21,13 @@ long parse_int(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure i++; } - ulong value = str.ptr[i .. str.length].parse_uint(bytesTaken, base); - if (bytesTaken && *bytesTaken != 0) - *bytesTaken += i; + ulong value = str.ptr[i .. str.length].parse_uint(bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += i; return neg ? -cast(long)value : cast(long)value; } -long parse_int_with_decimal(const(char)[] str, out ulong fixedPointDivisor, size_t* bytesTaken = null, int base = 10) pure +long parse_int_with_decimal(const(char)[] str, out ulong fixed_point_divisor, size_t* bytes_taken = null, int base = 10) pure { size_t i = 0; bool neg = false; @@ -40,13 +40,13 @@ long parse_int_with_decimal(const(char)[] str, out ulong fixedPointDivisor, size i++; } - ulong value = str[i .. str.length].parse_uint_with_decimal(fixedPointDivisor, bytesTaken, base); - if (bytesTaken && *bytesTaken != 0) - *bytesTaken += i; + ulong value = str[i .. str.length].parse_uint_with_decimal(fixed_point_divisor, bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += i; return neg ? -cast(long)value : cast(long)value; } -ulong parse_uint(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure +ulong parse_uint(const(char)[] str, size_t* bytes_taken = null, int base = 10) pure { assert(base > 1 && base <= 36, "Invalid base"); @@ -76,12 +76,12 @@ ulong parse_uint(const(char)[] str, size_t* bytesTaken = null, int base = 10) pu } } - if (bytesTaken) - *bytesTaken = s - str.ptr; + if (bytes_taken) + *bytes_taken = s - str.ptr; return value; } -ulong parse_uint_with_decimal(const(char)[] str, out ulong fixedPointDivisor, size_t* bytesTaken = null, int base = 10) pure +ulong parse_uint_with_decimal(const(char)[] str, out ulong fixed_point_divisor, size_t* bytes_taken = null, int base = 10) pure { assert(base > 1 && base <= 36, "Invalid base"); @@ -126,19 +126,19 @@ parse_decimal: } done: - fixedPointDivisor = divisor; - if (bytesTaken) - *bytesTaken = s - str.ptr; + fixed_point_divisor = divisor; + if (bytes_taken) + *bytes_taken = s - str.ptr; return value; } -ulong parse_uint_with_base(const(char)[] str, size_t* bytesTaken = null) pure +ulong parse_uint_with_base(const(char)[] str, size_t* bytes_taken = null) pure { const(char)* p = str.ptr; int base = parse_base_prefix(str); - ulong i = parse_uint(str, bytesTaken, base); - if (bytesTaken && *bytesTaken != 0) - *bytesTaken += str.ptr - p; + ulong i = parse_uint(str, bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += str.ptr - p; return i; } @@ -222,15 +222,15 @@ unittest } -// on error or not-a-number, result will be nan and bytesTaken will contain 0 -double parse_float(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure +// on error or not-a-number, result will be nan and bytes_taken will contain 0 +double parse_float(const(char)[] str, size_t* bytes_taken = null, int base = 10) pure { // TODO: E-notation... size_t taken = void; ulong div = void; long value = str.parse_int_with_decimal(div, &taken, base); - if (bytesTaken) - *bytesTaken = taken; + if (bytes_taken) + *bytes_taken = taken; if (taken == 0) return double.nan; return cast(double)value / div; @@ -253,18 +253,18 @@ unittest } -ptrdiff_t format_int(long value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ', bool showSign = false) pure +ptrdiff_t format_int(long value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ', bool show_sign = false) pure { const bool neg = value < 0; - showSign |= neg; + show_sign |= neg; - if (buffer.ptr && buffer.length < showSign) + if (buffer.ptr && buffer.length < show_sign) return -1; ulong i = neg ? -value : value; - ptrdiff_t r = format_uint(i, buffer.ptr ? buffer.ptr[(width == 0 ? showSign : 0) .. buffer.length] : null, base, width, fill); - if (r < 0 || !showSign) + ptrdiff_t r = format_uint(i, buffer.ptr ? buffer.ptr[(width == 0 ? show_sign : 0) .. buffer.length] : null, base, width, fill); + if (r < 0 || !show_sign) return r; if (buffer.ptr) @@ -286,10 +286,10 @@ ptrdiff_t format_int(long value, char[] buffer, uint base = 10, uint width = 0, if (buffer.ptr[0] == fill) { // we don't need to shift it left... - size_t sgnOffset = 0; - while (buffer.ptr[sgnOffset + 1] == fill) - ++sgnOffset; - buffer.ptr[sgnOffset] = sgn; + size_t sgn_offset = 0; + while (buffer.ptr[sgn_offset + 1] == fill) + ++sgn_offset; + buffer.ptr[sgn_offset] = sgn; return r; } @@ -316,13 +316,13 @@ ptrdiff_t format_uint(ulong value, char[] buffer, uint base = 10, uint width = 0 assert(base >= 2 && base <= 36, "Invalid base"); ulong i = value; - uint numLen = 0; + uint num_len = 0; char[64] t = void; if (i == 0) { if (buffer.length > 0) t.ptr[0] = '0'; - numLen = 1; + num_len = 1; } else { @@ -334,14 +334,14 @@ ptrdiff_t format_uint(ulong value, char[] buffer, uint base = 10, uint width = 0 if (buffer.ptr) { int d = cast(int)(i % base); - t.ptr[numLen] = cast(char)((d < 10 ? '0' : 'A' - 10) + d); + t.ptr[num_len] = cast(char)((d < 10 ? '0' : 'A' - 10) + d); } - ++numLen; + ++num_len; } } - uint len = max(numLen, width); - uint padding = width > numLen ? width - numLen : 0; + uint len = max(num_len, width); + uint padding = width > num_len ? width - num_len : 0; if (buffer.ptr) { @@ -351,7 +351,7 @@ ptrdiff_t format_uint(ulong value, char[] buffer, uint base = 10, uint width = 0 size_t offset = 0; while (padding--) buffer.ptr[offset++] = fill; - for (uint j = numLen; j > 0; ) + for (uint j = num_len; j > 0; ) buffer.ptr[offset++] = t[--j]; } return len; @@ -442,12 +442,12 @@ template to(T) return r; } } - else static if (isSomeInt!T) // call-through for other int types; reduce instantiation bloat + else static if (is_some_int!T) // call-through for other int types; reduce instantiation bloat { T to(const(char)[] str) => cast(T)to!long(str); } - else static if (isSomeFloat!T) // call-through for other float types; reduce instantiation bloat + else static if (is_some_float!T) // call-through for other float types; reduce instantiation bloat { T to(const(char)[] str) => cast(T)to!double(str); @@ -481,15 +481,15 @@ private: uint get_digit(char c) pure { - uint zeroBase = c - '0'; - if (zeroBase < 10) - return zeroBase; - uint ABase = c - 'A'; - if (ABase < 26) - return ABase + 10; - uint aBase = c - 'a'; - if (aBase < 26) - return aBase + 10; + uint zero_base = c - '0'; + if (zero_base < 10) + return zero_base; + uint A_base = c - 'A'; + if (A_base < 26) + return A_base + 10; + uint a_base = c - 'a'; + if (a_base < 26) + return a_base + 10; return -1; } @@ -518,7 +518,7 @@ size_t format_struct(T)(ref T value, char[] buffer) nothrow @nogc alias args = value.tupleof; // alias args = AliasSeq!(value.tupleof); -// alias args = InterleaveSeparator!(", ", value.tupleof); +// alias args = INTERLEAVE_SEPARATOR!(", ", value.tupleof); // pragma(msg, args); return concat(buffer, args).length; } diff --git a/src/urt/crc.d b/src/urt/crc.d index 74f266b..b26b6c9 100644 --- a/src/urt/crc.d +++ b/src/urt/crc.d @@ -1,51 +1,51 @@ module urt.crc; import urt.meta : IntForWidth; -import urt.traits : isUnsignedInt; +import urt.traits : is_unsigned_int; nothrow @nogc: enum Algorithm : ubyte { - CRC16_USB, - CRC16_MODBUS, - CRC16_KERMIT, - CRC16_XMODEM, - CRC16_CCITT_FALSE, - CRC16_ISO_HDLC, - CRC16_DNP, - CRC32_ISO_HDLC, - CRC32_CASTAGNOLI, - - CRC16_Default_ShortPacket = CRC16_KERMIT, // good default choice for small packets - CRC32_Default = CRC32_CASTAGNOLI, // has SSE4.2 hardware implementation - - // aliases - CRC16_BLUETOOTH = CRC16_KERMIT, - CRC16_CCITT_TRUE = CRC16_KERMIT, - CRC16_CCITT = CRC16_KERMIT, - CRC16_EZSP = CRC16_CCITT_FALSE, - CRC16_IBM_SDLC = CRC16_ISO_HDLC, - CRC32_NVME = CRC32_CASTAGNOLI, + crc16_usb, + crc16_modbus, + crc16_kermit, + crc16_xmodem, + crc16_ccitt_false, + crc16_iso_hdlc, + crc16_dnp, + crc32_iso_hdlc, + crc32_castagnoli, + + crc16_default_short_packet = crc16_kermit, // good default choice for small packets + crc32_default = crc32_castagnoli, // has SSE4.2 hardware implementation + + // aliases + crc16_bluetooth = crc16_kermit, + crc16_ccitt_true = crc16_kermit, + crc16_ccitt = crc16_kermit, + crc16_ezsp = crc16_ccitt_false, + crc16_ibm_sdlc = crc16_iso_hdlc, + crc32_nvme = crc32_castagnoli, } -struct CRCParams +struct crc_params { ubyte width; bool reflect; uint poly; uint initial; - uint finalXor; + uint final_xor; uint check; } -alias CRCTable(Algorithm algo) = CRCTable!(paramTable[algo].width, paramTable[algo].poly, paramTable[algo].reflect); -alias CRCType(Algorithm algo) = IntForWidth!(paramTable[algo].width); +alias CRCType(Algorithm algo) = IntForWidth!(param_table[algo].width); +alias crc_table(Algorithm algo) = crc_table!(param_table[algo].width, param_table[algo].poly, param_table[algo].reflect); // compute a CRC with runtime parameters -T calculate_crc(T = uint)(const void[] data, ref const CRCParams params, ref const T[256] table) pure - if (isUnsignedInt!T) +T calculate_crc(T = uint)(const void[] data, ref const crc_params params, ref const T[256] table) pure + if (is_unsigned_int!T) { assert(params.width <= T.sizeof*8, "T is too small for the CRC width"); @@ -64,22 +64,22 @@ T calculate_crc(T = uint)(const void[] data, ref const CRCParams params, ref con crc = cast(T)((crc << 8) ^ table[(crc >> 8) ^ b]); } - return crc ^ cast(T)params.finalXor; + return crc ^ cast(T)params.final_xor; } // compute a CRC with hard-coded parameters -T calculate_crc(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = cast(T)paramTable[algo].initial^paramTable[algo].finalXor) pure - if (isUnsignedInt!T) +T calculate_crc(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = cast(T)param_table[algo].initial^param_table[algo].final_xor) pure + if (is_unsigned_int!T) { - enum CRCParams params = paramTable[algo]; + enum crc_params params = param_table[algo]; static assert(params.width <= T.sizeof*8, "T is too small for the CRC width"); - alias table = CRCTable!algo; + alias table = crc_table!algo; const ubyte[] bytes = cast(ubyte[])data; - static if (params.finalXor) - T crc = initial ^ params.finalXor; + static if (params.final_xor) + T crc = initial ^ params.final_xor; else T crc = initial; @@ -91,32 +91,32 @@ T calculate_crc(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = crc = cast(T)((crc << 8) ^ table[(crc >> 8) ^ b]); } - static if (params.finalXor) - return T(crc ^ params.finalXor); + static if (params.final_xor) + return T(crc ^ params.final_xor); else return crc; } // computes 2 CRC's for 2 points in the data stream... -T calculate_crc_2(Algorithm algo, T = IntForWidth!(paramTable[algo].width*2))(const void[] data, uint earlyOffset) pure - if (isUnsignedInt!T) +T calculate_crc_2(Algorithm algo, T = IntForWidth!(param_table[algo].width*2))(const void[] data, uint early_offset) pure + if (is_unsigned_int!T) { - enum CRCParams params = paramTable[algo]; + enum crc_params params = param_table[algo]; static assert(params.width * 2 <= T.sizeof*8, "T is too small for the CRC width"); - alias table = CRCTable!algo; + alias table = crc_table!algo; const ubyte[] bytes = cast(ubyte[])data; - T highCRC = 0; + T high_crc = 0; T crc = cast(T)params.initial; size_t i = 0; for (; i < bytes.length; ++i) { - if (i == earlyOffset) + if (i == early_offset) { - highCRC = crc; + high_crc = crc; goto fast_loop; // skips a redundant loop entry check } static if (params.reflect) @@ -136,27 +136,27 @@ T calculate_crc_2(Algorithm algo, T = IntForWidth!(paramTable[algo].width*2))(co } done: - static if (params.finalXor) + static if (params.final_xor) { - crc ^= cast(T)params.finalXor; - highCRC ^= cast(T)params.finalXor; + crc ^= cast(T)params.final_xor; + high_crc ^= cast(T)params.final_xor; } static if (params.width <= 8) - return ushort(crc | highCRC << 8); + return ushort(crc | high_crc << 8); else static if (params.width <= 16) - return uint(crc | highCRC << 16); + return uint(crc | high_crc << 16); else if (params.width <= 32) - return ulong(crc | highCRC << 32); + return ulong(crc | high_crc << 32); } -T[256] generate_crc_table(T)(ref const CRCParams params) pure - if (isUnsignedInt!T) +T[256] generate_crc_table(T)(ref const crc_params params) pure + if (is_unsigned_int!T) { - enum typeWidth = T.sizeof * 8; - assert(params.width <= typeWidth && params.width > typeWidth/2, "CRC width must match the size of the type"); - T topBit = cast(T)(1 << (params.width - 1)); + enum type_width = T.sizeof * 8; + assert(params.width <= type_width && params.width > type_width/2, "CRC width must match the size of the type"); + T top_bit = cast(T)(1 << (params.width - 1)); T[256] table = void; @@ -169,7 +169,7 @@ T[256] generate_crc_table(T)(ref const CRCParams params) pure crc <<= (params.width - 8); // Shift to align with the polynomial width foreach (_; 0..8) { - if ((crc & topBit) != 0) + if ((crc & top_bit) != 0) crc = cast(T)((crc << 1) ^ params.poly); else crc <<= 1; @@ -189,38 +189,38 @@ unittest { immutable ubyte[9] checkData = ['1','2','3','4','5','6','7','8','9']; - assert(calculate_crc!(Algorithm.CRC16_MODBUS)(checkData[]) == paramTable[Algorithm.CRC16_MODBUS].check); - assert(calculate_crc!(Algorithm.CRC16_EZSP)(checkData[]) == paramTable[Algorithm.CRC16_EZSP].check); - assert(calculate_crc!(Algorithm.CRC16_KERMIT)(checkData[]) == paramTable[Algorithm.CRC16_KERMIT].check); - assert(calculate_crc!(Algorithm.CRC16_USB)(checkData[]) == paramTable[Algorithm.CRC16_USB].check); - assert(calculate_crc!(Algorithm.CRC16_XMODEM)(checkData[]) == paramTable[Algorithm.CRC16_XMODEM].check); - assert(calculate_crc!(Algorithm.CRC16_ISO_HDLC)(checkData[]) == paramTable[Algorithm.CRC16_ISO_HDLC].check); - assert(calculate_crc!(Algorithm.CRC16_DNP)(checkData[]) == paramTable[Algorithm.CRC16_DNP].check); - assert(calculate_crc!(Algorithm.CRC32_ISO_HDLC)(checkData[]) == paramTable[Algorithm.CRC32_ISO_HDLC].check); - assert(calculate_crc!(Algorithm.CRC32_CASTAGNOLI)(checkData[]) == paramTable[Algorithm.CRC32_CASTAGNOLI].check); + assert(calculate_crc!(Algorithm.crc16_modbus)(checkData[]) == param_table[Algorithm.crc16_modbus].check); + assert(calculate_crc!(Algorithm.crc16_ezsp)(checkData[]) == param_table[Algorithm.crc16_ezsp].check); + assert(calculate_crc!(Algorithm.crc16_kermit)(checkData[]) == param_table[Algorithm.crc16_kermit].check); + assert(calculate_crc!(Algorithm.crc16_usb)(checkData[]) == param_table[Algorithm.crc16_usb].check); + assert(calculate_crc!(Algorithm.crc16_xmodem)(checkData[]) == param_table[Algorithm.crc16_xmodem].check); + assert(calculate_crc!(Algorithm.crc16_iso_hdlc)(checkData[]) == param_table[Algorithm.crc16_iso_hdlc].check); + assert(calculate_crc!(Algorithm.crc16_dnp)(checkData[]) == param_table[Algorithm.crc16_dnp].check); + assert(calculate_crc!(Algorithm.crc32_iso_hdlc)(checkData[]) == param_table[Algorithm.crc32_iso_hdlc].check); + assert(calculate_crc!(Algorithm.crc32_castagnoli)(checkData[]) == param_table[Algorithm.crc32_castagnoli].check); // check that rolling CRC works... - ushort crc = calculate_crc!(Algorithm.CRC16_MODBUS)(checkData[0 .. 5]); - assert(calculate_crc!(Algorithm.CRC16_MODBUS)(checkData[5 .. 9], crc) == paramTable[Algorithm.CRC16_MODBUS].check); - crc = calculate_crc!(Algorithm.CRC16_ISO_HDLC)(checkData[0 .. 5]); - assert(calculate_crc!(Algorithm.CRC16_ISO_HDLC)(checkData[5 .. 9], crc) == paramTable[Algorithm.CRC16_ISO_HDLC].check); - uint crc32 = calculate_crc!(Algorithm.CRC32_ISO_HDLC)(checkData[0 .. 5]); - assert(calculate_crc!(Algorithm.CRC32_ISO_HDLC)(checkData[5 .. 9], crc32) == paramTable[Algorithm.CRC32_ISO_HDLC].check); + ushort crc = calculate_crc!(Algorithm.crc16_modbus)(checkData[0 .. 5]); + assert(calculate_crc!(Algorithm.crc16_modbus)(checkData[5 .. 9], crc) == param_table[Algorithm.crc16_modbus].check); + crc = calculate_crc!(Algorithm.crc16_iso_hdlc)(checkData[0 .. 5]); + assert(calculate_crc!(Algorithm.crc16_iso_hdlc)(checkData[5 .. 9], crc) == param_table[Algorithm.crc16_iso_hdlc].check); + uint crc32 = calculate_crc!(Algorithm.crc32_iso_hdlc)(checkData[0 .. 5]); + assert(calculate_crc!(Algorithm.crc32_iso_hdlc)(checkData[5 .. 9], crc32) == param_table[Algorithm.crc32_iso_hdlc].check); } private: -enum CRCParams[] paramTable = [ - CRCParams(16, true, 0x8005, 0xFFFF, 0xFFFF, 0xB4C8), // CRC16_USB - CRCParams(16, true, 0x8005, 0xFFFF, 0x0000, 0x4B37), // CRC16_MODBUS - CRCParams(16, true, 0x1021, 0x0000, 0x0000, 0x2189), // CRC16_KERMIT - CRCParams(16, false, 0x1021, 0x0000, 0x0000, 0x31C3), // CRC16_XMODEM - CRCParams(16, false, 0x1021, 0xFFFF, 0x0000, 0x29B1), // CRC16_CCITT_FALSE - CRCParams(16, true, 0x1021, 0xFFFF, 0xFFFF, 0x906E), // CRC16_ISO_HDLC - CRCParams(16, true, 0x3D65, 0x0000, 0xFFFF, 0xEA82), // CRC16_DNP - CRCParams(32, true, 0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF, 0xCBF43926), // CRC32_ISO_HDLC - CRCParams(32, true, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, 0xE3069283), // CRC32_CASTAGNOLI +enum crc_params[] param_table = [ + crc_params(16, true, 0x8005, 0xFFFF, 0xFFFF, 0xB4C8), // crc16_usb + crc_params(16, true, 0x8005, 0xFFFF, 0x0000, 0x4B37), // crc16_modbus + crc_params(16, true, 0x1021, 0x0000, 0x0000, 0x2189), // crc16_kermit + crc_params(16, false, 0x1021, 0x0000, 0x0000, 0x31C3), // crc16_xmodem + crc_params(16, false, 0x1021, 0xFFFF, 0x0000, 0x29B1), // crc16_ccitt_false + crc_params(16, true, 0x1021, 0xFFFF, 0xFFFF, 0x906E), // crc16_iso_hdlc + crc_params(16, true, 0x3D65, 0x0000, 0xFFFF, 0xEA82), // crc16_dnp + crc_params(32, true, 0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF, 0xCBF43926), // crc32_iso_hdlc + crc_params(32, true, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, 0xE3069283), // crc32_castagnoli ]; // helper function to reflect bits (reverse bit order) @@ -236,7 +236,7 @@ T reflect(T)(T value, ubyte bits) } // this minimises the number of table instantiations -template CRCTable(uint width, uint poly, bool reflect) +template crc_table(uint width, uint poly, bool reflect) { - __gshared immutable CRCTable = generate_crc_table!(IntForWidth!width)(CRCParams(width, reflect, poly, 0, 0, 0)); + __gshared immutable crc_table = generate_crc_table!(IntForWidth!width)(crc_params(width, reflect, poly, 0, 0, 0)); } diff --git a/src/urt/digest/sha.d b/src/urt/digest/sha.d index fc64067..197b7f4 100644 --- a/src/urt/digest/sha.d +++ b/src/urt/digest/sha.d @@ -105,7 +105,7 @@ ubyte[Context.DigestLen] sha_finalise(Context)(ref Context ctx) // reverse all the bytes when copying the final state to the output hash. uint[Context.DigestElements] digest = void; foreach (uint j; 0 .. Context.DigestElements) - digest[j] = byteReverse(ctx.state[j]); + digest[j] = byte_reverse(ctx.state[j]); return cast(ubyte[Context.DigestLen])digest; } diff --git a/src/urt/encoding.d b/src/urt/encoding.d index 36ced1b..54eb70d 100644 --- a/src/urt/encoding.d +++ b/src/urt/encoding.d @@ -7,8 +7,8 @@ enum Hex(const char[] s) = (){ ubyte[s.length / 2] r; ptrdiff_t len = hex_decode enum Base64(const char[] s) = (){ ubyte[base64_decode_length(s)] r; ptrdiff_t len = base64_decode(s, r); assert(len == r.sizeof, "Not a base64 string!"); return r; }(); -ptrdiff_t base64_encode_length(size_t sourceLength) pure - => (sourceLength + 2) / 3 * 4; +ptrdiff_t base64_encode_length(size_t source_length) pure + => (source_length + 2) / 3 * 4; ptrdiff_t base64_encode(const void[] data, char[] result) pure { @@ -54,8 +54,8 @@ ptrdiff_t base64_encode(const void[] data, char[] result) pure return out_len; } -ptrdiff_t base64_decode_length(size_t sourceLength) pure -=> sourceLength / 4 * 3; +ptrdiff_t base64_decode_length(size_t source_length) pure +=> source_length / 4 * 3; ptrdiff_t base64_decode(const char[] data, void[] result) pure { @@ -144,7 +144,7 @@ ptrdiff_t hex_encode(const void[] data, char[] result) pure ptrdiff_t hex_decode(const char[] data, void[] result) pure { - import urt.string.ascii : isHex; + import urt.string.ascii : is_hex; if (data.length & 1) return -1; @@ -157,7 +157,7 @@ ptrdiff_t hex_decode(const char[] data, void[] result) pure { ubyte c0 = data[i]; ubyte c1 = data[i + 1]; - if (!c0.isHex || !c1.isHex) + if (!c0.is_hex || !c1.is_hex) return -1; if ((c0 | 0x20) >= 'a') @@ -193,12 +193,12 @@ unittest ptrdiff_t url_encode_length(const char[] data) pure { - import urt.string.ascii : isURL; + import urt.string.ascii : is_url; size_t len = 0; foreach (c; data) { - if (c.isURL || c == ' ') + if (c.is_url || c == ' ') ++len; else len += 3; @@ -208,14 +208,14 @@ ptrdiff_t url_encode_length(const char[] data) pure ptrdiff_t url_encode(const char[] data, char[] result) pure { - import urt.string.ascii : isURL, hexDigits; + import urt.string.ascii : is_url, hex_digits; size_t j = 0; for (size_t i = 0; i < data.length; ++i) { char c = data[i]; - if (c.isURL || c == ' ') + if (c.is_url || c == ' ') { if (j == result.length) return -1; @@ -226,8 +226,8 @@ ptrdiff_t url_encode(const char[] data, char[] result) pure if (j + 2 == result.length) return -1; result[j++] = '%'; - result[j++] = hexDigits[c >> 4]; - result[j++] = hexDigits[c & 0xF]; + result[j++] = hex_digits[c >> 4]; + result[j++] = hex_digits[c & 0xF]; } } @@ -250,7 +250,7 @@ ptrdiff_t url_decode_length(const char[] data) pure ptrdiff_t url_decode(const char[] data, char[] result) pure { - import urt.string.ascii : isHex; + import urt.string.ascii : is_hex; size_t j = 0; for (size_t i = 0; i < data.length; ++i) @@ -268,7 +268,7 @@ ptrdiff_t url_decode(const char[] data, char[] result) pure ubyte c0 = data[i + 1]; ubyte c1 = data[i + 2]; - if (!c0.isHex || !c1.isHex) + if (!c0.is_hex || !c1.is_hex) return -1; i += 2; diff --git a/src/urt/endian.d b/src/urt/endian.d index 879b666..bb806d9 100644 --- a/src/urt/endian.d +++ b/src/urt/endian.d @@ -4,20 +4,20 @@ import urt.processor; import urt.traits; public import urt.processor : LittleEndian; -public import urt.util : byteReverse; +public import urt.util : byte_reverse; pure nothrow @nogc: // load from byte arrays pragma(inline, true) T endianToNative(T, bool little)(ref const ubyte[1] bytes) - if (T.sizeof == 1 && isIntegral!T) + if (T.sizeof == 1 && is_integral!T) { return cast(T)bytes[0]; } ushort endianToNative(T, bool little)(ref const ubyte[2] bytes) - if (T.sizeof == 2 && isIntegral!T) + if (T.sizeof == 2 && is_integral!T) { if (__ctfe || !SupportUnalignedLoadStore) { @@ -33,12 +33,12 @@ ushort endianToNative(T, bool little)(ref const ubyte[2] bytes) static if (LittleEndian == little) return *cast(ushort*)bytes.ptr; else - return byteReverse(*cast(ushort*)bytes.ptr); + return byte_reverse(*cast(ushort*)bytes.ptr); } } uint endianToNative(T, bool little)(ref const ubyte[4] bytes) - if (T.sizeof == 4 && isIntegral!T) + if (T.sizeof == 4 && is_integral!T) { if (__ctfe || !SupportUnalignedLoadStore) { @@ -54,12 +54,12 @@ uint endianToNative(T, bool little)(ref const ubyte[4] bytes) static if (LittleEndian == little) return *cast(uint*)bytes.ptr; else - return byteReverse(*cast(uint*)bytes.ptr); + return byte_reverse(*cast(uint*)bytes.ptr); } } ulong endianToNative(T, bool little)(ref const ubyte[8] bytes) - if (T.sizeof == 8 && isIntegral!T) + if (T.sizeof == 8 && is_integral!T) { if (__ctfe || !SupportUnalignedLoadStore) { @@ -75,12 +75,12 @@ ulong endianToNative(T, bool little)(ref const ubyte[8] bytes) static if (LittleEndian == little) return *cast(ulong*)bytes.ptr; else - return byteReverse(*cast(ulong*)bytes.ptr); + return byte_reverse(*cast(ulong*)bytes.ptr); } } pragma(inline, true) T endianToNative(T, bool little)(ref const ubyte[T.sizeof] bytes) - if (!isIntegral!T && !is(T == struct) && !is(T == U[N], U, size_t N)) + if (!is_integral!T && !is(T == struct) && !is(T == U[N], U, size_t N)) { import urt.meta : IntForWidth; alias U = IntForWidth!(T.sizeof*8); @@ -156,7 +156,7 @@ ubyte[2] nativeToEndian(bool little)(ushort u) static if (SupportUnalignedLoadStore) { static if (LittleEndian != little) - u = byteReverse(u); + u = byte_reverse(u); else pragma(inline, true); return *cast(ubyte[2]*)&u; @@ -176,7 +176,7 @@ ubyte[4] nativeToEndian(bool little)(uint u) static if (SupportUnalignedLoadStore) { static if (LittleEndian != little) - u = byteReverse(u); + u = byte_reverse(u); else pragma(inline, true); return *cast(ubyte[4]*)&u; @@ -196,7 +196,7 @@ ubyte[8] nativeToEndian(bool little)(ulong u) static if (SupportUnalignedLoadStore) { static if (LittleEndian != little) - u = byteReverse(u); + u = byte_reverse(u); else pragma(inline, true); return *cast(ubyte[8]*)&u; @@ -204,7 +204,7 @@ ubyte[8] nativeToEndian(bool little)(ulong u) } pragma(inline, true) auto nativeToEndian(bool little, T)(T val) - if (!isIntegral!T && !is(T == struct) && !is(T == U[N], U, size_t N)) + if (!is_integral!T && !is(T == struct) && !is(T == U[N], U, size_t N)) { import urt.meta : IntForWidth; alias U = IntForWidth!(T.sizeof*8); @@ -261,36 +261,36 @@ ubyte[T.sizeof] nativeToLittleEndian(T)(auto ref const T data) // load/store from/to memory void storeBigEndian(T)(T* target, const T val) - if (isSomeInt!T || is(T == float)) + if (is_some_int!T || is(T == float)) { version (BigEndian) *target = val; else - *target = byteReverse(val); + *target = byte_reverse(val); } void storeLittleEndian(T)(T* target, const T val) - if (isSomeInt!T || is(T == float)) + if (is_some_int!T || is(T == float)) { version (LittleEndian) *target = val; else - *target = byteReverse(val); + *target = byte_reverse(val); } T loadBigEndian(T)(const(T)* src) - if (isSomeInt!T || is(T == float)) + if (is_some_int!T || is(T == float)) { version (BigEndian) return *src; else - return byteReverse(*src); + return byte_reverse(*src); } T loadLittleEndian(T)(const(T)* src) - if (isSomeInt!T || is(T == float)) + if (is_some_int!T || is(T == float)) { version (LittleEndian) return *src; else - return byteReverse(*src); + return byte_reverse(*src); } diff --git a/src/urt/fibre.d b/src/urt/fibre.d index 574e551..2e736d2 100644 --- a/src/urt/fibre.d +++ b/src/urt/fibre.d @@ -2,7 +2,7 @@ module urt.fibre; import urt.mem; import urt.time; -import urt.util : isAligned, max; +import urt.util : is_aligned, max; version (Windows) version = UseWindowsFibreAPI; @@ -49,7 +49,7 @@ struct Fibre mainFibre = co_active(); // TODO: i think it's a bug that this stuff isn't initialised! - isDelegate = false; + is_delegate = false; abortRequested = false; finished = true; // init in a state ready to be recycled... aborted = false; @@ -62,7 +62,7 @@ struct Fibre while (true) { try { - if (thisFibre.isDelegate) + if (thisFibre.is_delegate) { FibreEntryDelegate dg; dg.ptr = thisFibre.userData; @@ -104,7 +104,7 @@ struct Fibre { this(cast(FibreEntryFunc)fibreEntry.funcptr, yieldHandler, fibreEntry.ptr, stackSize); - isDelegate = true; + is_delegate = true; } this(FibreEntryFunc fibreEntry, YieldHandler yieldHandler, void* userData = null, size_t stackSize = DefaultStackSize) nothrow @@ -151,7 +151,7 @@ struct Fibre this.fibreEntry = cast(FibreEntryFunc)fibreEntry.funcptr; userData = fibreEntry.ptr; - isDelegate = true; + is_delegate = true; abortRequested = false; finished = false; aborted = false; @@ -163,7 +163,7 @@ struct Fibre this.fibreEntry = fibreEntry; this.userData = userData; - isDelegate = false; + is_delegate = false; abortRequested = false; finished = false; aborted = false; @@ -198,7 +198,7 @@ private: YieldHandler yieldHandler; cothread_t fibre; - bool isDelegate; + bool is_delegate; bool abortRequested; bool finished; bool aborted; @@ -570,7 +570,7 @@ else void co_init_stack(void* base, void* top, coentry_t entry) { - assert(isAligned!16(base) && isAligned!16(top), "Stack must be aligned to 16 bytes"); + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); void** sp = cast(void**)top; // seek to top of stack *--sp = &crash; // crash if entrypoint returns @@ -741,7 +741,7 @@ else void co_init_stack(void* base, void* top, coentry_t entry) { - assert(isAligned!16(base) && isAligned!16(top), "Stack must be aligned to 16 bytes"); + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); void** p = cast(void**)base; p[8] = cast(void*)top; // starting sp @@ -804,7 +804,7 @@ else void co_init_stack(void* base, void* top, coentry_t entry) { - assert(isAligned!16(base) && isAligned!16(top), "Stack must be aligned to 16 bytes"); + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); void** p = cast(void**)base; p[0] = cast(void*)top; // x16 (stack pointer) diff --git a/src/urt/file.d b/src/urt/file.d index 8d3995c..37dfad0 100644 --- a/src/urt/file.d +++ b/src/urt/file.d @@ -195,7 +195,7 @@ Result get_path(ref const File file, ref char[] buffer) if (result == 0 || result > dwPathLen) return getlasterror_result(); - size_t pathLen = tmp[0..result].uniConvert(buffer); + size_t pathLen = tmp[0..result].uni_convert(buffer); if (!pathLen) return InternalResult.buffer_too_small; if (buffer.length >= 4 && buffer[0..4] == `\\?\`) @@ -774,7 +774,7 @@ Result get_temp_filename(ref char[] buffer, const(char)[] dstDir, const(char)[] if (!GetTempFileNameW(dstDir.twstringz, prefix.twstringz, 0, tmp.ptr)) return getlasterror_result(); size_t resLen = wcslen(tmp.ptr); - resLen = tmp[((dstDir.length == 0 && tmp[0] == '\\') ? 1 : 0)..resLen].uniConvert(buffer); + resLen = tmp[((dstDir.length == 0 && tmp[0] == '\\') ? 1 : 0)..resLen].uni_convert(buffer); if (resLen == 0) { DeleteFileW(tmp.ptr); diff --git a/src/urt/format/json.d b/src/urt/format/json.d index 997b9e1..a48faed 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -326,7 +326,7 @@ Variant parse_node(ref const(char)[] text) r.flags = Variant.Flags.Map; return r; } - else if (text[0].isNumeric || (text[0] == '-' && text.length > 1 && text[1].isNumeric)) + else if (text[0].is_numeric || (text[0] == '-' && text.length > 1 && text[1].is_numeric)) { bool neg = text[0] == '-'; size_t taken = void; diff --git a/src/urt/inet.d b/src/urt/inet.d index 1c2e019..6dccd11 100644 --- a/src/urt/inet.d +++ b/src/urt/inet.d @@ -284,7 +284,7 @@ nothrow @nogc: if (buffer.length < offset) return -1; foreach (i, c; tmp[0 .. offset]) - buffer[i] = c.toLower; + buffer[i] = c.to_lower; } return offset; } diff --git a/src/urt/mem/alloc.d b/src/urt/mem/alloc.d index 5ec148a..a87c8dd 100644 --- a/src/urt/mem/alloc.d +++ b/src/urt/mem/alloc.d @@ -13,13 +13,13 @@ void[] alloc(size_t size) nothrow @nogc void[] alloc_aligned(size_t size, size_t alignment) nothrow @nogc { - import urt.util : isPowerOf2, max; + import urt.util : is_power_of_2, max; alignment = max(alignment, (void*).sizeof); - assert(isPowerOf2(alignment), "Alignment must be a power of two!"); + assert(is_power_of_2(alignment), "Alignment must be a power of two!"); version (Windows) { - import urt.util : alignDown; + import urt.util : align_down; // This is how Visual Studio's _aligned_malloc works... // see C:\Program Files (x86)\Windows Kits\10\Source\10.0.15063.0\ucrt\heap\align.cpp @@ -34,7 +34,7 @@ void[] alloc_aligned(size_t size, size_t alignment) nothrow @nogc return null; size_t ptr = cast(size_t)mem; - size_t allocptr = alignDown(ptr + header_size, alignment); + size_t allocptr = align_down(ptr + header_size, alignment); (cast(void**)allocptr)[-1] = mem; return (cast(void*)allocptr)[0 .. size]; @@ -62,10 +62,10 @@ void[] realloc(void[] mem, size_t newSize) nothrow @nogc void[] realloc_aligned(void[] mem, size_t newSize, size_t alignment) nothrow @nogc { - import urt.util : isPowerOf2, min, max; + import urt.util : is_power_of_2, min, max; alignment = max(alignment, (void*).sizeof); - assert(isPowerOf2(alignment), "Alignment must be a power of two!"); + assert(is_power_of_2(alignment), "Alignment must be a power of two!"); void[] newAlloc = newSize > 0 ? alloc_aligned(newSize, alignment) : null; if (newAlloc !is null && mem !is null) diff --git a/src/urt/mem/region.d b/src/urt/mem/region.d index 9a4a228..a657ace 100644 --- a/src/urt/mem/region.d +++ b/src/urt/mem/region.d @@ -6,7 +6,7 @@ import urt.util; static Region* makeRegion(void[] mem) pure nothrow @nogc { assert(mem.length >= Region.sizeof, "Memory block too small"); - Region* region = cast(Region*)mem.ptr.alignUp(Region.alignof); + Region* region = cast(Region*)mem.ptr.align_up(Region.alignof); size_t alignBytes = cast(void*)region - mem.ptr; if (size_t.sizeof > 4 && mem.length > uint.max + alignBytes + Region.sizeof) region.length = uint.max; @@ -21,7 +21,7 @@ struct Region void[] alloc(size_t size, size_t alignment = size_t.sizeof) pure nothrow @nogc { size_t ptr = cast(size_t)&this + Region.sizeof + offset; - size_t alignedPtr = ptr.alignUp(alignment); + size_t alignedPtr = ptr.align_up(alignment); size_t alignBytes = alignedPtr - ptr; if (offset + alignBytes + size > length) return null; diff --git a/src/urt/mem/scratchpad.d b/src/urt/mem/scratchpad.d index dcc681b..2a20c30 100644 --- a/src/urt/mem/scratchpad.d +++ b/src/urt/mem/scratchpad.d @@ -8,7 +8,7 @@ nothrow @nogc: enum size_t MaxScratchpadSize = 2048; enum size_t NumScratchBuffers = 4; -static assert(MaxScratchpadSize.isPowerOf2, "Scratchpad size must be a power of 2"); +static assert(MaxScratchpadSize.is_power_of_2, "Scratchpad size must be a power of 2"); void[] alloc_scratchpad(size_t size = MaxScratchpadSize) @@ -19,7 +19,7 @@ void[] alloc_scratchpad(size_t size = MaxScratchpadSize) return null; } - size = max(size.nextPowerOf2, WindowSize); + size = max(size.next_power_of_2, WindowSize); size_t maskBits = size / WindowSize; size_t mask = (1 << maskBits) - 1; diff --git a/src/urt/meta/nullable.d b/src/urt/meta/nullable.d index 3d981cc..98f29fd 100644 --- a/src/urt/meta/nullable.d +++ b/src/urt/meta/nullable.d @@ -9,8 +9,8 @@ template Nullable(T) { struct Nullable { - enum T NullValue = null; - T value = NullValue; + enum T null_value = null; + T value = null_value; this(T v) { @@ -18,7 +18,7 @@ template Nullable(T) } bool opCast(T : bool)() const - => value !is NullValue; + => value !is null_value; bool opEquals(typeof(null)) const => value is null; @@ -42,16 +42,16 @@ template Nullable(T) } template Nullable(T) - if (isBoolean!T) + if (is_boolean!T) { struct Nullable { - enum ubyte NullValue = 0xFF; - private ubyte _value = NullValue; + enum ubyte null_value = 0xFF; + private ubyte _value = null_value; this(typeof(null)) { - _value = NullValue; + _value = null_value; } this(T v) { @@ -62,27 +62,27 @@ template Nullable(T) => _value == 1; bool opCast(T : bool)() const - => _value != NullValue; + => _value != null_value; bool opEquals(typeof(null)) const - => _value == NullValue; + => _value == null_value; bool opEquals(T v) const => _value == cast(ubyte)v; void opAssign(typeof(null)) { - _value = NullValue; + _value = null_value; } void opAssign(U)(U v) if (is(U : T)) { - assert(v != NullValue); + assert(v != null_value); _value = cast(ubyte)v; } ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { - if (value == NullValue) + if (value == null_value) return formatValue(null, buffer, format, formatArgs); else return formatValue(value, buffer, format, formatArgs); @@ -91,16 +91,16 @@ template Nullable(T) } template Nullable(T) - if (isSomeInt!T) + if (is_some_int!T) { struct Nullable { - enum T NullValue = isSignedInt!T ? T.min : T.max; - T value = NullValue; + enum T null_value = is_signed_int!T ? T.min : T.max; + T value = null_value; this(typeof(null)) { - value = NullValue; + value = null_value; } this(T v) { @@ -108,27 +108,27 @@ template Nullable(T) } bool opCast(T : bool)() const - => value != NullValue; + => value != null_value; bool opEquals(typeof(null)) const - => value == NullValue; + => value == null_value; bool opEquals(T v) const - => value != NullValue && value == v; + => value != null_value && value == v; void opAssign(typeof(null)) { - value = NullValue; + value = null_value; } void opAssign(U)(U v) if (is(U : T)) { - assert(v != NullValue); + assert(v != null_value); value = v; } ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { - if (value == NullValue) + if (value == null_value) return formatValue(null, buffer, format, formatArgs); else return formatValue(value, buffer, format, formatArgs); @@ -137,16 +137,16 @@ template Nullable(T) } template Nullable(T) - if (isSomeFloat!T) + if (is_some_float!T) { struct Nullable { - enum T NullValue = T.nan; - T value = NullValue; + enum T null_value = T.nan; + T value = null_value; this(typeof(null)) { - value = NullValue; + value = null_value; } this(T v) { @@ -154,16 +154,16 @@ template Nullable(T) } bool opCast(T : bool)() const - => value !is NullValue; + => value !is null_value; bool opEquals(typeof(null)) const - => value is NullValue; + => value is null_value; bool opEquals(T v) const => value == v; // because nan doesn't compare with anything void opAssign(typeof(null)) { - value = NullValue; + value = null_value; } void opAssign(U)(U v) if (is(U : T)) @@ -173,7 +173,7 @@ template Nullable(T) ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { - if (value is NullValue) + if (value is null_value) return formatValue(null, buffer, format, formatArgs); else return formatValue(value, buffer, format, formatArgs); @@ -189,42 +189,42 @@ template Nullable(T) struct Nullable { T value = void; - bool isValue = false; + bool is_value = false; this(typeof(null)) { - isValue = false; + is_value = false; } this(T v) { moveEmplace(v, value); - isValue = true; + is_value = true; } ~this() { - if (isValue) + if (is_value) value.destroy(); } bool opCast(T : bool)() const - => isValue; + => is_value; bool opEquals(typeof(null)) const - => !isValue; + => !is_value; bool opEquals(T v) const - => isValue && value == v; + => is_value && value == v; void opAssign(typeof(null)) { - if (isValue) + if (is_value) value.destroy(); - isValue = false; + is_value = false; } void opAssign(U)(U v) if (is(U : T)) { - if (!isValue) + if (!is_value) moveEmplace(v, value); else value = v; @@ -232,7 +232,7 @@ template Nullable(T) ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { - if (!isValue) + if (!is_value) return formatValue(null, buffer, format, formatArgs); else return formatValue(value, buffer, format, formatArgs); @@ -246,39 +246,39 @@ template Nullable(T) struct Nullable { T value; - bool isValue; + bool is_value; this(typeof(null)) { - isValue = false; + is_value = false; } this(T v) { value = v; - isValue = true; + is_value = true; } bool opCast(T : bool)() const - => isValue; + => is_value; bool opEquals(typeof(null)) const - => !isValue; + => !is_value; bool opEquals(T v) const - => isValue && value == v; + => is_value && value == v; void opAssign(typeof(null)) { - isValue = false; + is_value = false; } void opAssign(T v) { value = v; - isValue = true; + is_value = true; } ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { - if (!isValue) + if (!is_value) return formatValue(null, buffer, format, formatArgs); else return formatValue(value, buffer, format, formatArgs); diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index bec2f81..e71470a 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -53,28 +53,28 @@ template static_index_of(args...) }(); } -template InterleaveSeparator(alias sep, Args...) +template INTERLEAVE_SEPARATOR(alias sep, Args...) { - alias InterleaveSeparator = AliasSeq!(); + alias INTERLEAVE_SEPARATOR = AliasSeq!(); static foreach (i, A; Args) static if (i > 0) - InterleaveSeparator = AliasSeq!(InterleaveSeparator, sep, A); + INTERLEAVE_SEPARATOR = AliasSeq!(INTERLEAVE_SEPARATOR, sep, A); else - InterleaveSeparator = AliasSeq!(A); + INTERLEAVE_SEPARATOR = AliasSeq!(A); } -template EnumKeys(E) +template enum_keys(E) { - static assert(is(E == enum), "EnumKeys only works with enums!"); - __gshared immutable string[EnumStrings.length] EnumKeys = [ EnumStrings ]; - private alias EnumStrings = __traits(allMembers, E); + static assert(is(E == enum), "enum_keys only works with enums!"); + __gshared immutable string[enum_strings.length] enum_keys = [ enum_strings ]; + private alias enum_strings = __traits(allMembers, E); } E enum_from_string(E)(const(char)[] key) if (is(E == enum)) { - foreach (i, k; EnumKeys!E) + foreach (i, k; enum_keys!E) if (key[] == k[]) return cast(E)i; return cast(E)-1; diff --git a/src/urt/range/package.d b/src/urt/range/package.d index 4d69c46..3592b11 100644 --- a/src/urt/range/package.d +++ b/src/urt/range/package.d @@ -8,13 +8,13 @@ template map(fun...) { /** Params: - r = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + r = an $(REF_ALTTEXT input range, is_input_range, std,range,primitives) Returns: A range with each fun applied to all the elements. If there is more than one fun, the element type will be `Tuple` containing one element for each fun. */ auto map(Range)(Range r) - if (isInputRange!(Unqual!Range)) + if (is_input_range!(Unqual!Range)) { import urt.meta : AliasSeq, STATIC_MAP; @@ -56,7 +56,7 @@ private struct MapResult(alias fun, Range) alias R = Unqual!Range; R _input; - static if (isBidirectionalRange!R) + static if (is_bidirectional_range!R) { @property auto ref back()() { @@ -101,7 +101,7 @@ private struct MapResult(alias fun, Range) return fun(_input.front); } - static if (isRandomAccessRange!R) + static if (is_random_access_range!R) { static if (is(typeof(Range.init[ulong.max]))) private alias opIndex_t = ulong; @@ -147,7 +147,7 @@ private struct MapResult(alias fun, Range) } } - static if (isForwardRange!R) + static if (is_forward_range!R) { @property auto save() { @@ -189,10 +189,10 @@ template reduce(fun...) if (isIterable!R) { import std.exception : enforce; - alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); + alias E = Select!(is_input_range!R, ElementType!R, ForeachType!R); alias Args = STATIC_MAP!(ReduceSeedType!E, binfuns); - static if (isInputRange!R) + static if (is_input_range!R) { // no need to throw if range is statically known to be non-empty static if (!__traits(compiles, @@ -259,7 +259,7 @@ template reduce(fun...) import std.algorithm.internal : algoFormat; static assert(Args.length == fun.length, algoFormat("Seed %s does not have the correct amount of fields (should be %s)", Args.stringof, fun.length)); - alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); + alias E = Select!(is_input_range!R, ElementType!R, ForeachType!R); static if (mustInitialize) bool initialized = false; @@ -309,7 +309,7 @@ template fold(fun...) { /** Params: - r = the $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to fold + r = the $(REF_ALTTEXT input range, is_input_range, std,range,primitives) to fold seeds = the initial values of each accumulator (optional), one for each predicate Returns: Either the accumulated result for a single predicate, or a diff --git a/src/urt/range/primitives.d b/src/urt/range/primitives.d index b9789cd..f5ddec8 100644 --- a/src/urt/range/primitives.d +++ b/src/urt/range/primitives.d @@ -3,44 +3,44 @@ module urt.range.primitives; import urt.traits; -enum bool isInputRange(R) = +enum bool is_input_range(R) = is(typeof(R.init) == R) && is(typeof((R r) { return r.empty; } (R.init)) == bool) && (is(typeof((return ref R r) => r.front)) || is(typeof(ref (return ref R r) => r.front))) && !is(typeof((R r) { return r.front; } (R.init)) == void) && is(typeof((R r) => r.popFront)); -enum bool isInputRange(R, E) = - .isInputRange!R && isQualifierConvertible!(ElementType!R, E); +enum bool is_input_range(R, E) = + .is_input_range!R && isQualifierConvertible!(ElementType!R, E); -enum bool isForwardRange(R) = isInputRange!R +enum bool is_forward_range(R) = is_input_range!R && is(typeof((R r) { return r.save; } (R.init)) == R); -enum bool isForwardRange(R, E) = - .isForwardRange!R && isQualifierConvertible!(ElementType!R, E); +enum bool is_forward_range(R, E) = + .is_forward_range!R && isQualifierConvertible!(ElementType!R, E); -enum bool isBidirectionalRange(R) = isForwardRange!R +enum bool is_bidirectional_range(R) = is_forward_range!R && is(typeof((R r) => r.popBack)) && (is(typeof((return ref R r) => r.back)) || is(typeof(ref (return ref R r) => r.back))) && is(typeof(R.init.back.init) == ElementType!R); -enum bool isBidirectionalRange(R, E) = - .isBidirectionalRange!R && isQualifierConvertible!(ElementType!R, E); +enum bool is_bidirectional_range(R, E) = + .is_bidirectional_range!R && isQualifierConvertible!(ElementType!R, E); -enum bool isRandomAccessRange(R) = - is(typeof(lvalueOf!R[1]) == ElementType!R) +enum bool is_random_access_range(R) = + is(typeof(lvalue_of!R[1]) == ElementType!R) && !(isAutodecodableString!R && !isAggregateType!R) - && isForwardRange!R - && (isBidirectionalRange!R || isInfinite!R) + && is_forward_range!R + && (is_bidirectional_range!R || isInfinite!R) && (hasLength!R || isInfinite!R) - && (isInfinite!R || !is(typeof(lvalueOf!R[$ - 1])) - || is(typeof(lvalueOf!R[$ - 1]) == ElementType!R)); -enum bool isRandomAccessRange(R, E) = - .isRandomAccessRange!R && isQualifierConvertible!(ElementType!R, E); + && (isInfinite!R || !is(typeof(lvalue_of!R[$ - 1])) + || is(typeof(lvalue_of!R[$ - 1]) == ElementType!R)); +enum bool is_random_access_range(R, E) = + .is_random_access_range!R && isQualifierConvertible!(ElementType!R, E); // is this in the wrong place? should this be a general traits for arrays and stuff too? template ElementType(R) { - static if (is(typeof(lvalueOf!R.front))) - alias ElementType = typeof(lvalueOf!R.front); + static if (is(typeof(lvalue_of!R.front))) + alias ElementType = typeof(lvalue_of!R.front); else static if (is(R : T[], T)) alias ElementType = T; else diff --git a/src/urt/string/ansi.d b/src/urt/string/ansi.d index e17b489..931d60d 100644 --- a/src/urt/string/ansi.d +++ b/src/urt/string/ansi.d @@ -70,14 +70,14 @@ nothrow @nogc: size_t parse_ansi_code(const(char)[] text) { - import urt.string.ascii : isNumeric; + import urt.string.ascii : is_numeric; if (text.length < 3 || text[0] != '\x1b') return 0; if (text[1] != '[' && text[1] != 'O') return 0; size_t i = 2; - for (; i < text.length && (text[i].isNumeric || text[i] == ';'); ++i) + for (; i < text.length && (text[i].is_numeric || text[i] == ';'); ++i) {} if (i == text.length) return 0; diff --git a/src/urt/string/ascii.d b/src/urt/string/ascii.d index 5541d3a..9edab53 100644 --- a/src/urt/string/ascii.d +++ b/src/urt/string/ascii.d @@ -2,14 +2,14 @@ module urt.string.ascii; -char[] toLower(const(char)[] str) pure nothrow +char[] to_lower(const(char)[] str) pure nothrow { - return toLower(str, new char[str.length]); + return to_lower(str, new char[str.length]); } -char[] toUpper(const(char)[] str) pure nothrow +char[] to_upper(const(char)[] str) pure nothrow { - return toUpper(str, new char[str.length]); + return to_upper(str, new char[str.length]); } @@ -17,7 +17,7 @@ nothrow @nogc: // some character category flags... // 1 = alpha, 2 = numeric, 4 = white, 8 = newline, 10 = control, 20 = ???, 40 = url, 80 = hex -__gshared immutable ubyte[128] charDetails = [ +private __gshared immutable ubyte[128] char_details = [ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x14, 0x18, 0x10, 0x10, 0x18, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, @@ -28,20 +28,20 @@ __gshared immutable ubyte[128] charDetails = [ 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x00, 0x40, 0x10 ]; -__gshared immutable char[16] hexDigits = "0123456789ABCDEF"; +__gshared immutable char[16] hex_digits = "0123456789ABCDEF"; -bool isSpace(char c) pure => c < 128 && (charDetails[c] & 4); -bool isNewline(char c) pure => c < 128 && (charDetails[c] & 8); -bool isWhitespace(char c) pure => c < 128 && (charDetails[c] & 0xC); -bool isAlpha(char c) pure => c < 128 && (charDetails[c] & 1); -bool isNumeric(char c) pure => cast(uint)(c - '0') <= 9; -bool isAlphaNumeric(char c) pure => c < 128 && (charDetails[c] & 3); -bool isHex(char c) pure => c < 128 && (charDetails[c] & 0x80); -bool isControlChar(char c) pure => c < 128 && (charDetails[c] & 0x10); -bool isURL(char c) pure => c < 128 && (charDetails[c] & 0x40); +bool is_space(char c) pure => c < 128 && (char_details[c] & 4); +bool is_newline(char c) pure => c < 128 && (char_details[c] & 8); +bool is_whitespace(char c) pure => c < 128 && (char_details[c] & 0xC); +bool is_alpha(char c) pure => c < 128 && (char_details[c] & 1); +bool is_numeric(char c) pure => cast(uint)(c - '0') <= 9; +bool is_alpha_numeric(char c) pure => c < 128 && (char_details[c] & 3); +bool is_hex(char c) pure => c < 128 && (char_details[c] & 0x80); +bool is_control_char(char c) pure => c < 128 && (char_details[c] & 0x10); +bool is_url(char c) pure => c < 128 && (char_details[c] & 0x40); -char toLower(char c) pure +char to_lower(char c) pure { // this is the typical way; which is faster on a weak arch? // if (c >= 'A' && c <= 'Z') @@ -53,7 +53,7 @@ char toLower(char c) pure return c; } -char toUpper(char c) pure +char to_upper(char c) pure { // this is the typical way; which is faster on a weak arch? // if (c >= 'a' && c <= 'z') @@ -65,26 +65,26 @@ char toUpper(char c) pure return c; } -char[] toLower(const(char)[] str, char[] buffer) pure +char[] to_lower(const(char)[] str, char[] buffer) pure { foreach (i; 0 .. str.length) - buffer[i] = toLower(str[i]); + buffer[i] = to_lower(str[i]); return buffer; } -char[] toUpper(const(char)[] str, char[] buffer) pure +char[] to_upper(const(char)[] str, char[] buffer) pure { foreach (i; 0 .. str.length) - buffer[i] = toUpper(str[i]); + buffer[i] = to_upper(str[i]); return buffer; } -char[] toLower(char[] str) pure +char[] to_lower(char[] str) pure { - return toLower(str, str); + return to_lower(str, str); } -char[] toUpper(char[] str) pure +char[] to_upper(char[] str) pure { - return toUpper(str, str); + return to_upper(str, str); } diff --git a/src/urt/string/format.d b/src/urt/string/format.d index 8175c42..410432e 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -291,17 +291,17 @@ struct DefFormat(T) // TODO: what formats are interesting for ints? bool leadingZeroes = false; - bool toLower = false; + bool to_lower = false; bool varLen = false; ptrdiff_t padding = 0; uint base = 10; static if (is(T == long)) { - bool showSign = false; + bool show_sign = false; if (format.length && format[0] == '+') { - showSign = true; + show_sign = true; format.popFront; } } @@ -315,7 +315,7 @@ struct DefFormat(T) varLen = true; format.popFront; } - if (format.length && format[0].isNumeric) + if (format.length && format[0].is_numeric) { bool success; padding = format.parse_int_fast(success); @@ -332,7 +332,7 @@ struct DefFormat(T) if (b == 'x') { base = 16; - toLower = format[0] == 'x' && buffer.ptr; + to_lower = format[0] == 'x' && buffer.ptr; } else if (b == 'b') base = 2; @@ -344,11 +344,11 @@ struct DefFormat(T) } static if (is(T == long)) - size_t len = format_int(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', showSign); + size_t len = format_int(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', show_sign); else size_t len = format_uint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); - if (toLower && len > 0) + if (to_lower && len > 0) { for (size_t i = 0; i < len; ++i) if (cast(uint)(buffer.ptr[i] - 'A') < 26) @@ -392,9 +392,9 @@ struct DefFormat(T) varLen = true; format.popFront; } - if (varLen && (!format.length || !format[0].isNumeric)) + if (varLen && (!format.length || !format[0].is_numeric)) return -2; - if (format.length && format[0].isNumeric) + if (format.length && format[0].is_numeric) { bool success; width = format.parse_int_fast(success); @@ -450,12 +450,12 @@ struct DefFormat(T) return 0; int grp1 = 1, grp2 = 0; - if (format.length && format[0].isNumeric) + if (format.length && format[0].is_numeric) { bool success; grp1 = cast(int)format.parse_int_fast(success); if (success && format.length > 0 && format[0] == ':' && - format.length > 1 && format[1].isNumeric) + format.length > 1 && format[1].is_numeric) { format.popFront(); grp2 = cast(int)format.parse_int_fast(success); @@ -634,7 +634,7 @@ struct DefFormat(T) static assert(false, "Not implemented for type: ", T.stringof); } - static if (isSomeInt!T || is(T == bool)) + static if (is_some_int!T || is(T == bool)) { ptrdiff_t toInt() const pure nothrow @nogc { @@ -922,9 +922,9 @@ unittest template allAreStrings(Args...) { static if (Args.length == 1) - enum allAreStrings = is(Args[0] : const(char[])) || is(isSomeChar!(Args[0])); + enum allAreStrings = is(Args[0] : const(char[])) || is(is_some_char!(Args[0])); else - enum allAreStrings = (is(Args[0] : const(char[])) || is(isSomeChar!(Args[0]))) && allAreStrings!(Args[1 .. $]); + enum allAreStrings = (is(Args[0] : const(char[])) || is(is_some_char!(Args[0]))) && allAreStrings!(Args[1 .. $]); } template allConstCorrectStrings(Args...) @@ -943,7 +943,7 @@ template constCorrectedStrings(Args...) { static if (is(Ty : const(char)[])) constCorrectedStrings = AliasSeq!(constCorrectedStrings, const(char[])); - else static if (isSomeChar!Ty) + else static if (is_some_char!Ty) constCorrectedStrings = AliasSeq!(constCorrectedStrings, const(char)); else static assert(false, "Argument must be a char array or a char: ", T); diff --git a/src/urt/string/package.d b/src/urt/string/package.d index c4abc6f..6a103d0 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -69,7 +69,7 @@ ptrdiff_t icmp(const(char)[] a, const(char)[] b) pure nothrow @nogc return a.length - b.length; for (size_t i = 0; i < a.length; ++i) { - ptrdiff_t diff = toLower(a[i]) - toLower(b[i]); + ptrdiff_t diff = to_lower(a[i]) - to_lower(b[i]); if (diff) return diff; } @@ -98,12 +98,12 @@ inout(char)[] trim(bool Front = true, bool Back = true)(inout(char)[] s) pure no size_t first = 0, last = s.length; static if (Front) { - while (first < s.length && isWhitespace(s.ptr[first])) + while (first < s.length && is_whitespace(s.ptr[first])) ++first; } static if (Back) { - while (last > first && isWhitespace(s.ptr[last - 1])) + while (last > first && is_whitespace(s.ptr[last - 1])) --last; } return s.ptr[first .. last]; @@ -274,9 +274,9 @@ char[] unEscape(char[] s) pure nothrow @nogc char[] toHexString(const(void[]) data, char[] buffer, uint group = 0, uint secondaryGroup = 0, const(char)[] seps = " -") pure nothrow @nogc { - import urt.util : isPowerOf2; - assert(group.isPowerOf2); - assert(secondaryGroup.isPowerOf2); + import urt.util : is_power_of_2; + assert(group.is_power_of_2); + assert(secondaryGroup.is_power_of_2); assert((secondaryGroup == 0 && seps.length > 0) || seps.length > 1, "Secondary grouping requires additional separator"); if (data.length == 0) @@ -296,8 +296,8 @@ char[] toHexString(const(void[]) data, char[] buffer, uint group = 0, uint secon size_t offset = 0; for (size_t i = 0; true; ) { - buffer[offset++] = hexDigits[src[i] >> 4]; - buffer[offset++] = hexDigits[src[i] & 0xF]; + buffer[offset++] = hex_digits[src[i] >> 4]; + buffer[offset++] = hex_digits[src[i] & 0xF]; bool sep = (i & mask) == mask; if (++i == data.length) diff --git a/src/urt/string/string.d b/src/urt/string/string.d index f2747fa..a6d73d4 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -626,7 +626,7 @@ nothrow @nogc: ref MutableString!Embed insert(Things...)(size_t offset, auto ref Things things) { import urt.string.format : _concat = concat; - import urt.util : max, nextPowerOf2; + import urt.util : max, next_power_of_2; char* oldPtr = ptr; size_t oldLen = length(); @@ -638,7 +638,7 @@ nothrow @nogc: debug assert(newLen <= MaxStringLen, "String too long"); size_t oldAlloc = allocated(); - ptr = newLen <= oldAlloc ? oldPtr : allocStringBuffer(max(16, cast(ushort)newLen + 4).nextPowerOf2 - 4); + ptr = newLen <= oldAlloc ? oldPtr : allocStringBuffer(max(16, cast(ushort)newLen + 4).next_power_of_2 - 4); memmove(ptr + offset + insertLen, oldPtr + offset, oldLen - offset); _concat(ptr[offset .. offset + insertLen], forward!things); writeLength(newLen); @@ -654,7 +654,7 @@ nothrow @nogc: ref MutableString!Embed insertFormat(Things...)(size_t offset, auto ref Things things) { import urt.string.format : _format = format; - import urt.util : max, nextPowerOf2; + import urt.util : max, next_power_of_2; char* oldPtr = ptr; size_t oldLen = length(); @@ -666,7 +666,7 @@ nothrow @nogc: debug assert(newLen <= MaxStringLen, "String too long"); size_t oldAlloc = allocated(); - ptr = newLen <= oldAlloc ? oldPtr : allocStringBuffer(max(16, cast(ushort)newLen + 4).nextPowerOf2 - 4); + ptr = newLen <= oldAlloc ? oldPtr : allocStringBuffer(max(16, cast(ushort)newLen + 4).next_power_of_2 - 4); memmove(ptr + offset + insertLen, oldPtr + offset, oldLen - offset); _format(ptr[offset .. offset + insertLen], forward!things); writeLength(newLen); diff --git a/src/urt/string/uni.d b/src/urt/string/uni.d index ee9b551..bc3e44c 100644 --- a/src/urt/string/uni.d +++ b/src/urt/string/uni.d @@ -3,16 +3,16 @@ module urt.string.uni; nothrow @nogc: -size_t uniConvert(const(char)[] s, wchar[] buffer) +size_t uni_convert(const(char)[] s, wchar[] buffer) { const(char)* p = s.ptr; const(char)* pend = p + s.length; wchar* b = buffer.ptr; - wchar* bEnd = buffer.ptr + buffer.length; + wchar* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; // End of output buffer if ((*p & 0x80) == 0) // 1-byte sequence: 0xxxxxxx *b++ = *p++; @@ -32,7 +32,7 @@ size_t uniConvert(const(char)[] s, wchar[] buffer) } else if ((*p & 0xF8) == 0xF0) // 4-byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx { - if (p + 3 >= pend || b + 1 >= bEnd) + if (p + 3 >= pend || b + 1 >= bend) return 0; // Unexpected end of input/output dchar codepoint = ((p[0] & 0x07) << 18) | ((p[1] & 0x3F) << 12) | ((p[2] & 0x3F) << 6) | (p[3] & 0x3F); codepoint -= 0x10000; @@ -47,16 +47,16 @@ size_t uniConvert(const(char)[] s, wchar[] buffer) return b - buffer.ptr; } -size_t uniConvert(const(char)[] s, dchar[] buffer) +size_t uni_convert(const(char)[] s, dchar[] buffer) { const(char)* p = s.ptr; const(char)* pend = p + s.length; dchar* b = buffer.ptr; - dchar* bEnd = buffer.ptr + buffer.length; + dchar* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; if ((*p & 0x80) == 0) // 1-byte sequence: 0xxxxxxx *b++ = *p++; @@ -84,16 +84,16 @@ size_t uniConvert(const(char)[] s, dchar[] buffer) return b - buffer.ptr; } -size_t uniConvert(const(wchar)[] s, char[] buffer) +size_t uni_convert(const(wchar)[] s, char[] buffer) { const(wchar)* p = s.ptr; const(wchar)* pend = p + s.length; char* b = buffer.ptr; - char* bEnd = buffer.ptr + buffer.length; + char* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; // End of output buffer if (p[0] >= 0xD800) { @@ -103,7 +103,7 @@ size_t uniConvert(const(wchar)[] s, char[] buffer) return 0; // Unexpected end of input if (p[0] < 0xDC00) // Surrogate pair: 110110xxxxxxxxxx 110111xxxxxxxxxx { - if (b + 3 >= bEnd) + if (b + 3 >= bend) return 0; // End of output buffer dchar codepoint = 0x10000 + ((p[0] - 0xD800) << 10) + (p[1] - 0xDC00); b[0] = 0xF0 | (codepoint >> 18); @@ -120,7 +120,7 @@ size_t uniConvert(const(wchar)[] s, char[] buffer) *b++ = cast(char)*p++; else if (*p < 0x800) // 2-byte sequence: 110xxxxx 10xxxxxx { - if (b + 1 >= bEnd) + if (b + 1 >= bend) return 0; // End of output buffer b[0] = 0xC0 | cast(char)(*p >> 6); b[1] = 0x80 | (*p++ & 0x3F); @@ -129,7 +129,7 @@ size_t uniConvert(const(wchar)[] s, char[] buffer) else // 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx { three_byte_seq: - if (b + 2 >= bEnd) + if (b + 2 >= bend) return 0; // End of output buffer b[0] = 0xE0 | (*p >> 12); b[1] = 0x80 | ((*p >> 6) & 0x3F); @@ -140,16 +140,16 @@ size_t uniConvert(const(wchar)[] s, char[] buffer) return b - buffer.ptr; } -size_t uniConvert(const(wchar)[] s, dchar[] buffer) +size_t uni_convert(const(wchar)[] s, dchar[] buffer) { const(wchar)* p = s.ptr; const(wchar)* pend = p + s.length; dchar* b = buffer.ptr; - dchar* bEnd = buffer.ptr + buffer.length; + dchar* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; // End of output buffer if (p[0] >= 0xD800 && p[0] < 0xE000) { @@ -168,22 +168,22 @@ size_t uniConvert(const(wchar)[] s, dchar[] buffer) return b - buffer.ptr; } -size_t uniConvert(const(dchar)[] s, char[] buffer) +size_t uni_convert(const(dchar)[] s, char[] buffer) { const(dchar)* p = s.ptr; const(dchar)* pend = p + s.length; char* b = buffer.ptr; - char* bEnd = buffer.ptr + buffer.length; + char* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; // End of output buffer if (*p < 0x80) // 1-byte sequence: 0xxxxxxx *b++ = cast(char)*p++; else if (*p < 0x800) // 2-byte sequence: 110xxxxx 10xxxxxx { - if (b + 1 >= bEnd) + if (b + 1 >= bend) return 0; // End of output buffer b[0] = 0xC0 | cast(char)(*p >> 6); b[1] = 0x80 | (*p++ & 0x3F); @@ -191,7 +191,7 @@ size_t uniConvert(const(dchar)[] s, char[] buffer) } else if (*p < 0x10000) // 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx { - if (b + 2 >= bEnd) + if (b + 2 >= bend) return 0; // End of output buffer b[0] = 0xE0 | cast(char)(*p >> 12); b[1] = 0x80 | ((*p >> 6) & 0x3F); @@ -200,7 +200,7 @@ size_t uniConvert(const(dchar)[] s, char[] buffer) } else if (*p < 0x110000) // 4-byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx { - if (b + 3 >= bEnd) + if (b + 3 >= bend) return 0; // End of output buffer b[0] = 0xF0 | (*p >> 18); b[1] = 0x80 | ((*p >> 12) & 0x3F); @@ -214,22 +214,22 @@ size_t uniConvert(const(dchar)[] s, char[] buffer) return b - buffer.ptr; } -size_t uniConvert(const(dchar)[] s, wchar[] buffer) +size_t uni_convert(const(dchar)[] s, wchar[] buffer) { const(dchar)* p = s.ptr; const(dchar)* pend = p + s.length; wchar* b = buffer.ptr; - wchar* bEnd = buffer.ptr + buffer.length; + wchar* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; // End of output buffer if (*p < 0x10000) *b++ = cast(wchar)*p++; else if (*p < 0x110000) { - if (b + 1 >= bEnd) + if (b + 1 >= bend) return 0; // End of output buffer dchar codepoint = *p++ - 0x10000; b[0] = 0xD800 | (codepoint >> 10); @@ -244,7 +244,7 @@ size_t uniConvert(const(dchar)[] s, wchar[] buffer) unittest { - immutable dstring unicodeTest = + immutable dstring unicode_test = "Basic ASCII: Hello, World!\n" ~ "BMP Examples: 你好, مرحبا, שלום, 😊, ☂️\n" ~ "Supplementary Planes: 𐍈, 𝒜, 🀄, 🚀\n" ~ @@ -257,18 +257,18 @@ unittest "U+E000–U+FFFF Range:  (U+E000), 豈 (U+F900)" ~ "Control Characters: \u0008 \u001B \u0000\n"; - char[1024] utf8Buffer; - wchar[512] utf16Buffer; - dchar[512] utf32Buffer; + char[1024] utf8_buffer; + wchar[512] utf16_buffer; + dchar[512] utf32_buffer; // test all conversions with characters in every significant value range - size_t utf8Len = uniConvert(unicodeTest, utf8Buffer); // D-C - size_t utf16Len = uniConvert(utf8Buffer[0..utf8Len], utf16Buffer); // C-W - size_t utf32Len = uniConvert(utf16Buffer[0..utf16Len], utf32Buffer); // W-D - utf16Len = uniConvert(utf32Buffer[0..utf32Len], utf16Buffer); // D-W - utf8Len = uniConvert(utf16Buffer[0..utf16Len], utf8Buffer); // W-C - utf32Len = uniConvert(utf8Buffer[0..utf8Len], utf32Buffer); // C-D - assert(unicodeTest[] == utf32Buffer[0..utf32Len]); + size_t utf8_len = uni_convert(unicode_test, utf8_buffer); // D-C + size_t utf16_len = uni_convert(utf8_buffer[0..utf8_len], utf16_buffer); // C-W + size_t utf32_len = uni_convert(utf16_buffer[0..utf16_len], utf32_buffer); // W-D + utf16_len = uni_convert(utf32_buffer[0..utf32_len], utf16_buffer); // D-W + utf8_len = uni_convert(utf16_buffer[0..utf16_len], utf8_buffer); // W-C + utf32_len = uni_convert(utf8_buffer[0..utf8_len], utf32_buffer); // C-D + assert(unicode_test[] == utf32_buffer[0..utf32_len]); // TODO: test all the error cases; invalid characters, buffer overflows, truncated inputs, etc... //... diff --git a/src/urt/time.d b/src/urt/time.d index 33a7693..901884a 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -1,6 +1,6 @@ module urt.time; -import urt.traits : isSomeFloat; +import urt.traits : is_some_float; version (Windows) { @@ -137,7 +137,7 @@ pure nothrow @nogc: bool opCast(T : bool)() const => ticks != 0; - T opCast(T)() const if (isSomeFloat!T) + T opCast(T)() const if (is_some_float!T) => cast(T)ticks / cast(T)ticksPerSecond; bool opEquals(Duration b) const diff --git a/src/urt/traits.d b/src/urt/traits.d index 30ef675..bfa8073 100644 --- a/src/urt/traits.d +++ b/src/urt/traits.d @@ -3,67 +3,67 @@ module urt.traits; import urt.meta; -enum bool isType(alias X) = is(X); +enum bool is_type(alias X) = is(X); -enum bool isBoolean(T) = __traits(isUnsigned, T) && is(T : bool); +enum bool is_boolean(T) = __traits(isUnsigned, T) && is(T : bool); -enum bool isUnsignedInt(T) = is(Unqual!T == ubyte) || is(Unqual!T == ushort) || is(Unqual!T == uint) || is(Unqual!T == ulong); -enum bool isSignedInt(T) = is(Unqual!T == byte) || is(Unqual!T == short) || is(Unqual!T == int) || is(Unqual!T == long); -enum bool isSomeInt(T) = isUnsignedInt!T || isSignedInt!T; -enum bool isUnsignedIntegral(T) = is(Unqual!T == bool) || isUnsignedInt!T || isSomeChar!T; -enum bool isSignedIntegral(T) = isSignedInt!T; -enum bool isIntegral(T) = isUnsignedIntegral!T || isSignedIntegral!T; -enum bool isSomeFloat(T) = is(Unqual!T == float) || is(Unqual!T == double) || is(Unqual!T == real); +enum bool is_unsigned_int(T) = is(Unqual!T == ubyte) || is(Unqual!T == ushort) || is(Unqual!T == uint) || is(Unqual!T == ulong); +enum bool is_signed_int(T) = is(Unqual!T == byte) || is(Unqual!T == short) || is(Unqual!T == int) || is(Unqual!T == long); +enum bool is_some_int(T) = is_unsigned_int!T || is_signed_int!T; +enum bool is_unsigned_integral(T) = is(Unqual!T == bool) || is_unsigned_int!T || is_some_char!T; +enum bool is_signed_integral(T) = is_signed_int!T; +enum bool is_integral(T) = is_unsigned_integral!T || is_signed_integral!T; +enum bool is_some_float(T) = is(Unqual!T == float) || is(Unqual!T == double) || is(Unqual!T == real); -enum bool isEnum(T) = is(T == enum); -template enumType(T) - if (isEnum!T) +enum bool is_enum(T) = is(T == enum); +template EnumType(T) + if (is_enum!T) { static if (is(T E == enum)) - alias enumType = E; + alias EnumType = E; else static assert(false, "How this?"); } -template isUnsigned(T) +template is_unsigned(T) { static if (!__traits(isUnsigned, T)) - enum isUnsigned = false; + enum is_unsigned = false; else static if (is(T U == enum)) - enum isUnsigned = isUnsigned!U; + enum is_unsigned = is_unsigned!U; else - enum isUnsigned = __traits(isZeroInit, T) // Not char, wchar, or dchar. + enum is_unsigned = __traits(isZeroInit, T) // Not char, wchar, or dchar. && !is(immutable T == immutable bool) && !is(T == __vector); } -enum bool isSigned(T) = __traits(isArithmetic, T) && !__traits(isUnsigned, T) && is(T : real); +enum bool is_signed(T) = __traits(isArithmetic, T) && !__traits(isUnsigned, T) && is(T : real); -template isSomeChar(T) +template is_some_char(T) { static if (!__traits(isUnsigned, T)) - enum isSomeChar = false; + enum is_some_char = false; else static if (is(T U == enum)) - enum isSomeChar = isSomeChar!U; + enum is_some_char = is_some_char!U; else - enum isSomeChar = !__traits(isZeroInit, T); + enum is_some_char = !__traits(isZeroInit, T); } -enum bool isSomeFunction(alias T) = is(T == return) || is(typeof(T) == return) || is(typeof(&T) == return); -enum bool isFunctionPointer(alias T) = is(typeof(*T) == function); -enum bool isDelegate(alias T) = is(typeof(T) == delegate) || is(T == delegate); +enum bool is_some_function(alias T) = is(T == return) || is(typeof(T) == return) || is(typeof(&T) == return); +enum bool is_function_pointer(alias T) = is(typeof(*T) == function); +enum bool is_delegate(alias T) = is(typeof(T) == delegate) || is(T == delegate); -template isCallable(alias callable) +template is_callable(alias callable) { static if (is(typeof(&callable.opCall) == delegate)) - enum bool isCallable = true; + enum bool is_callable = true; else static if (is(typeof(&callable.opCall) V : V*) && is(V == function)) - enum bool isCallable = true; + enum bool is_callable = true; else static if (is(typeof(&callable.opCall!()) TemplateInstanceType)) - enum bool isCallable = isCallable!TemplateInstanceType; + enum bool is_callable = is_callable!TemplateInstanceType; else static if (is(typeof(&callable!()) TemplateInstanceType)) - enum bool isCallable = isCallable!TemplateInstanceType; + enum bool is_callable = is_callable!TemplateInstanceType; else - enum bool isCallable = isSomeFunction!callable; + enum bool is_callable = is_some_function!callable; } @@ -79,7 +79,7 @@ template Unqual(T : const U, U) template Unsigned(T) { - static if (isUnsigned!T) + static if (is_unsigned!T) alias Unsigned = T; else static if (is(T == long)) alias Unsigned = ulong; @@ -113,7 +113,7 @@ template Unsigned(T) template Signed(T) { - static if (isSigned!T) + static if (is_signed!T) alias Unsigned = T; else static if (is(T == ulong)) alias Signed = long; @@ -144,7 +144,7 @@ template Signed(T) } template ReturnType(alias func) - if (isCallable!func) + if (is_callable!func) { static if (is(FunctionTypeOf!func R == return)) alias ReturnType = R; @@ -153,7 +153,7 @@ template ReturnType(alias func) } template Parameters(alias func) - if (isCallable!func) + if (is_callable!func) { static if (is(FunctionTypeOf!func P == function)) alias Parameters = P; @@ -161,26 +161,26 @@ template Parameters(alias func) static assert(0, "argument has no parameters"); } -template ParameterIdentifierTuple(alias func) - if (isCallable!func) +template parameter_identifier_tuple(alias func) + if (is_callable!func) { static if (is(FunctionTypeOf!func PT == __parameters)) { - alias ParameterIdentifierTuple = AliasSeq!(); + alias parameter_identifier_tuple = AliasSeq!(); static foreach (i; 0 .. PT.length) { - static if (!isFunctionPointer!func && !isDelegate!func + static if (!is_function_pointer!func && !is_delegate!func // Unnamed parameters yield CT error. && is(typeof(__traits(identifier, PT[i .. i+1]))) // Filter out unnamed args, which look like (Type) instead of (Type name). && PT[i].stringof != PT[i .. i+1].stringof[1..$-1]) { - ParameterIdentifierTuple = AliasSeq!(ParameterIdentifierTuple, + parameter_identifier_tuple = AliasSeq!(parameter_identifier_tuple, __traits(identifier, PT[i .. i+1])); } else { - ParameterIdentifierTuple = AliasSeq!(ParameterIdentifierTuple, ""); + parameter_identifier_tuple = AliasSeq!(parameter_identifier_tuple, ""); } } } @@ -188,12 +188,12 @@ template ParameterIdentifierTuple(alias func) { static assert(0, func.stringof ~ " is not a function"); // avoid pointless errors - alias ParameterIdentifierTuple = AliasSeq!(); + alias parameter_identifier_tuple = AliasSeq!(); } } template FunctionTypeOf(alias func) - if (isCallable!func) + if (is_callable!func) { static if ((is(typeof(& func) Fsym : Fsym*) && is(Fsym == function)) || is(typeof(& func) Fsym == delegate)) alias FunctionTypeOf = Fsym; // HIT: (nested) function symbol @@ -218,27 +218,27 @@ template FunctionTypeOf(alias func) } // is T a primitive/builtin type? -enum isPrimitive(T) = isIntegral!T || isSomeFloat!T || (isEnum!T && isPrimitive!(enumType!T) || - is(T == P*, P) || is(T == S[], S) || (is(T == A[N], A, size_t N) && isPrimitive!A) || +enum is_primitive(T) = is_integral!T || is_some_float!T || (is_enum!T && is_primitive!(EnumType!T) || + is(T == P*, P) || is(T == S[], S) || (is(T == A[N], A, size_t N) && is_primitive!A) || is(T == R function(Args), R, Args...) || is(T == R delegate(Args), R, Args...)); -enum isDefaultConstructible(T) = isPrimitive!T || (is(T == struct) && __traits(compiles, { T t; })); +enum is_default_constructible(T) = is_primitive!T || (is(T == struct) && __traits(compiles, { T t; })); -enum isConstructible(T, Args...) = (isPrimitive!T && (Args.length == 0 || (Args.length == 1 && is(Args[0] : T)))) || +enum is_constructible(T, Args...) = (is_primitive!T && (Args.length == 0 || (Args.length == 1 && is(Args[0] : T)))) || (is(T == struct) && __traits(compiles, (Args args) { T x = T(args); })); // this probably fails if the struct can't be assigned to x... TODO: use placement new? // TODO: we need to know it's not calling an elaborate constructor... -//enum isTriviallyConstructible(T, Args...) = (isPrimitive!T && (Args.length == 0 || (Args.length == 1 && is(Args[0] : T)))) || -// (is(T == struct) && __traits(compiles, (Args args) { auto x = T(args); })); // this probably fails if the struct can't be assigned to x... TODO: use placement new? +//enum is_trivially_constructible(T, Args...) = (is_primitive!T && (Args.length == 0 || (Args.length == 1 && is(Args[0] : T)))) || +// (is(T == struct) && __traits(compiles, (Args args) { auto x = T(args); })); // this probably fails if the struct can't be assigned to x... TODO: use placement new? -//enum isCopyConstructible(T) = isPrimitive!T || (is(T == struct) && __traits(compiles, { T u = lvalueOf!T; })); -//enum isMoveConstructible(T) = isPrimitive!T || (is(T == struct) && __traits(compiles, { T u = rvalueOf!T; })); +//enum is_copy_constructible(T) = is_primitive!T || (is(T == struct) && __traits(compiles, { T u = lvalue_of!T; })); +//enum is_move_constructible(T) = is_primitive!T || (is(T == struct) && __traits(compiles, { T u = rvalue_of!T; })); -enum isTriviallyDefaultConstructible(T) = isDefaultConstructible!T; // dlang doesn't have elaborate default constructors (YET...) -//enum isTriviallyCopyConstructible(T) = isPrimitive!T; // TODO: somehow find out if there is no copy constructor -//enum isTriviallyMoveConstructible(T) = isPrimitive!T || is(T == struct); // TODO: somehow find out if there is no move constructor +enum is_trivially_default_constructible(T) = is_default_constructible!T; // dlang doesn't have elaborate default constructors (YET...) +//enum is_trivially_copy_constructible(T) = is_primitive!T; // TODO: somehow find out if there is no copy constructor +//enum is_trivially_move_constructible(T) = is_primitive!T || is(T == struct); // TODO: somehow find out if there is no move constructor // helpers to test certain expressions private struct __InoutWorkaroundStruct{} -@property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init) pure nothrow @nogc; -@property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init) pure nothrow @nogc; +@property T rvalue_of(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init) pure nothrow @nogc; +@property ref T lvalue_of(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init) pure nothrow @nogc; diff --git a/src/urt/util.d b/src/urt/util.d index 6eb457d..fb8fe2d 100644 --- a/src/urt/util.d +++ b/src/urt/util.d @@ -40,23 +40,23 @@ auto max(T, U)(auto ref inout T a, auto ref inout U b) template Align(size_t value, size_t alignment = size_t.sizeof) { - static assert(isPowerOf2(alignment), "Alignment must be a power of two: ", alignment); + static assert(is_power_of_2(alignment), "Alignment must be a power of two: ", alignment); enum Align = alignTo(value, alignment); } -enum IsAligned(size_t value) = isAligned(value); -enum IsPowerOf2(size_t value) = isPowerOf2(value); -enum NextPowerOf2(size_t value) = nextPowerOf2(value); +enum IsAligned(size_t value) = is_aligned(value); +enum IsPowerOf2(size_t value) = is_power_of_2(value); +enum NextPowerOf2(size_t value) = next_power_of_2(value); -bool isPowerOf2(T)(T x) - if (isSomeInt!T) +bool is_power_of_2(T)(T x) + if (is_some_int!T) { return (x & (x - 1)) == 0; } -T nextPowerOf2(T)(T x) - if (isSomeInt!T) +T next_power_of_2(T)(T x) + if (is_some_int!T) { x -= 1; x |= x >> 1; @@ -71,40 +71,40 @@ T nextPowerOf2(T)(T x) return cast(T)(x + 1); } -T alignDown(size_t alignment, T)(T value) - if (isSomeInt!T || is(T == U*, U)) +T align_down(size_t alignment, T)(T value) + if (is_some_int!T || is(T == U*, U)) { return cast(T)(cast(size_t)value & ~(alignment - 1)); } -T alignDown(T)(T value, size_t alignment) - if (isSomeInt!T || is(T == U*, U)) +T align_down(T)(T value, size_t alignment) + if (is_some_int!T || is(T == U*, U)) { return cast(T)(cast(size_t)value & ~(alignment - 1)); } -T alignUp(size_t alignment, T)(T value) - if (isSomeInt!T || is(T == U*, U)) +T align_up(size_t alignment, T)(T value) + if (is_some_int!T || is(T == U*, U)) { return cast(T)((cast(size_t)value + (alignment - 1)) & ~(alignment - 1)); } -T alignUp(T)(T value, size_t alignment) - if (isSomeInt!T || is(T == U*, U)) +T align_up(T)(T value, size_t alignment) + if (is_some_int!T || is(T == U*, U)) { return cast(T)((cast(size_t)value + (alignment - 1)) & ~(alignment - 1)); } -bool isAligned(size_t alignment, T)(T value) - if (isSomeInt!T || is(T == U*, U)) +bool is_aligned(size_t alignment, T)(T value) + if (is_some_int!T || is(T == U*, U)) { static assert(IsPowerOf2!alignment, "Alignment must be a power of two"); static assert(T.sizeof <= size_t.sizeof, "TODO"); return (cast(size_t)value & (alignment - 1)) == 0; } -bool isAligned(T)(T value, size_t alignment) - if (isSomeInt!T || is(T == U*, U)) +bool is_aligned(T)(T value, size_t alignment) + if (is_some_int!T || is(T == U*, U)) { static assert(T.sizeof <= size_t.sizeof, "TODO"); return (cast(size_t)value & (alignment - 1)) == 0; @@ -142,7 +142,7 @@ ubyte log2(ubyte val) } ubyte log2(T)(T val) - if (isSomeInt!T && T.sizeof > 1) + if (is_some_int!T && T.sizeof > 1) { if (T.sizeof > 4 && val >> 32) { @@ -174,7 +174,7 @@ ubyte log2(T)(T val) +/ ubyte log2(T)(T x) - if (isIntegral!T) + if (is_integral!T) { ubyte result = 0; static if (T.sizeof > 4) @@ -203,7 +203,7 @@ ubyte log2(T)(T x) } ubyte clz(bool nonZero = false, T)(T x) - if (isIntegral!T) + if (is_integral!T) { static if (nonZero) debug assert(x != 0); @@ -272,7 +272,7 @@ ubyte clz(T : bool)(T x) => x ? 7 : 8; ubyte ctz(bool nonZero = false, T)(T x) - if (isIntegral!T) + if (is_integral!T) { static if (nonZero) debug assert(x != 0); @@ -380,7 +380,7 @@ ubyte ctz(T : bool)(T x) => x ? 0 : 8; ubyte popcnt(T)(T x) - if (isIntegral!T) + if (is_integral!T) { if (__ctfe || !IS_LDC_OR_GDC) { @@ -412,9 +412,9 @@ ubyte popcnt(T)(T x) ubyte popcnt(T : bool)(T x) => x ? 1 : 0; -ubyte byteReverse(ubyte v) +ubyte byte_reverse(ubyte v) => v; -ushort byteReverse(ushort v) +ushort byte_reverse(ushort v) { if (__ctfe || !IS_LDC_OR_GDC) return cast(ushort)((v << 8) | (v >> 8)); @@ -423,7 +423,7 @@ ushort byteReverse(ushort v) else assert(false, "Unreachable"); } -uint byteReverse(uint v) +uint byte_reverse(uint v) { if (__ctfe || !IS_LDC_OR_GDC) return cast(uint)((v << 24) | ((v & 0xFF00) << 8) | ((v >> 8) & 0xFF00) | (v >> 24)); @@ -432,7 +432,7 @@ uint byteReverse(uint v) else assert(false, "Unreachable"); } -ulong byteReverse(ulong v) +ulong byte_reverse(ulong v) { if (__ctfe || !IS_LDC_OR_GDC) return cast(ulong)((v << 56) | ((v & 0xFF00) << 40) | ((v & 0xFF0000) << 24) | ((v & 0xFF000000) << 8) | ((v >> 8) & 0xFF000000) | ((v >> 24) & 0xFF0000) | ((v >> 40) & 0xFF00) | (v >> 56)); @@ -441,17 +441,17 @@ ulong byteReverse(ulong v) else assert(false, "Unreachable"); } -pragma(inline, true) T byteReverse(T)(T val) - if (!isIntegral!T) +pragma(inline, true) T byte_reverse(T)(T val) + if (!is_integral!T) { import urt.meta : IntForWidth; alias U = IntForWidth!(T.sizeof*8); - U r = byteReverse(*cast(U*)&val); + U r = byte_reverse(*cast(U*)&val); return *cast(T*)&r; } -T bitReverse(T)(T x) - if (isSomeInt!T) +T bit_reverse(T)(T x) + if (is_some_int!T) { if (__ctfe || !IS_LDC) { @@ -478,7 +478,7 @@ T bitReverse(T)(T x) static if (T.sizeof == 1) return x; else - return byteReverse(x); + return byte_reverse(x); } } else @@ -533,39 +533,39 @@ unittest assert(x.swap(y) == 10); assert(x.swap(30) == 20); - static assert(isPowerOf2(0) == true); - static assert(isPowerOf2(1) == true); - static assert(isPowerOf2(2) == true); - static assert(isPowerOf2(3) == false); - static assert(isPowerOf2(4) == true); - static assert(isPowerOf2(5) == false); - static assert(isPowerOf2(ulong(uint.max) + 1) == true); - static assert(isPowerOf2(ulong.max) == false); - assert(isPowerOf2(0) == true); - assert(isPowerOf2(1) == true); - assert(isPowerOf2(2) == true); - assert(isPowerOf2(3) == false); - assert(isPowerOf2(4) == true); - assert(isPowerOf2(5) == false); - assert(isPowerOf2(ulong(uint.max) + 1) == true); - assert(isPowerOf2(ulong.max) == false); - - static assert(nextPowerOf2(0) == 0); - static assert(nextPowerOf2(1) == 1); - static assert(nextPowerOf2(2) == 2); - static assert(nextPowerOf2(3) == 4); - static assert(nextPowerOf2(4) == 4); - static assert(nextPowerOf2(5) == 8); - static assert(nextPowerOf2(uint.max) == 0); - static assert(nextPowerOf2(ulong(uint.max)) == ulong(uint.max) + 1); - assert(nextPowerOf2(0) == 0); - assert(nextPowerOf2(1) == 1); - assert(nextPowerOf2(2) == 2); - assert(nextPowerOf2(3) == 4); - assert(nextPowerOf2(4) == 4); - assert(nextPowerOf2(5) == 8); - assert(nextPowerOf2(uint.max) == 0); - assert(nextPowerOf2(ulong(uint.max)) == ulong(uint.max) + 1); + static assert(is_power_of_2(0) == true); + static assert(is_power_of_2(1) == true); + static assert(is_power_of_2(2) == true); + static assert(is_power_of_2(3) == false); + static assert(is_power_of_2(4) == true); + static assert(is_power_of_2(5) == false); + static assert(is_power_of_2(ulong(uint.max) + 1) == true); + static assert(is_power_of_2(ulong.max) == false); + assert(is_power_of_2(0) == true); + assert(is_power_of_2(1) == true); + assert(is_power_of_2(2) == true); + assert(is_power_of_2(3) == false); + assert(is_power_of_2(4) == true); + assert(is_power_of_2(5) == false); + assert(is_power_of_2(ulong(uint.max) + 1) == true); + assert(is_power_of_2(ulong.max) == false); + + static assert(next_power_of_2(0) == 0); + static assert(next_power_of_2(1) == 1); + static assert(next_power_of_2(2) == 2); + static assert(next_power_of_2(3) == 4); + static assert(next_power_of_2(4) == 4); + static assert(next_power_of_2(5) == 8); + static assert(next_power_of_2(uint.max) == 0); + static assert(next_power_of_2(ulong(uint.max)) == ulong(uint.max) + 1); + assert(next_power_of_2(0) == 0); + assert(next_power_of_2(1) == 1); + assert(next_power_of_2(2) == 2); + assert(next_power_of_2(3) == 4); + assert(next_power_of_2(4) == 4); + assert(next_power_of_2(5) == 8); + assert(next_power_of_2(uint.max) == 0); + assert(next_power_of_2(ulong(uint.max)) == ulong(uint.max) + 1); static assert(log2(ubyte(0)) == 0); static assert(log2(ubyte(1)) == 0); @@ -679,50 +679,50 @@ unittest assert(popcnt(true) == 1); assert(popcnt('D') == 2); // 0x44 - static assert(bitReverse(ubyte(0)) == 0); - static assert(bitReverse(ubyte(1)) == 128); - static assert(bitReverse(ubyte(2)) == 64); - static assert(bitReverse(ubyte(3)) == 192); - static assert(bitReverse(ubyte(4)) == 32); - static assert(bitReverse(ubyte(5)) == 160); - static assert(bitReverse(ubyte(6)) == 96); - static assert(bitReverse(ubyte(7)) == 224); - static assert(bitReverse(ubyte(8)) == 16); - static assert(bitReverse(ubyte(255)) == 255); - static assert(bitReverse(ushort(0b1101100010000000)) == 0b0000000100011011); - static assert(bitReverse(uint(0x73810000)) == 0x000081CE); - static assert(bitReverse(ulong(0x7381000000000000)) == 0x00000000000081CE); - assert(bitReverse(ubyte(0)) == 0); - assert(bitReverse(ubyte(1)) == 128); - assert(bitReverse(ubyte(2)) == 64); - assert(bitReverse(ubyte(3)) == 192); - assert(bitReverse(ubyte(4)) == 32); - assert(bitReverse(ubyte(5)) == 160); - assert(bitReverse(ubyte(6)) == 96); - assert(bitReverse(ubyte(7)) == 224); - assert(bitReverse(ubyte(8)) == 16); - assert(bitReverse(ubyte(255)) == 255); - assert(bitReverse(ushort(0b1101100010000000)) == 0b0000000100011011); - assert(bitReverse(uint(0x73810000)) == 0x000081CE); - assert(bitReverse(ulong(0x7381000000000000)) == 0x00000000000081CE); - - static assert(byteReverse(0x12) == 0x12); - static assert(byteReverse(0x1234) == 0x3412); - static assert(byteReverse(0x12345678) == 0x78563412); - static assert(byteReverse(0x123456789ABCDEF0) == 0xF0DEBC9A78563412); - static assert(byteReverse(true) == true); - static assert(byteReverse(char(0x12)) == char(0x12)); - static assert(byteReverse(wchar(0x1234)) == wchar(0x3412)); - static assert(byteReverse(cast(dchar)0x12345678) == cast(dchar)0x78563412); - assert(byteReverse(0x12) == 0x12); - assert(byteReverse(0x1234) == 0x3412); - assert(byteReverse(0x12345678) == 0x78563412); - assert(byteReverse(0x123456789ABCDEF0) == 0xF0DEBC9A78563412); - assert(byteReverse(true) == true); - assert(byteReverse(char(0x12)) == char(0x12)); - assert(byteReverse(wchar(0x1234)) == wchar(0x3412)); - assert(byteReverse(cast(dchar)0x12345678) == cast(dchar)0x78563412); + static assert(bit_reverse(ubyte(0)) == 0); + static assert(bit_reverse(ubyte(1)) == 128); + static assert(bit_reverse(ubyte(2)) == 64); + static assert(bit_reverse(ubyte(3)) == 192); + static assert(bit_reverse(ubyte(4)) == 32); + static assert(bit_reverse(ubyte(5)) == 160); + static assert(bit_reverse(ubyte(6)) == 96); + static assert(bit_reverse(ubyte(7)) == 224); + static assert(bit_reverse(ubyte(8)) == 16); + static assert(bit_reverse(ubyte(255)) == 255); + static assert(bit_reverse(ushort(0b1101100010000000)) == 0b0000000100011011); + static assert(bit_reverse(uint(0x73810000)) == 0x000081CE); + static assert(bit_reverse(ulong(0x7381000000000000)) == 0x00000000000081CE); + assert(bit_reverse(ubyte(0)) == 0); + assert(bit_reverse(ubyte(1)) == 128); + assert(bit_reverse(ubyte(2)) == 64); + assert(bit_reverse(ubyte(3)) == 192); + assert(bit_reverse(ubyte(4)) == 32); + assert(bit_reverse(ubyte(5)) == 160); + assert(bit_reverse(ubyte(6)) == 96); + assert(bit_reverse(ubyte(7)) == 224); + assert(bit_reverse(ubyte(8)) == 16); + assert(bit_reverse(ubyte(255)) == 255); + assert(bit_reverse(ushort(0b1101100010000000)) == 0b0000000100011011); + assert(bit_reverse(uint(0x73810000)) == 0x000081CE); + assert(bit_reverse(ulong(0x7381000000000000)) == 0x00000000000081CE); + + static assert(byte_reverse(0x12) == 0x12); + static assert(byte_reverse(0x1234) == 0x3412); + static assert(byte_reverse(0x12345678) == 0x78563412); + static assert(byte_reverse(0x123456789ABCDEF0) == 0xF0DEBC9A78563412); + static assert(byte_reverse(true) == true); + static assert(byte_reverse(char(0x12)) == char(0x12)); + static assert(byte_reverse(wchar(0x1234)) == wchar(0x3412)); + static assert(byte_reverse(cast(dchar)0x12345678) == cast(dchar)0x78563412); + assert(byte_reverse(0x12) == 0x12); + assert(byte_reverse(0x1234) == 0x3412); + assert(byte_reverse(0x12345678) == 0x78563412); + assert(byte_reverse(0x123456789ABCDEF0) == 0xF0DEBC9A78563412); + assert(byte_reverse(true) == true); + assert(byte_reverse(char(0x12)) == char(0x12)); + assert(byte_reverse(wchar(0x1234)) == wchar(0x3412)); + assert(byte_reverse(cast(dchar)0x12345678) == cast(dchar)0x78563412); float frev; *cast(uint*)&frev = 0x0000803F; - assert(byteReverse(1.0f) is frev); + assert(byte_reverse(1.0f) is frev); } diff --git a/src/urt/zip.d b/src/urt/zip.d index 62f4fda..040a056 100644 --- a/src/urt/zip.d +++ b/src/urt/zip.d @@ -6,20 +6,20 @@ import urt.hash; import urt.mem.allocator; import urt.result; -alias zlib_crc = calculate_crc!(Algorithm.CRC32_ISO_HDLC); +alias zlib_crc = calculate_crc!(Algorithm.crc32_iso_hdlc); nothrow @nogc: // this is a port of tinflate (tiny inflate) -enum gzip_flag : ubyte +enum GzipFlag : ubyte { - FTEXT = 1, - FHCRC = 2, - FEXTRA = 4, - FNAME = 8, - FCOMMENT = 16 + ftext = 1, + fhcrc = 2, + fextra = 4, + fname = 8, + fcomment = 16 } Result zlib_uncompress(const(void)[] source, void[] dest, out size_t destLen) @@ -112,7 +112,7 @@ Result gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen) const(ubyte)* start = src + 10; // skip extra data if present - if (flg & gzip_flag.FEXTRA) + if (flg & GzipFlag.fextra) { uint xlen = loadLittleEndian!ushort(cast(ushort*)start); @@ -123,7 +123,7 @@ Result gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen) } // skip file name if present - if (flg & gzip_flag.FNAME) + if (flg & GzipFlag.fname) { do { @@ -134,7 +134,7 @@ Result gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen) } // skip file comment if present - if (flg & gzip_flag.FCOMMENT) + if (flg & GzipFlag.fcomment) { do { @@ -145,7 +145,7 @@ Result gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen) } // check header crc if present - if (flg & gzip_flag.FHCRC) + if (flg & GzipFlag.fhcrc) { uint hcrc; From 8374c4f71de2a16083936ccf613be07c97d8fcfa Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 17 Aug 2025 16:18:37 +1000 Subject: [PATCH 004/138] Improve SI unit/quantity --- src/urt/meta/package.d | 23 +- src/urt/si/quantity.d | 202 ++++++++++++--- src/urt/si/unit.d | 571 +++++++++++++++++++++++++++++++++-------- 3 files changed, 644 insertions(+), 152 deletions(-) diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index e71470a..19f10f3 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -6,26 +6,29 @@ alias Alias(T) = T; alias AliasSeq(TList...) = TList; -template IntForWidth(size_t width, bool signed = false) +template IntForWidth(size_t bits, bool signed = false) { - static if (width <= 8 && !signed) + static if (bits <= 8 && !signed) alias IntForWidth = ubyte; - else static if (width <= 8 && signed) + else static if (bits <= 8 && signed) alias IntForWidth = byte; - else static if (width <= 16 && !signed) + else static if (bits <= 16 && !signed) alias IntForWidth = ushort; - else static if (width <= 16 && signed) + else static if (bits <= 16 && signed) alias IntForWidth = short; - else static if (width <= 32 && !signed) + else static if (bits <= 32 && !signed) alias IntForWidth = uint; - else static if (width <= 32 && signed) + else static if (bits <= 32 && signed) alias IntForWidth = int; - else static if (width <= 64 && !signed) + else static if (bits <= 64 && !signed) alias IntForWidth = ulong; - else static if (width <= 64 && signed) + else static if (bits <= 64 && signed) alias IntForWidth = long; } +alias TypeForOp(string op, U) = typeof(mixin(op ~ "U()")); +alias TypeForOp(string op, A, B) = typeof(mixin("A()" ~ op ~ "B()")); + template STATIC_MAP(alias fun, args...) { alias STATIC_MAP = AliasSeq!(); @@ -72,7 +75,7 @@ template enum_keys(E) } E enum_from_string(E)(const(char)[] key) - if (is(E == enum)) +if (is(E == enum)) { foreach (i, k; enum_keys!E) if (key[] == k[]) diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index 33462df..8fcecc6 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -1,6 +1,8 @@ module urt.si.quantity; +import urt.meta : TypeForOp; import urt.si.unit; +import urt.traits; nothrow @nogc: @@ -9,6 +11,12 @@ alias VarQuantity = Quantity!(double); alias Scalar = Quantity!(double, ScaledUnit()); alias Metres = Quantity!(double, ScaledUnit(Metre)); alias Seconds = Quantity!(double, ScaledUnit(Second)); +alias Volts = Quantity!(double, ScaledUnit(Volt)); +alias Amps = Quantity!(double, ScaledUnit(Ampere)); +alias AmpHours = Quantity!(double, AmpereHour); +alias Watts = Quantity!(double, ScaledUnit(Watt)); +alias Kilowatts = Quantity!(double, Kilowatt); +alias WattHours = Quantity!(double, WattHour); struct Quantity(T, ScaledUnit _unit = ScaledUnit(uint.max)) @@ -20,7 +28,7 @@ nothrow @nogc: enum Dynamic = _unit.pack == uint.max; enum IsCompatible(ScaledUnit U) = _unit.unit == U.unit; - T value; + T value = 0; static if (Dynamic) ScaledUnit unit; @@ -31,11 +39,20 @@ nothrow @nogc: if (is(U : T)) => unit.unit == compatibleWith.unit.unit; - this(T value) pure + static if (Dynamic) { - static if (Dynamic) - this.unit = ScaledUnit(); - this.value = value; + this(T value, ScaledUnit unit = ScaledUnit()) pure + { + this.unit = unit; + this.value = value; + } + } + else + { + this(T value) pure + { + this.value = value; + } } this(U, ScaledUnit _U)(Quantity!(U, _U) b) pure @@ -49,25 +66,26 @@ nothrow @nogc: else { static if (b.Dynamic) - assert(isCompatible(b), "Incompatible unit!"); + assert(isCompatible(b), "Incompatible units!"); else - static assert(IsCompatible!_U, "Incompatible unit: ", unit, " and ", b.unit); + static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); value = adjustScale(b); } } - + void opAssign()(T value) pure { static if (Dynamic) unit = Scalar; else - static assert(unit == Unit(), "Incompatible unit: ", unit, " and Scalar"); + static assert(unit == Unit(), "Incompatible units: ", unit.toString, " and Scalar"); this.value = value; } void opAssign(U, ScaledUnit _U)(Quantity!(U, _U) b) pure - if (is(U : T)) { + static assert(__traits(compiles, value = b.value), "cannot implicitly convert ScaledUnit of type `", U, "` to `", T, "`"); + static if (Dynamic) { unit = b.unit; @@ -76,36 +94,44 @@ nothrow @nogc: else { static if (b.Dynamic) - assert(isCompatible(b), "Incompatible unit!"); + assert(isCompatible(b), "Incompatible units!"); else - static assert(IsCompatible!_U, "Incompatible unit: ", unit, " and ", b.unit); + static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); value = adjustScale(b); } } - auto opBinary(string op, U)(U value) const pure - if ((op == "+" || op == "-") && is(U : T)) + auto opUnary(string op)() const pure + if (op == "+" || op == "-") { + alias RT = Quantity!(TypeForOp!(op, T), _unit); static if (Dynamic) - assert(unit == Scalar); + return RT(mixin(op ~ "value"), unit); else - static assert(unit == Unit(), "Incompatible unit: ", unit, " and Scalar"); - return mixin("this.value " ~ op ~ " value"); + return RT(mixin(op ~ "value")); } + auto opBinary(string op, U)(U value) const pure + if ((op == "+" || op == "-") && is(U : T)) + => opBinary!op(Quantity!(U, ScaledUnit())(value)); + auto opBinary(string op, U, ScaledUnit _U)(Quantity!(U, _U) b) const pure if ((op == "+" || op == "-") && is(U : T)) { + // TODO: what unit should be result take? + // for float T, I reckon maybe the MAX exponent? + // for int types... we need to do some special shit to manage overflows! + // HACK: for now, we just scale to the left-hand size... :/ static if (!Dynamic && !b.Dynamic && unit == Unit() && b.unit == Unit()) return mixin("value " ~ op ~ " b.value"); else { static if (Dynamic || b.Dynamic) - assert(isCompatible(b), "Incompatible unit!"); + assert(isCompatible(b), "Incompatible units!"); else - static assert(IsCompatible!_U, "Incompatible unit: ", unit, " and ", b.unit); + static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); - This r; + Quantity!(TypeForOp!(op, T, U), _unit) r; r.value = mixin("value " ~ op ~ " adjustScale(b)"); static if (Dynamic) r.unit = unit; @@ -120,7 +146,7 @@ nothrow @nogc: return mixin("this.value " ~ op ~ " value"); else { - This r; + Quantity!(TypeForOp!(op, T, U), unit) r; r.value = mixin("this.value " ~ op ~ " value"); static if (Dynamic) r.unit = unit; @@ -135,33 +161,63 @@ nothrow @nogc: return mixin("value " ~ op ~ " b.value"); else { + // TODO: if the unit product is invalid, then we need to decide a target scaling factor... static if (Dynamic || b.Dynamic) - { - Quantity!T r; - r.unit = mixin("unit " ~ op ~ " b.unit"); - } + const u = mixin("unit " ~ op ~ " b.unit"); else - Quantity!(T, mixin("unit " ~ op ~ " b.unit")) r; + enum u = mixin("unit " ~ op ~ " b.unit"); - // TODO: if the unit product is invalid, then we apply the scaling factor... - // ... but which side should we scale to? probably the left I guess... + alias RT = TypeForOp!(op, T, U); + RT v = mixin("value " ~ op ~ " b.value"); - r.value = mixin("value " ~ op ~ " b.value"); - return r; + static if (Dynamic || b.Dynamic) + return Quantity!RT(v, u); + else + return Quantity!(RT, u)(v); } } - void opOpAssign(string op, U)(U value) pure - if (is(U : T)) + void opOpAssign(string op)(T value) pure { + // TODO: in D; ubyte += int is allowed, so we should cast the result to T this = opBinary!op(value); } void opOpAssign(string op, U, ScaledUnit _U)(Quantity!(U, _U) b) pure { + // TODO: in D; ubyte += int is allowed, so we should cast the result to T this = opBinary!op(b); } + bool opCast(T : bool)() const pure + => value != 0; + + // not clear if this should return the raw value, or the normalised value...? +// T opCast(T)() const pure +// if (isSomeFloat!T || isSomeInt!T) +// { +// assert(unit.pack == 0, "Non-scalar unit can't cast to scalar"); +// assert(false, "TODO: should we be applying the scale to this result?"); +// return cast(T)value; +// } + + T opCast(T)() const pure + if (is(T == Quantity!(U, _U), U, ScaledUnit _U)) + { + static if (is(T == Quantity!(U, _U), U, ScaledUnit _U)) + { + T r; + static if (Dynamic || T.Dynamic) + assert(isCompatible(r), "Incompatible units!"); + else + static assert(IsCompatible!_U, "Incompatible units: ", r.unit.toString, " and ", unit.toString); + r.value = cast(U)r.adjustScale(this); + static if (T.Dynamic) + r.unit = unit; + return r; + } + } + bool opEquals(U)(U value) const pure if (is(U : T)) { @@ -185,14 +241,14 @@ nothrow @nogc: // can't compare mismatch unit types... i think? static if (Dynamic || rh.Dynamic) - assert(isCompatible(rh), "Incompatible unit!"); + assert(isCompatible(rh), "Incompatible units!"); else - static assert(IsCompatible!_U, "Incompatible unit: ", unit, " and ", rh.unit); + static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", rh.unit.toString); // TODO: meeting in the middle is only better if the signs are opposite // otherwise we should just scale to the left... static if (Dynamic && rh.Dynamic) - { + {{ // if the scale values are both dynamic, it should be more precise if we meet in the middle... auto lScale = unit.scale(); auto lTrans = unit.offset(); @@ -200,7 +256,7 @@ nothrow @nogc: auto rTrans = rh.unit.offset(); lhs = lhs*lScale + lTrans; rhs = rhs*rScale + rTrans; - } + }} else rhs = adjustScale(rh); @@ -222,6 +278,80 @@ nothrow @nogc: } } + auto normalise() const pure + { + static if (Dynamic) + { + Quantity!T r; + r.unit = ScaledUnit(unit.unit); + } + else + Quantity!(T, ScaledUnit(unit.unit)) r; + r.value = r.adjustScale(this); + return r; + } + + ptrdiff_t toString(char[] buffer) const + { + import urt.conv : format_float; + + double v = value; + ScaledUnit u = unit; + + if (u.pack) + { + // round upward to the nearest ^3 + if (u.siScale) + { + int x = u.exp; + if (u.unit.pack == 0) + { + if (x == -3) + { + v *= 0.1; + u = ScaledUnit(Unit(), x + 1); + } + else if (x != -2) + { + v *= u.scale(); + u = ScaledUnit(); + } + } + else + { + x = (x + 33) % 3; + if (x != 0) + { + u = ScaledUnit(u.unit, u.exp + (3 - x)); + if (x == 1) + v *= 0.01; + else + v *= 0.1; + } + } + } + } + + ptrdiff_t l = format_float(v, buffer); + if (l < 0) + return l; + + if (u.pack) + { + ptrdiff_t l2 = u.toString(buffer[l .. $]); + if (l2 < 0) + return l2; + l += l2; + } + + return l; + } + + ptrdiff_t fromString(const(char)[] s) + { + return -1; + } + private: T adjustScale(U, ScaledUnit _U)(Quantity!(U, _U) b) const pure { diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index cf40a9e..1db2668 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -1,5 +1,8 @@ module urt.si.unit; +import urt.array; +import urt.string; + nothrow @nogc: @@ -39,6 +42,9 @@ enum Candela = Unit(UnitType.Luma); enum Radian = Unit(UnitType.Angle); // non-si units +enum Minute = ScaledUnit(Second, ScaleFactor.Minute); +enum Hour = ScaledUnit(Second, ScaleFactor.Hour); +enum Day = ScaledUnit(Second, ScaleFactor.Day); enum Inch = ScaledUnit(Metre, ScaleFactor.Inch); enum Foot = ScaledUnit(Metre, ScaleFactor.Foot); enum Mile = ScaledUnit(Metre, ScaleFactor.Mile); @@ -61,6 +67,7 @@ enum Litre = ScaledUnit(CubicMetre, -3); enum Gram = ScaledUnit(Kilogram, -3); enum Milligram = ScaledUnit(Kilogram, -6); enum Hertz = Cycle / Second; +//enum Kilohertz = TODO: ONOES! Our system can't encode kilohertz! This is disaster!! enum Newton = Kilogram * Metre / Second^^2; enum Pascal = Newton / Metre^^2; enum PSI = ScaledUnit(Pascal, ScaleFactor.PSI); @@ -96,7 +103,18 @@ enum UnitType : ubyte struct Unit { -nothrow @nogc: +nothrow: + // debug/ctfe helper + string toString() pure + { + char[32] t; + ptrdiff_t l = toString(t); + if (l < 0) + return "Invalid unit"; // OR JUST COULDN'T STRINGIFY! + return t[0..l].idup; + } + +@nogc: uint pack; @@ -225,82 +243,39 @@ nothrow @nogc: this = this.opBinary!op(rh); } - ptrdiff_t toString(char[] buffer) const + ptrdiff_t toString(char[] buffer) const pure { - immutable string[24] si = [ -// "da", "h", "k", "10k", "100k", "M", "10M", "100M", "G", "10G", "100G", "T", "10T", "100T", "P", "10P", "100P", "E", "10E", "100E", "Z", "10Z", "100Z", "Y" - "10", "100", "k", "10k", "100k", "M", "10M", "100M", "G", "10G", "100G", "T", "10T", "100T", "P", "10P", "100P", "E", "10E", "100E", "Z", "10Z", "100Z", "Y" - ]; - immutable string[24] si_inv = [ -// "d", "c", "m", "100μ", "10μ", "μ", "100n", "10n", "n", "100p", "10p", "p", "100f", "10f", "f", "100a", "10a", "a", "100z", "10z", "z", "100y", "10y", "y" - "100m", "10m", "m", "100μ", "10μ", "μ", "100n", "10n", "n", "100p", "10p", "p", "100f", "10f", "f", "100a", "10a", "a", "100z", "10z", "z", "100y", "10y", "y" - ]; - - ptrdiff_t len = 0; - - // ⁰¹⁻²³⁴⁵⁶⁷⁸⁹·° - -// if (q(0).exponent == 0) -// { -// // if we have a scaling factor... what do we write? -//// if (sf) ... -// len = 0; -// return len; -// } -// -// auto name = this in unitNames; -// if (name) -// { -// len = name.length; -// buffer[0 .. len] = *name; -// return len; -// } -// -// int f = sf(); -// name = unit() in unitNames; -// -// if ((name || q(1).exponent == 0) && f <= 24) -// { -// if (f > 0) -// { -// ref immutable string[24] prefix = inv ? si_inv : si; -// -// uint scale = f - 1; -// if (q(0).measure == UnitType.Mass) -// scale += 3; -// -// len = prefix[scale].length; -// buffer[0 .. len] = prefix[scale][]; -// } -// -// if (name) -// { -// buffer[len .. len + name.length] = *name; -// len += name.length; -// } -// else -// { -// immutable string[UnitType.max + 1] unit_name = [ "g", "m", "s", "A", "K", "cd", "cy" ]; -// -// string uname = unit_name[q(0).measure]; -// buffer[len .. len + uname.length] = uname[]; -// len += uname.length; -// } -// -// if (q(0).measure != 1) -// { -// buffer[len++] = '^'; -// len += q(0).exponent.toString(buffer[len .. $]); -// } -// -// } -// else -// { -// // multiple terms, or an odd scale factor... -// // TODO... -// assert(false); -// } + assert(false, "TODO"); + } + + ptrdiff_t fromString(const(char)[] s) pure + { + if (s.length == 0) + { + pack = 0; + return 0; + } + + Unit r; + size_t len = s.length; + bool invert; + char sep; + while (const(char)[] unit = s.split!('/', '*')(sep)) + { + int p = unit.takePower(); + if (p == 0) + return -1; // invalid power + if (const Unit* u = unit in unitMap) + r *= (*u) ^^ (invert ? -p : p); + else + { + assert(false, "TODO?"); + } + if (sep == '/') + invert = true; + } + this = r; return len; } @@ -381,7 +356,18 @@ enum ExtendedScaleFactor : ubyte struct ScaledUnit { -nothrow @nogc: +nothrow: + // debug/ctfe helper + string toString() pure + { + char[32] t; + ptrdiff_t l = toString(t); + if (l < 0) + return "Invalid unit"; // OR JUST COULDN'T STRINGIFY! + return t[0..l].idup; + } + +@nogc: uint pack; @@ -574,6 +560,244 @@ nothrow @nogc: bool opEquals(Unit rh) const pure => (pack & 0xFF000000) ? false : unit == rh; + ptrdiff_t parseUnit(const(char)[] s, out float preScale) pure + { + preScale = 1; + + if (s.length == 0) + { + pack = 0; + return 0; + } + + size_t len = s.length; + if (s[0] == '-') + { + if (s.length == 1) + return -1; + preScale = -1; + s = s[1 .. $]; + } + + ScaledUnit r; + bool invert; + char sep; + while (const(char)[] term = s.split!('/', '*')(sep)) + { + int p = term.takePower(); + if (p == 0) + return -1; // invalid exponent + + if (const ScaledUnit* su = term in noScaleUnitMap) + r *= (*su) ^^ (invert ? -p : p); + else + { + size_t offset = 0; + + // parse the exponent + int e = 0; + if (term[0].is_numeric) + { + if (term[0] == '0') + { + if (term.length < 2 || term[1] != '.') + return -1; + e = 1; + offset = 2; + while (offset < term.length) + { + if (term[offset] == '1') + break; + if (term[offset] != '0') + return -1; + ++e; + ++offset; + } + ++offset; + e = -e; + } + else if (term[0] == '1') + { + offset = 1; + while (offset < term.length) + { + if (term[offset] != '0') + break; + ++e; + ++offset; + } + } + else + return -1; + } + + if (offset == term.length) + r *= ScaledUnit(Unit(), e); + else + { + // try and parse SI prefix... + switch (term[offset]) + { + case 'Y': e += 24; ++offset; break; + case 'Z': e += 21; ++offset; break; + case 'E': e += 18; ++offset; break; + case 'P': e += 15; ++offset; break; + case 'T': e += 12; ++offset; break; + case 'G': e += 9; ++offset; break; + case 'M': e += 6; ++offset; break; + case 'k': e += 3; ++offset; break; + case 'h': e += 2; ++offset; break; + case 'c': e -= 2; ++offset; break; + case 'u': e -= 6; ++offset; break; + case 'n': e -= 9; ++offset; break; + case 'p': e -= 12; ++offset; break; + case 'f': e -= 15; ++offset; break; + case 'a': e -= 18; ++offset; break; + case 'z': e -= 21; ++offset; break; + case 'y': e -= 24; ++offset; break; + case 'm': + // can confuse with metres... so gotta check... + if (offset + 1 < term.length) + e -= 3, ++offset; + break; + case 'd': + if (offset + 1 < term.length && term[offset + 1] == 'a') + { + e += 1, offset += 2; + break; + } + e -= 1, ++offset; + break; + default: + if (offset + "µ".length < term.length && term[offset .. offset + "µ".length] == "µ") + e -= 6, offset += "µ".length; + break; + } + if (offset == term.length) + return -1; + + term = term[offset .. $]; + if (const Unit* u = term in unitMap) + { + if (term == "kg") + { + // we alrady parsed the 'k' + return -1; + } + r *= ScaledUnit((*u) ^^ (invert ? -p : p), e); + } + else if (const ScaledUnit* su = term in scaledUnitMap) + r *= ScaledUnit(su.unit, su.exp + e) ^^ (invert ? -p : p); + else if (const ScaledUnit* su = term in noScaleUnitMap) + { + r *= (*su) ^^ (invert ? -p : p); + preScale *= 10^^e; + } + else + return -1; // string was not taken? + } + } + + if (sep == '/') + invert = true; + } + this = r; + return len; + } + + ptrdiff_t toString(char[] buffer) const pure + { + if (!unit.pack) + { + if (siScale && exp == -2) + { + if (buffer.length == 0) + return -1; + buffer[0] = '%'; + return 1; + } + else + assert(false, "TODO!"); + } + + size_t len = 0; + if (siScale) + { + int x = exp; + if (x != 0) + { + // for scale factors between SI units, we'll normalise to the next higher unit... + int y = (x + 33) % 3; + if (y != 0) + { + if (y == 1) + { + if (buffer.length < 2) + return -1; + --x; + buffer[0..2] = "10"; + len += 2; + } + else + { + if (buffer.length < 3) + return -1; + x -= 2; + buffer[0..3] = "100"; + len += 3; + } + } + assert(x >= -30, "TODO: handle this very small case"); + + if (x != 0) + { + if (buffer.length <= len) + return -1; + buffer[len++] = "qryzafpnum kMGTPEZYRQ"[x/3 + 10]; + } + } + + if (const string* name = unit in unitNames) + { + if (buffer.length < len + name.length) + return -1; + buffer[len .. len + name.length] = *name; + len += name.length; + } + else + { + // synth a unit name... + assert(false, "TODO"); + } + } + else + { + if (const string* name = this in scaledUnitNames) + { + if (buffer.length < len + name.length) + return -1; + buffer[len .. len + name.length] = *name; + len += name.length; + } + else + { + // what now? + assert(false, "TODO"); + } + } + return len; + } + + ptrdiff_t fromString(const(char)[] s) pure + { + float scale; + ptrdiff_t r = parseUnit(s, scale); + if (scale != 1) + return -1; + return r; + } + + size_t toHash() const pure => pack; @@ -688,39 +912,41 @@ immutable double[4] tempOffsets = [ (-273.15*9)/5 + 32 // K -> F ]; -immutable string[ScaledUnit] unitNames = [ +immutable string[Unit] unitNames = [ + Metre : "m", + Metre^^2 : "m²", + Metre^^3 : "m³", + Kilogram : "kg", + Second : "s", + Ampere : "A", + Kelvin : "K", + Candela : "cd", + Radian : "rad", - // ⁰¹⁻²³⁴⁵⁶⁷⁸⁹·° + // derived units + Newton : "N", + Pascal : "Pa", + Joule : "J", + Watt : "W", + Coulomb : "C", + Volt : "V", + Ohm : "Ω", + Farad : "F", + Siemens : "S", + Weber : "Wb", + Tesla : "T", + Henry : "H", + Lumen : "lm", + Lux : "lx", +]; - // base units - ScaledUnit() : "", - ScaledUnit(Metre) : "m", - ScaledUnit(Kilogram) : "kg", - ScaledUnit(Second) : "s", - ScaledUnit(Ampere) : "A", - ScaledUnit(Kelvin) : "°K", - ScaledUnit(Candela) : "cd", - ScaledUnit(Radian) : "rad", +immutable string[ScaledUnit] scaledUnitNames = [ + Minute : "min", +// Minute : "mins", + Hour : "hr", +// Hour : "hrs", + Day : "day", - // derived units - ScaledUnit(SquareMetre) : "m²", - ScaledUnit(CubicMetre) : "m³", - ScaledUnit(Newton) : "N", - ScaledUnit(Pascal) : "Pa", - ScaledUnit(Joule) : "J", - ScaledUnit(Watt) : "W", - ScaledUnit(Coulomb) : "C", - ScaledUnit(Volt) : "V", - ScaledUnit(Ohm) : "Ω", - ScaledUnit(Farad) : "F", - ScaledUnit(Siemens) : "S", - ScaledUnit(Weber) : "Wb", - ScaledUnit(Tesla) : "T", - ScaledUnit(Henry) : "H", - ScaledUnit(Lumen) : "lm", - ScaledUnit(Lux) : "lx", - - // scaled units Inch : "in", Foot : "ft", Mile : "mi", @@ -745,3 +971,136 @@ immutable string[ScaledUnit] unitNames = [ AmpereHour : "Ah", WattHour : "Wh", ]; + +immutable Unit[string] unitMap = [ + // base units + "m" : Metre, + "kg" : Kilogram, + "s" : Second, + "A" : Ampere, + "°K" : Kelvin, + "cd" : Candela, + "rad" : Radian, + + // derived units + "N" : Newton, + "Pa" : Pascal, + "J" : Joule, + "W" : Watt, + "C" : Coulomb, + "V" : Volt, + "Ω" : Ohm, + "F" : Farad, + "S" : Siemens, + "Wb" : Weber, + "T" : Tesla, + "H" : Henry, + "lm" : Lumen, + "lx" : Lux, + + // questionable... :/ + "VA" : Watt, + "var" : Watt, +]; + +immutable ScaledUnit[string] noScaleUnitMap = [ + "min" : Minute, + "mins" : Minute, + "hr" : Hour, + "hrs" : Hour, + "day" : Day, + "days" : Day, + "'" : Inch, + "in" : Inch, + "\"" : Foot, + "ft" : Foot, + "mi" : Mile, + "oz" : Ounce, + // TODO: us/uk floz/gallon? + "lb" : Pound, + "°" : Degree, + "deg" : Degree, + "°C" : Celsius, + "°F" : Fahrenheit, + "Ah" : AmpereHour, + "Wh" : WattHour, + "cy" : Cycle, + "Hz" : Hertz, + "psi" : PSI, + + // questionable... :/ + "VAh" : WattHour, + "varh" : WattHour, +]; + +immutable ScaledUnit[string] scaledUnitMap = [ + "%" : Percent, + "‰" : Permille, + "l" : Litre, + "g" : Gram, +]; + +int takePower(ref const(char)[] s) pure +{ + size_t e = s.findFirst('^'); + if (e < s.length) + { + const(char)[] p = s[e+1..$]; + s = s[0..e]; + if (s.length == 0 || p.length == 0) + return 0; + if (p[0] == '-') + { + if (p.length != 2 || uint(p[2] - '0') > 4) + return 0; + return -(p[2] - '0'); + } + if (p.length != 1 || uint(p[1] - '0') > 4) + return 0; + return p[2] - '0'; + } + else if (s.length > 2) + { + if (s[$-2..$] == "¹") + { + if (s.length > 5 && s[$-5..$-2] == "⁻") + { + s = s[0..$-5]; + return -1; + } + s = s[0..$-2]; + return 1; + } + if (s[$-2..$] == "²") + { + if (s.length > 5 && s[$-5..$-2] == "⁻") + { + s = s[0..$-5]; + return -2; + } + s = s[0..$-2]; + return 2; + } + if (s[$-2..$] == "³") + { + if (s.length > 5 && s[$-5..$-2] == "⁻") + { + s = s[0..$-5]; + return -3; + } + s = s[0..$-2]; + return 3; + } + } + else if (s.length > 3 && s[$-3..$] == "⁴") + { + if (s.length > 6 && s[$-6..$-3] == "⁻") + { + s = s[0..$-6]; + return -4; + } + s = s[0..$-3]; + return 4; + } + return 1; +} From f0fc1600bfff67ec1cab377927829b83336e6a5c Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 19 Aug 2025 20:47:19 +1000 Subject: [PATCH 005/138] Improve variant support for Quantity Also improve user type support... which is kinda unrelated, but ya-know! --- src/urt/conv.d | 88 +++++ src/urt/format/json.d | 23 +- src/urt/inet.d | 10 + src/urt/si/quantity.d | 2 +- src/urt/variant.d | 805 ++++++++++++++++++++++++++++++++++-------- 5 files changed, 758 insertions(+), 170 deletions(-) diff --git a/src/urt/conv.d b/src/urt/conv.d index a20b6ee..dbbd3f9 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -253,6 +253,94 @@ unittest } +ptrdiff_t parse(T)(const char[] text, out T result) +{ + import urt.array : beginsWith; + import urt.traits; + + alias UT = Unqual!T; + + static if (is(UT == bool)) + { + if (text.beginsWith("true")) + { + result = true; + return 4; + } + result = false; + if (text.beginsWith("false")) + return 5; + return -1; + } + else static if (is_some_int!T) + { + size_t taken; + static if (is_signed_int!T) + long r = text.parse_int(&taken); + else + ulong r = text.parse_uint(&taken); + if (!taken) + return -1; + if (r >= T.min && r <= T.max) + { + result = cast(T)r; + return taken; + } + return -2; + } + else static if (is_some_float!T) + { + size_t taken; + double f = text.parse_float(&taken); + if (!taken) + return -1; + result = cast(T)f; + return taken; + } + else static if (is_enum!T) + { + static assert(false, "TODO: do we want to parse from enum keys?"); + // case-sensitive? + } + else static if (is(T == struct) && __traits(compiles, { result.fromString(text); })) + { + return result.fromString(text); + } + else + static assert(false, "Cannot parse " ~ T.stringof ~ " from string"); +} + +unittest +{ + { + bool r; + assert("true".parse(r) == 4 && r == true); + assert("false".parse(r) == 5 && r == false); + assert("wow".parse(r) == -1); + } + { + int r; + assert("-10".parse(r) == 3 && r == -10); + } + { + ubyte r; + assert("10".parse(r) == 2 && r == 10); + assert("-10".parse(r) == -1); + assert("257".parse(r) == -2); + } + { + float r; + assert("10".parse(r) == 2 && r == 10.0f); + assert("-2.5".parse(r) == 4 && r == -2.5f); + } + { + import urt.inet; + IPAddr r; + assert("10.0.0.1".parse(r) == 8 && r == IPAddr(10,0,0,1)); + } +} + + ptrdiff_t format_int(long value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ', bool show_sign = false) pure { const bool neg = value < 0; diff --git a/src/urt/format/json.d b/src/urt/format/json.d index a48faed..ca2cd26 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -23,28 +23,9 @@ ptrdiff_t write_json(ref const Variant val, char[] buffer, bool dense = false, u final switch (val.type) { case Variant.Type.Null: - if (!buffer.ptr) - return 4; - if (buffer.length < 4) - return -1; - buffer[0 .. 4] = "null"; - return 4; - - case Variant.Type.False: - if (!buffer.ptr) - return 5; - if (buffer.length < 5) - return -1; - buffer[0 .. 5] = "false"; - return 5; - case Variant.Type.True: - if (!buffer.ptr) - return 4; - if (buffer.length < 4) - return -1; - buffer[0 .. 4] = "true"; - return 4; + case Variant.Type.False: + return val.toString(buffer, null, null); case Variant.Type.Map: case Variant.Type.Array: diff --git a/src/urt/inet.d b/src/urt/inet.d index 6dccd11..4b41981 100644 --- a/src/urt/inet.d +++ b/src/urt/inet.d @@ -74,6 +74,16 @@ nothrow @nogc: bool opEquals(const(ubyte)[4] bytes) const pure => b == bytes; + int opCmp(ref const IPAddr rhs) const pure + { + uint a = loadBigEndian(&address), b = loadBigEndian(&rhs.address); + if (a < b) + return -1; + else if (a > b) + return 1; + return 0; + } + IPAddr opUnary(string op : "~")() const pure { IPAddr r; diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index 8fcecc6..c8da35e 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -194,7 +194,7 @@ nothrow @nogc: // not clear if this should return the raw value, or the normalised value...? // T opCast(T)() const pure -// if (isSomeFloat!T || isSomeInt!T) +// if (is_some_float!T || is_some_int!T) // { // assert(unit.pack == 0, "Non-scalar unit can't cast to scalar"); // assert(false, "TODO: should we be applying the scale to this result?"); diff --git a/src/urt/variant.d b/src/urt/variant.d index 8567b46..cc89cf5 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -1,9 +1,12 @@ module urt.variant; +import urt.algorithm : compare; import urt.array; +import urt.conv; import urt.kvp; import urt.lifetime; import urt.map; +import urt.mem.allocator; import urt.si.quantity; import urt.si.unit : ScaledUnit; import urt.traits; @@ -65,80 +68,81 @@ nothrow @nogc: } this(I)(I i) - if (is(I == byte) || is(I == short)) + if (is_some_int!I) { - flags = Flags.NumberInt; - value.l = i; - if (i >= 0) - flags |= Flags.UintFlag | Flags.Uint64Flag; - } - this(I)(I i) - if (is(I == ubyte) || is(I == ushort)) - { - flags = cast(Flags)(Flags.NumberUint | Flags.IntFlag); - value.ul = i; - } - - this(int i) - { - flags = Flags.NumberInt; - value.l = i; - if (i >= 0) - flags |= Flags.UintFlag | Flags.Uint64Flag; - } - - this(uint i) - { - flags = Flags.NumberUint; - value.ul = i; - if (i <= int.max) - flags |= Flags.IntFlag; - } + static if (is_signed_int!I) + value.l = i; + else + value.ul = i; - this(long i) - { - flags = Flags.NumberInt64; - value.l = i; - if (i >= 0) + static if (is(I == ubyte) || is(I == ushort)) + flags = cast(Flags)(Flags.NumberUint | Flags.IntFlag | Flags.Int64Flag); + else static if (is(I == byte) || is(I == short) || is(I == int)) + { + flags = Flags.NumberInt; + if (i >= 0) + flags |= Flags.UintFlag | Flags.Uint64Flag; + } + else static if (is(I == uint)) { - flags |= Flags.Uint64Flag; + flags = Flags.NumberUint; if (i <= int.max) - flags |= Flags.IntFlag | Flags.UintFlag; + flags |= Flags.IntFlag; + } + else static if (is(I == long)) + { + flags = Flags.NumberInt64; + if (i >= 0) + { + flags |= Flags.Uint64Flag; + if (i <= int.max) + flags |= Flags.IntFlag | Flags.UintFlag; + else if (i <= uint.max) + flags |= Flags.UintFlag; + } + else if (i >= int.min) + flags |= Flags.IntFlag; + } + else static if (is(I == ulong)) + { + flags = Flags.NumberUint64; + if (i <= int.max) + flags |= Flags.IntFlag | Flags.UintFlag | Flags.Int64Flag; else if (i <= uint.max) - flags |= Flags.UintFlag; + flags |= Flags.UintFlag | Flags.Int64Flag; + else if (i <= long.max) + flags |= Flags.Int64Flag; } - else if (i >= int.min) - flags |= Flags.IntFlag; } - this(ulong i) + this(F)(F f) + if (is_some_float!F) { - flags = Flags.NumberUint64; - value.ul = i; - if (i <= int.max) - flags |= Flags.IntFlag | Flags.UintFlag | Flags.Int64Flag; - else if (i <= uint.max) - flags |= Flags.UintFlag | Flags.Int64Flag; - else if (i <= long.max) - flags |= Flags.Int64Flag; - } - - this(float f) - { - flags = Flags.NumberFloat; + static if (is(F == float)) + flags = Flags.NumberFloat; + else + flags = Flags.NumberDouble; value.d = f; } - this(double d) + + this(E)(E e) + if (is(E == enum)) { - flags = Flags.NumberDouble; - value.d = d; + static if (is(E T == enum)) + { + this(T(e)); + // TODO: do we keep a record of the enum keys for stringification? + } } this(U, ScaledUnit _U)(Quantity!(U, _U) q) { this(q.value); - flags |= Flags.IsQuantity; - count = q.unit.pack; + if (q.unit.pack) + { + flags |= Flags.IsQuantity; + count = q.unit.pack; + } } this(const(char)[] s) // TODO: (S)(S s) @@ -209,19 +213,28 @@ nothrow @nogc: static if (is(T == class)) { count = UserTypeId!T; - value.p = cast(void*)&thing; + ptr = cast(void*)thing; } else static if (EmbedUserType!T) { + alloc = UserTypeId!T; flags |= Flags.Embedded; - alloc = UserTypeShortId!T; + + if (TypeDetailsFor!T.destroy) // TODO: we should check the same condition that determined if there is a destruct function... + flags |= Flags.NeedDestruction; + emplace(cast(T*)embed.ptr, forward!thing); } else { -// flags |= Flags.NeedDestruction; // if T has a destructor... count = UserTypeId!T; - assert(false, "TODO: alloc for the object..."); + alloc = type_detail_index!T(); + + if (TypeDetailsFor!T.destroy) // TODO: we should check the same condition that determined if there is a destruct function... + flags |= Flags.NeedDestruction; + + ptr = defaultAllocator().alloc(T.sizeof, T.alignof).ptr; + emplace(cast(T*)ptr, forward!thing); } } @@ -230,12 +243,26 @@ nothrow @nogc: destroy!false(); } + void opAssign(ref Variant value) + { + if (&this is &value) + return; // TODO: should this be an assert instead of a graceful handler? + destroy!false(); + new(this) Variant(value); + } + version (EnableMoveSemantics) { + void opAssign(Variant value) + { + destroy!false(); + new(this) Variant(__rvalue(value)); // TODO: value.move + } + } + // TODO: since this is a catch-all, the error messages will be a bit shit // maybe we can find a way to constrain it to valid inputs? void opAssign(T)(auto ref T value) { - destroy!false(); - emplace(&this, forward!value); + this = Variant(value); } // TODO: do we want Variant to support +=, ~=, etc...? @@ -266,6 +293,230 @@ nothrow @nogc: return nodeArray.pushBack(); } + bool opEquals(T)(ref const Variant rhs) const pure + { + return opCmp(rhs) == 0; + } + bool opEquals(T)(auto ref const T rhs) const + { + // TODO: handle short-cut array/map comparisons? + static if (is(T == typeof(null))) + return type == Type.Null || ((type == Type.String || type == Type.Array || type == Type.Map) && empty()); + else static if (is(T == bool)) + { + if (!isBool) + return false; // do non-zero numbers evaluate true? what about non-zero strings? etc... + return asBool == rhs; + } + else static if (is_some_int!T || is_some_float!T) + { + if (!isNumber) + return false; + static if (is_some_int!T) if (!canFitInt!T) + return false; + if (isQuantity) + return asQuantity!double() == Quantity!T(rhs); + return as!T == rhs; + } + else static if (is(T == Quantity!(U, _U), U, ScaledUnit _U)) + { + if (!isNumber) + return false; + return asQuantity!double() == rhs; + } + else static if (is(T E == enum)) + { + // TODO: should we also do string key comparisons? + return opEquals(cast(E)rhs); + } + else static if (is(T : const(char)[])) + return isString && asString() == rhs[]; + else static if (ValidUserType!T) + return asUser!T == rhs; + else + static assert(false, "TODO: variant comparison with '", T.stringof, "' not supported"); + } + + int opCmp(ref const Variant rhs) const pure + { + const(Variant)* a, b; + bool invert = false; + if (this.type <= rhs.type) + a = &this, b = &rhs; + else + a = &rhs, b = &this, invert = true; + + int r = 0; + final switch (a.type) + { + case Type.Null: + if (b.type == Type.Null) + return 0; + else if ((b.type == Type.String || b.type == Type.Array || b.type == Type.Map) && b.empty()) + return 0; + r = -1; // sort null before other things... + break; + + case Type.True: + case Type.False: + // if both sides are bool + if (b.type <= Type.False) + { + r = a.asBool - b.asBool; + break; + } + // TODO: maybe we don't want to accept bool/number comparison? + goto case; // we will compare bools with numbers... + + case Type.Number: + if (b.type <= Type.Number) + { + static double asDoubleWithBool(ref const Variant v) + => v.isBool() ? double(v.asBool()) : v.asDouble(); + + if (a.isQuantity || b.isQuantity) + { + // we can't compare different units + uint aunit = a.isQuantity ? (a.count & 0xFFFFFF) : 0; + uint bunit = b.isQuantity ? (b.count & 0xFFFFFF) : 0; + if (aunit != bunit) + { + r = aunit - bunit; + break; + } + + // matching units, but we'll only do quantity comparison if there is some scaling + ubyte ascale = a.isQuantity ? (a.count >> 24) : 0; + ubyte bscale = b.isQuantity ? (b.count >> 24) : 0; + if (ascale || bscale) + { + Quantity!double aq = a.isQuantity ? a.asQuantity!double() : Quantity!double(asDoubleWithBool(*a)); + Quantity!double bq = b.isQuantity ? b.asQuantity!double() : Quantity!double(asDoubleWithBool(*b)); + r = aq.opCmp(bq); + break; + } + } + + if (a.flags & Flags.FloatFlag || b.flags & Flags.FloatFlag) + { + // float comparison + // TODO: determine if float/bool comparison seems right? is: -1 < false < 0.9 < true < 1.1? + double af = asDoubleWithBool(*a); + double bf = asDoubleWithBool(*b); + r = af < bf ? -1 : af > bf ? 1 : 0; + break; + } + + // TODO: this could be further optimised by comparing the value range flags... + if ((a.flags & (Flags.Int64Flag | Flags.IsBool)) == 0) + { + ulong aul = a.asUlong(); + if ((b.flags & (Flags.Int64Flag | Flags.IsBool)) == 0) + { + ulong bul = b.asUlong(); + r = aul < bul ? -1 : aul > bul ? 1 : 0; + } + else + r = 1; // a is in ulong range, rhs is not; a is larger... + break; + } + if ((b.flags & (Flags.Int64Flag | Flags.IsBool)) == 0) + { + r = -1; // b is in ulong range, lhs is not; b is larger... + break; + } + + long al = a.isBool() ? a.asBool() : a.asLong(); + long bl = b.isBool() ? b.asBool() : b.asLong(); + r = al < bl ? -1 : al > bl ? 1 : 0; + } + else + r = -1; // sort numbers before other things... + break; + + case Type.String: + if (b.type != Type.String) + { + r = -1; + break; + } + r = compare(a.asString(), b.asString()); + break; + + case Type.Array: + if (b.type != Type.Array) + { + r = -1; + break; + } + r = compare(a.asArray()[], b.asArray()[]); + break; + + case Type.Map: + if (b.type != Type.Map) + { + r = -1; + break; + } + assert(false, "TODO"); + break; + + case Type.User: + uint at = a.userType; + uint bt = b.userType; + if (at != bt) + { + r = at < bt ? -1 : at > bt ? 1 : 0; + break; + } + alias PureHack = ref TypeDetails function(uint index) pure nothrow @nogc; + if (flags & Flags.Embedded) + { + ref const TypeDetails td = (cast(PureHack)&find_type_details)(alloc); + r = td.cmp(a.embed.ptr, b.embed.ptr, 0); + } + else + { + ref const TypeDetails td = (cast(PureHack)&get_type_details)(alloc); + r = td.cmp(a.ptr, b.ptr, 0); + } + break; + } + return invert ? -r : r; + } + int opCmp(T)(auto ref const T rhs) const + { + // TODO: handle short-cut string, array, map comparisons + static if (is(T == typeof(null))) + return type == Type.Null || ((type == Type.String || type == Type.Array || type == Type.Map) && empty()) ? 0 : 1; + else static if (is(T : const(char)[])) + return isString() ? compare(asString(), rhs) : (type < Type.String ? -1 : 1); + static if (ValidUserType!T) + return compare(asUser!T, rhs); + else + return opCmp(Variant(rhs)); + } + + bool opBinary(string op)(ref const Variant rhs) const pure + if (op == "is") + { + // compare that Variant's are identical, not just equivalent! + assert(false, "TODO"); + } + bool opBinary(string op, T)(auto ref const T rhs) const + if (op == "is") + { + // TODO: handle short-cut array/map comparisons? + static if (is(T == typeof(null))) + return type == Type.Null || ((type == Type.String || type == Type.Array || type == Type.Map) && empty()); + else static if (is(T : const(char)[])) + return isString && asString().ptr is rhs.ptr && length() == rhs.length; + else static if (ValidUserType!T) + return asUser!T is rhs; + else + return opBinary!"is"(Variant(rhs)); + } + bool isNull() const pure => flags == Flags.Null; bool isFalse() const pure @@ -302,42 +553,77 @@ nothrow @nogc: if ((flags & Flags.TypeMask) != Type.User) return false; static if (EmbedUserType!T) - return alloc == UserTypeShortId!T; + return alloc == UserTypeId!T; else return count == UserTypeId!T; } + bool canFitInt(I)() const pure + if (is_some_int!I) + { + if (!isNumber || isFloat) + return false; + static if (is(I == ulong)) + return isUlong; + else static if (is(I == long)) + return isLong; + else static if (is(I == uint)) + return isUint; + else static if (is(I == int)) + return isInt; + else static if (is_signed_int!I) + { + if (!isInt) + return false; + int i = asInt(); + return i >= I.min && i <= I.max; + } + else + return isUlong && asUlong <= I.max; + } bool asBool() const pure @property { + if (isNull) + return false; assert(isBool()); return flags == Flags.True; } int asInt() const pure @property { - assert(isInt()); + if (isNull) + return 0; + assert(isInt(), "Value out of range for int"); return cast(int)value.l; } uint asUint() const pure @property { - assert(isUint()); + if (isNull) + return 0; + assert(isUint(), "Value out of range for uint"); return cast(uint)value.ul; } long asLong() const pure @property { - assert(isLong()); + if (isNull) + return 0; + assert(isLong(), "Value out of range for long"); return value.l; } ulong asUlong() const pure @property { - assert(isUlong()); + if (isNull) + return 0; + assert(isUlong(), "Value out of range for ulong"); return value.ul; } double asDouble() const pure @property { - assert(isNumber()); + if (isNull) + return 0; + assert(isNumber); if ((flags & Flags.DoubleFlag) != 0) return value.d; if ((flags & Flags.UintFlag) != 0) @@ -349,33 +635,40 @@ nothrow @nogc: return cast(double)cast(long)value.ul; } + float asFloat() const pure @property + { + if (isNull) + return 0; + assert(isNumber); + if ((flags & Flags.DoubleFlag) != 0) + return value.d; + if ((flags & Flags.UintFlag) != 0) + return cast(float)cast(uint)value.ul; + if ((flags & Flags.IntFlag) != 0) + return cast(float)cast(int)cast(long)value.ul; + if ((flags & Flags.Uint64Flag) != 0) + return cast(float)value.ul; + return cast(float)cast(long)value.ul; + } + Quantity!T asQuantity(T = double)() const pure @property + if (is_some_float!T || isSomeInt!T) { - assert(isNumber()); - - Quantity!double r; - static if (is(T == double)) - r.value = asDouble(); -// else static if (is(T == float)) -// r.value = asFloat(); - else static if (is(T == int)) - r.value = asInt(); - else static if (is(T == uint)) - r.value = asUint(); - else static if (is(T == long)) - r.value = asLong(); - else static if (is(T == ulong)) - r.value = asUlong(); - else - assert(false, "Unsupported quantity type!"); - if (isQuantity()) + if (isNull) + return Quantity!T(0); + assert(isNumber); + Quantity!T r; + r.value = as!T; + if (isQuantity) r.unit.pack = count; return r; } const(char)[] asString() const pure { - assert(isString()); + if (isNull) + return null; + assert(isString); if (flags & Flags.Embedded) return embed[0 .. embed[$-1]]; return value.s[0 .. count]; @@ -415,11 +708,72 @@ nothrow @nogc: static if (is(T == class)) return cast(inout(T))ptr; else static if (EmbedUserType!T) - static assert(false, "TODO: memcpy to a stack local and return that..."); + { + T r = void; + TypeDetailsFor!T.copy_emplace(embed.ptr, &r, false); + return r; + } else static assert(false, "Should be impossible?"); } + auto as(T)() inout pure + if (!ValidUserType!T || !UserTypeReturnByRef!T) + { + static if (is_some_int!T) + { + static if (is_signed_int!T) + { + static if (is(T == long)) + return asLong(); + else + { + int i = asInt(); + static if (!is(T == int)) + assert(i >= T.min && i <= T.max, "Value out of range for " ~ T.stringof); + return cast(T)i; + } + } + else + { + static if (is(T == ulong)) + return asUlong(); + else + { + uint u = asInt(); + static if (!is(T == uint)) + assert(u <= T.max, "Value out of range for " ~ T.stringof); + return cast(T)u; + } + } + } + else static if (is_some_float!T) + { + static if (is(T == float)) + return asFloat(); + else + return asDouble(); + } + else static if (is(T == Quantity!(U, _U), U, ScaledUnit _U)) + { + return asQuantity!U(); + } + else static if (is(T : const(char)[])) + { + static if (is(T == struct)) // for String/MutableString/etc + return T(asString); // TODO: error? shouldn't this NRVO?! + else + return asString; + } + else static if (ValidUserType!T) + return asUser!T; + else + static assert(false, "TODO!"); + } + ref inout(T) as(T)() inout pure + if (ValidUserType!T && UserTypeReturnByRef!T) + => asUser!T; + size_t length() const pure { if (flags == Flags.Null) @@ -429,7 +783,7 @@ nothrow @nogc: else if (isArray()) return count; else - assert(false); + assert(false, "Variant does not have `length`"); } bool empty() const pure @@ -479,41 +833,27 @@ nothrow @nogc: { final switch (type) { - case Variant.Type.Null: + case Variant.Type.Null: // assume type == 0 + case Variant.Type.True: // assume type == 1 + case Variant.Type.False: // assume type == 2 + __gshared immutable char** values = [ "null", "true", "false" ]; + size_t len = 4 + (type >> 1); if (!buffer.ptr) - return 4; - if (buffer.length < 4) + return len; + if (buffer.length < len) return -1; - buffer[0 .. 4] = "null"; - return 4; - - case Variant.Type.False: - if (!buffer.ptr) - return 5; - if (buffer.length < 5) - return -1; - buffer[0 .. 5] = "false"; - return 5; - - case Variant.Type.True: - if (!buffer.ptr) - return 4; - if (buffer.length < 4) - return -1; - buffer[0 .. 4] = "true"; - return 4; + buffer[0 .. len] = values[type][0 .. len]; + return len; case Variant.Type.Number: - import urt.conv; - if (isQuantity()) - assert(false, "TODO: implement quantity formatting for JSON"); + return asQuantity().toString(buffer);//, format, formatArgs); if (isDouble()) return asDouble().format_float(buffer); // TODO: parse args? - //format + assert(!format, "TODO"); if (flags & Flags.Uint64Flag) return asUlong().format_uint(buffer); @@ -540,12 +880,108 @@ nothrow @nogc: case Variant.Type.User: if (flags & Flags.Embedded) - return findTypeDetails(alloc).stringify(embed.ptr, buffer); + return find_type_details(alloc).stringify(cast(void*)embed.ptr, buffer, true); else - return findTypeDetails(count).stringify(ptr, buffer); + return type_details[alloc].stringify(cast(void*)ptr, buffer, true); } } + ptrdiff_t fromString(const(char)[] s) + { + import urt.string.ascii : is_numeric; + + if (s.empty || s == "null") + { + this = null; + return s.length; + } + if (s == "true") + { + this = true; + return 4; + } + if (s == "false") + { + this = false; + return 5; + } + + if (s[0] == '"') + { + for (size_t i = 1; i < s.length; ++i) + { + if (s[i] == '"') + { + assert(i == s.length - 1, "String must end with a quote"); + this = s[1 .. i]; + return i + 1; + } + } + assert(false, "String has no closing quote"); + } + + if (s[0].is_numeric) + { + size_t taken; + ScaledUnit unit; + ulong div; + long i = s.parse_int_with_decimal(div, &taken, 10); + if (taken < s.length) + { + size_t t2 = unit.fromString(s[taken .. $]); + if (t2 > 0) + taken += t2; + } + if (taken == s.length) + { + if (div != 1) + this = double(i) / div; + else + this = i; + if (unit.pack) + { + flags |= Flags.IsQuantity; + count = unit.pack; + } + return taken; + } + } + + align(64) void[256] buffer = void; + this = null; // clear the object since we'll probably use the embed buffer... + foreach (ushort i; 0 .. num_type_details) + { + debug assert(type_details[i].alignment <= 64 && type_details[i].size <= buffer.sizeof, "Buffer is too small for user type!"); + ptrdiff_t taken = type_details[i].stringify(type_details[i].embedded ? embed.ptr : buffer.ptr, cast(char[])s, false); + if (taken > 0) + { + flags = Flags.User; + if (type_details[i].destroy) + flags |= Flags.NeedDestruction; + if (type_details[i].embedded) + { + flags |= Flags.Embedded; + alloc = cast(ushort)type_details[i].type_id; + } + else + { + void* object = defaultAllocator().alloc(type_details[i].size, type_details[i].alignment).ptr; + type_details[i].copy_emplace(buffer.ptr, object, true); + if (type_details[i].destroy) + type_details[i].destroy(buffer.ptr); + ptr = object; + count = type_details[i].type_id; + alloc = i; + } + return taken; + } + } + + // what is this? + assert(false, "Can't parse variant from string"); + } + + package: union Value { @@ -586,6 +1022,19 @@ package: Type type() const pure => cast(Type)(flags & Flags.TypeMask); + uint userType() const pure + { + if (flags & Flags.Embedded) + return alloc; // short id + return count; // long id + } + inout(void)* userPtr() inout pure + { + if (flags & Flags.Embedded) + return embed.ptr; + return ptr; + } + ref inout(Array!Variant) nodeArray() @property inout pure => *cast(inout(Array!Variant)*)&value.n; void takeNodeArray(ref Array!Variant arr) @@ -611,18 +1060,19 @@ package: nodeArray.destroy!false(); else if (t == Type.User) { - if (flags & Flags.Embedded) - findTypeDetails(alloc).destroy(embed.ptr); - else - findTypeDetails(count).destroy(ptr); + ref const TypeDetails td = (flags & Flags.Embedded) ? find_type_details(alloc) : type_details[alloc]; + if (td.destroy) + td.destroy(userPtr); + if (!(flags & Flags.Embedded)) + defaultAllocator().free(ptr[0..td.size]); } } enum Type : ushort { Null = 0, - False = 1, - True = 2, + True = 1, + False = 2, Number = 3, String = 4, Array = 5, @@ -696,8 +1146,14 @@ import urt.hash : fnv1a; static assert(Variant.sizeof == 16); static assert(Variant.Type.max <= Variant.Flags.TypeMask); -enum uint UserTypeId(T) = fnv1a(cast(const(ubyte)[])T.stringof); // maybe this isn't a good enough hash? -enum uint UserTypeShortId(T) = cast(ushort)UserTypeId!T ^ (UserTypeId!T >> 16); +template UserTypeId(T) +{ + enum uint Hash = fnv1a(cast(const(ubyte)[])T.stringof); // maybe this isn't a good enough hash? + static if (!EmbedUserType!T) + enum uint UserTypeId = Hash; + else + enum ushort UserTypeId = cast(ushort)Hash ^ (Hash >> 16); +} enum bool EmbedUserType(T) = is(T == struct) && T.sizeof <= Variant.embed.sizeof - 2 && T.alignof <= Variant.alignof; enum bool UserTypeReturnByRef(T) = is(T == struct); @@ -717,42 +1173,95 @@ template MakeTypeDetails(T) // TODO: we can probably NOT do this for class types, and just use RTTI instead... shared static this() { - assert(numTypeDetails < typeDetails.length, "Too many user types!"); - - TypeDetails* ty = &typeDetails[numTypeDetails++]; - static if (EmbedUserType!T) - ty.typeId = UserTypeShortId!T; - else - ty.typeId = UserTypeId!T; - // TODO: I'd like to not generate a destroy function if the data is POD - ty.destroy = (void* val) { - static if (!is(T == class)) - destroy!false(*cast(T*)val); - }; - ty.stringify = (const void* val, char[] buffer) { - import urt.string.format : toString; - return toString(*cast(T*)val, buffer); - }; + assert(num_type_details < type_details.length, "Too many user types!"); + type_details[num_type_details++] = TypeDetailsFor!T; } alias MakeTypeDetails = void; } +ushort type_detail_index(T)() + if (ValidUserType!T) +{ + foreach (i; 0 .. num_type_details) + if (type_details[i].type_id == UserTypeId!T) + return i; + assert(false, "Why wasn't the type registered?"); +} + struct TypeDetails { - uint typeId; + uint type_id; + ushort size; + ubyte alignment; + bool embedded; + void function(void* src, void* dst, bool move) nothrow @nogc copy_emplace; void function(void* val) nothrow @nogc destroy; - ptrdiff_t function(const void* val, char[] buffer) nothrow @nogc stringify; + ptrdiff_t function(void* val, char[] buffer, bool format) nothrow @nogc stringify; + int function(const void* a, const void* b, int type) pure nothrow @nogc cmp; } -TypeDetails[8] typeDetails; -size_t numTypeDetails = 0; +__gshared TypeDetails[8] type_details; +__gshared ushort num_type_details = 0; -ref TypeDetails findTypeDetails(uint typeId) +ref TypeDetails find_type_details(uint type_id) { - foreach (i, ref td; typeDetails[0 .. numTypeDetails]) + foreach (i, ref td; type_details[0 .. num_type_details]) { - if (td.typeId == typeId) + if (td.type_id == type_id) return td; } assert(false, "TypeDetails not found!"); } +ref TypeDetails get_type_details(uint index) +{ + debug assert(index < num_type_details); + return type_details[index]; +} + +enum TypeDetailsFor(T) = TypeDetails(UserTypeId!T, + T.sizeof, + T.alignof, + EmbedUserType!T, + // moveEmplace + is(T == class) ? null : (void* src, void* dst, bool move) { + if (move) + moveEmplace(*cast(T*)src, *cast(T*)dst); + else + *cast(T*)dst = *cast(const T*)src; + }, + // destroy + is(T == class) ? null : (void* val) { + destroy!false(*cast(T*)val); + }, + // stringify + (void* val, char[] buffer, bool format) { + import urt.string.format : toString; + if (format) + return toString(*cast(const T*)val, buffer); + else + { + static if (__traits(compiles, { buffer.parse!T(*cast(T*)val); })) + return buffer.parse!T(*cast(T*)val); + else + return -1; + } + }, + // cmp + (const void* pa, const void* pb, int type) { + ref const T a = *cast(const T*)pa; + ref const T b = *cast(const T*)pb; + switch (type) + { + case 0: + static if (__traits(compiles, { a.opCmp(b); })) + return a.opCmp(b); + else + return a < b ? -1 : a > b ? 1 : 0; + case 1: + return a == b ? 1 : 0; + case 2: + return a is b ? 1 : 0; + default: + assert(false); + } + }); From 225132d41428c14baef7e7243f65f645fa197867 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 17 Aug 2025 17:26:03 +1000 Subject: [PATCH 006/138] Added ChaCha stream cypher --- src/urt/crypto/chacha.d | 299 ++++++++++++++++++++++++++++++++++++++++ src/urt/digest/md5.d | 4 +- src/urt/digest/sha.d | 8 +- src/urt/encoding.d | 45 ++++-- 4 files changed, 339 insertions(+), 17 deletions(-) create mode 100644 src/urt/crypto/chacha.d diff --git a/src/urt/crypto/chacha.d b/src/urt/crypto/chacha.d new file mode 100644 index 0000000..2f263db --- /dev/null +++ b/src/urt/crypto/chacha.d @@ -0,0 +1,299 @@ +module urt.crypto.chacha; + +import urt.endian : littleEndianToNative, nativeToLittleEndian; +import urt.mem; + +nothrow @nogc: + + +struct ChaChaContext +{ + uint[16] state; + union { + uint[16] keystream32; + ubyte[64] keystream8; + } + uint nonceLow; + ushort position; + ushort rounds; +} + + +void chacha_init_context(ref ChaChaContext ctx, ref const ubyte[16] key, ulong nonce, ulong counter = 0, uint rounds = 20) pure +{ + chacha_init_context(ctx, key, expandNonce(nonce), counter, rounds); +} + +void chacha_init_context(ref ChaChaContext ctx, ref const ubyte[32] key, ulong nonce, ulong counter = 0, uint rounds = 20) pure +{ + chacha_init_context(ctx, key, expandNonce(nonce), counter, rounds); +} + +void chacha_init_context(ref ChaChaContext ctx, ref const ubyte[16] key, ref const ubyte[12] nonce, ulong counter = 0, uint rounds = 20) pure +{ + ubyte[32] key256 = void; + key256[0 .. 16] = key[]; + key256[16 .. 32] = key[]; + chacha_init_context(ctx, key256, nonce, counter, rounds); +} + +void chacha_init_context(ref ChaChaContext ctx, ref const ubyte[32] key, ref const ubyte[12] nonce, ulong counter = 0, uint rounds = 20) pure +{ + // the number of rounds must be 8, 12 or 20 + assert (rounds == 8 || rounds == 12 || rounds == 20); + ctx.rounds = cast(ushort)rounds / 2; + + ctx.nonceLow = nonce[0..4].littleEndianToNative!uint; + + // the string "expand 32-byte k" in little-endian + enum uint[4] magic_constant = [ 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 ]; + + ctx.state[0] = magic_constant[0]; + ctx.state[1] = magic_constant[1]; + ctx.state[2] = magic_constant[2]; + ctx.state[3] = magic_constant[3]; + ctx.state[4] = key[0..4].littleEndianToNative!uint; + ctx.state[5] = key[4..8].littleEndianToNative!uint; + ctx.state[6] = key[8..12].littleEndianToNative!uint; + ctx.state[7] = key[12..16].littleEndianToNative!uint; + ctx.state[8] = key[16..20].littleEndianToNative!uint; + ctx.state[9] = key[20..24].littleEndianToNative!uint; + ctx.state[10] = key[24..28].littleEndianToNative!uint; + ctx.state[11] = key[28..32].littleEndianToNative!uint; + ctx.state[12] = cast(uint)counter; + ctx.state[13] = ctx.nonceLow | (counter >> 32); + ctx.state[14] = nonce[4..8].littleEndianToNative!uint; + ctx.state[15] = nonce[8..12].littleEndianToNative!uint; + + ctx.keystream32[] = 0; + + // set starting position to the end of the key buffer; there are no bytes to consume yet + ctx.position = 64; +} + +static void chacha_set_counter(ref ChaChaContext ctx, ulong counter) pure +{ + ctx.state[12] = cast(uint)counter; + ctx.state[13] = ctx.nonceLow | (counter >> 32); +} + +void chacha_free_context(ref ChaChaContext ctx) pure +{ + ctx.state[] = 0; + ctx.keystream32[] = 0; + ctx.position = 0; + ctx.nonceLow = 0; + ctx.rounds = 0; +} + +size_t chacha_update(ref ChaChaContext ctx, const ubyte[] input, ubyte[] output) pure +{ + import urt.util : min; + + size_t size = input.length; + assert(output.length >= size); + + size_t offset = 0; + if (ctx.position < 64) + { + size_t startBytes = min(size, 64 - ctx.position); + output[0 .. startBytes] = input[0 .. startBytes] ^ ctx.keystream8[ctx.position .. 64]; + ctx.position += startBytes; + size -= startBytes; + offset = startBytes; + } + while (size >= 64) + { + chacha_block_next(ctx); + output[offset .. offset + 64] = input[offset .. offset + 64] ^ ctx.keystream8[]; + offset += 64; + size -= 64; + } + if (size > 0) + { + chacha_block_next(ctx); + output[offset .. offset + size] = input[offset .. offset + size] ^ ctx.keystream8[0 .. size]; + ctx.position = cast(ushort)size; + } + + return input.length; +} + + +size_t chacha_crypt(const ubyte[] input, ubyte[] output, ref const ubyte[32] key, ulong nonce, ulong counter = 0) pure +{ + return chacha_crypt(input, output, key, expandNonce(nonce), counter); +} + +size_t chacha_crypt(const ubyte[] input, ubyte[] output, ref const ubyte[32] key, ref const ubyte[12] nonce, ulong counter = 0) pure +{ + assert(output.length >= input.length); + + ChaChaContext ctx; + ctx.chacha_init_context(key, nonce, counter); + size_t r = ctx.chacha_update(input, output); + ctx.chacha_free_context(); + return r; +} + + +private: + +ubyte[12] expandNonce(ulong nonce) pure +{ + ubyte[12] nonceBytes = void; + nonceBytes[0 .. 4] = 0; + nonceBytes[4 .. 12] = nativeToLittleEndian(nonce); + return nonceBytes; +} + +pragma(inline, true) +uint rotl32(int n)(uint x) pure + => (x << n) | (x >> (32 - n)); + +pragma(inline, true) +void chacha_quarter_round(uint a, uint b, uint c, uint d)(ref uint[16] state) pure +{ + state[a] += state[b]; state[d] = rotl32!16(state[d] ^ state[a]); + state[c] += state[d]; state[b] = rotl32!12(state[b] ^ state[c]); + state[a] += state[b]; state[d] = rotl32!8(state[d] ^ state[a]); + state[c] += state[d]; state[b] = rotl32!7(state[b] ^ state[c]); +} + +void chacha_block_next(ref ChaChaContext ctx) pure +{ + // this is where the crazy voodoo magic happens. + // mix the bytes a lot and hope that nobody finds out how to undo it. + ctx.keystream32 = ctx.state; + + // TODO: we might like to unroll this...? + foreach (i; 0 .. ctx.rounds) + { + chacha_quarter_round!(0, 4, 8, 12)(ctx.keystream32); + chacha_quarter_round!(1, 5, 9, 13)(ctx.keystream32); + chacha_quarter_round!(2, 6, 10, 14)(ctx.keystream32); + chacha_quarter_round!(3, 7, 11, 15)(ctx.keystream32); + + chacha_quarter_round!(0, 5, 10, 15)(ctx.keystream32); + chacha_quarter_round!(1, 6, 11, 12)(ctx.keystream32); + chacha_quarter_round!(2, 7, 8, 13)(ctx.keystream32); + chacha_quarter_round!(3, 4, 9, 14)(ctx.keystream32); + } + + ctx.keystream32[] += ctx.state[]; + + // increment counter + uint* counter = &ctx.state[12]; + counter[0]++; + if (counter[0] == 0) + { + // wrap around occured, increment higher 32 bits of counter + counter[1]++; + // limited to 2^64 blocks of 64 bytes each. + // if you want to process more than 1180591620717411303424 bytes, you have other problems. + // we could keep counting with counter[2] and counter[3] (nonce), but then we risk reusing the nonce which is very bad! + assert(counter[1] != 0); + } +} + + +unittest +{ + import urt.encoding; + + immutable ubyte[32] test1_key = HexDecode!"0000000000000000000000000000000000000000000000000000000000000000"; + immutable ubyte[12] test1_nonce = HexDecode!"000000000000000000000000"; + immutable ubyte[64] test1_input = 0; + immutable ubyte[64] test1_output = [ + 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28, + 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, 0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7, + 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d, 0x77, 0x24, 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37, + 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c, 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86 + ]; + + immutable ubyte[32] test2_key = HexDecode!"0000000000000000000000000000000000000000000000000000000000000001"; + immutable ubyte[12] test2_nonce = HexDecode!"000000000000000000000002"; + immutable ubyte[375] test2_input = [ + 0x41, 0x6e, 0x79, 0x20, 0x73, 0x75, 0x62, 0x6d,0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x74, + 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x49, 0x45,0x54, 0x46, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x6e, + 0x64, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74,0x68, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x6f, 0x72, 0x20, 0x66,0x6f, 0x72, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61,0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x6f, 0x72, + 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6f, 0x66,0x20, 0x61, 0x6e, 0x20, 0x49, 0x45, 0x54, 0x46, + 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65,0x74, 0x2d, 0x44, 0x72, 0x61, 0x66, 0x74, 0x20, + 0x6f, 0x72, 0x20, 0x52, 0x46, 0x43, 0x20, 0x61,0x6e, 0x64, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, 0x74,0x20, 0x6d, 0x61, 0x64, 0x65, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65,0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, + 0x20, 0x6f, 0x66, 0x20, 0x61, 0x6e, 0x20, 0x49,0x45, 0x54, 0x46, 0x20, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x69, 0x74, 0x79, 0x20, 0x69, 0x73, 0x20,0x63, 0x6f, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, + 0x65, 0x64, 0x20, 0x61, 0x6e, 0x20, 0x22, 0x49,0x45, 0x54, 0x46, 0x20, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e,0x22, 0x2e, 0x20, 0x53, 0x75, 0x63, 0x68, 0x20, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e,0x74, 0x73, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x20, 0x6f, 0x72, 0x61, 0x6c, 0x20,0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x49, 0x45,0x54, 0x46, 0x20, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x61, 0x73, 0x20,0x77, 0x65, 0x6c, 0x6c, 0x20, 0x61, 0x73, 0x20, + 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20,0x61, 0x6e, 0x64, 0x20, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x72, 0x6f, 0x6e, 0x69, 0x63, 0x20, 0x63,0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6d, 0x61,0x64, 0x65, 0x20, 0x61, 0x74, 0x20, 0x61, 0x6e, + 0x79, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x6f,0x72, 0x20, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x2c, + 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x61,0x72, 0x65, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f + ]; + immutable ubyte[375] test2_output = [ + 0xa3, 0xfb, 0xf0, 0x7d, 0xf3, 0xfa, 0x2f, 0xde,0x4f, 0x37, 0x6c, 0xa2, 0x3e, 0x82, 0x73, 0x70, + 0x41, 0x60, 0x5d, 0x9f, 0x4f, 0x4f, 0x57, 0xbd,0x8c, 0xff, 0x2c, 0x1d, 0x4b, 0x79, 0x55, 0xec, + 0x2a, 0x97, 0x94, 0x8b, 0xd3, 0x72, 0x29, 0x15,0xc8, 0xf3, 0xd3, 0x37, 0xf7, 0xd3, 0x70, 0x05, + 0x0e, 0x9e, 0x96, 0xd6, 0x47, 0xb7, 0xc3, 0x9f,0x56, 0xe0, 0x31, 0xca, 0x5e, 0xb6, 0x25, 0x0d, + 0x40, 0x42, 0xe0, 0x27, 0x85, 0xec, 0xec, 0xfa,0x4b, 0x4b, 0xb5, 0xe8, 0xea, 0xd0, 0x44, 0x0e, + 0x20, 0xb6, 0xe8, 0xdb, 0x09, 0xd8, 0x81, 0xa7,0xc6, 0x13, 0x2f, 0x42, 0x0e, 0x52, 0x79, 0x50, + 0x42, 0xbd, 0xfa, 0x77, 0x73, 0xd8, 0xa9, 0x05,0x14, 0x47, 0xb3, 0x29, 0x1c, 0xe1, 0x41, 0x1c, + 0x68, 0x04, 0x65, 0x55, 0x2a, 0xa6, 0xc4, 0x05,0xb7, 0x76, 0x4d, 0x5e, 0x87, 0xbe, 0xa8, 0x5a, + 0xd0, 0x0f, 0x84, 0x49, 0xed, 0x8f, 0x72, 0xd0,0xd6, 0x62, 0xab, 0x05, 0x26, 0x91, 0xca, 0x66, + 0x42, 0x4b, 0xc8, 0x6d, 0x2d, 0xf8, 0x0e, 0xa4,0x1f, 0x43, 0xab, 0xf9, 0x37, 0xd3, 0x25, 0x9d, + 0xc4, 0xb2, 0xd0, 0xdf, 0xb4, 0x8a, 0x6c, 0x91,0x39, 0xdd, 0xd7, 0xf7, 0x69, 0x66, 0xe9, 0x28, + 0xe6, 0x35, 0x55, 0x3b, 0xa7, 0x6c, 0x5c, 0x87,0x9d, 0x7b, 0x35, 0xd4, 0x9e, 0xb2, 0xe6, 0x2b, + 0x08, 0x71, 0xcd, 0xac, 0x63, 0x89, 0x39, 0xe2,0x5e, 0x8a, 0x1e, 0x0e, 0xf9, 0xd5, 0x28, 0x0f, + 0xa8, 0xca, 0x32, 0x8b, 0x35, 0x1c, 0x3c, 0x76,0x59, 0x89, 0xcb, 0xcf, 0x3d, 0xaa, 0x8b, 0x6c, + 0xcc, 0x3a, 0xaf, 0x9f, 0x39, 0x79, 0xc9, 0x2b,0x37, 0x20, 0xfc, 0x88, 0xdc, 0x95, 0xed, 0x84, + 0xa1, 0xbe, 0x05, 0x9c, 0x64, 0x99, 0xb9, 0xfd,0xa2, 0x36, 0xe7, 0xe8, 0x18, 0xb0, 0x4b, 0x0b, + 0xc3, 0x9c, 0x1e, 0x87, 0x6b, 0x19, 0x3b, 0xfe,0x55, 0x69, 0x75, 0x3f, 0x88, 0x12, 0x8c, 0xc0, + 0x8a, 0xaa, 0x9b, 0x63, 0xd1, 0xa1, 0x6f, 0x80,0xef, 0x25, 0x54, 0xd7, 0x18, 0x9c, 0x41, 0x1f, + 0x58, 0x69, 0xca, 0x52, 0xc5, 0xb8, 0x3f, 0xa3,0x6f, 0xf2, 0x16, 0xb9, 0xc1, 0xd3, 0x00, 0x62, + 0xbe, 0xbc, 0xfd, 0x2d, 0xc5, 0xbc, 0xe0, 0x91,0x19, 0x34, 0xfd, 0xa7, 0x9a, 0x86, 0xf6, 0xe6, + 0x98, 0xce, 0xd7, 0x59, 0xc3, 0xff, 0x9b, 0x64,0x77, 0x33, 0x8f, 0x3d, 0xa4, 0xf9, 0xcd, 0x85, + 0x14, 0xea, 0x99, 0x82, 0xcc, 0xaf, 0xb3, 0x41,0xb2, 0x38, 0x4d, 0xd9, 0x02, 0xf3, 0xd1, 0xab, + 0x7a, 0xc6, 0x1d, 0xd2, 0x9c, 0x6f, 0x21, 0xba,0x5b, 0x86, 0x2f, 0x37, 0x30, 0xe3, 0x7c, 0xfd, + 0xc4, 0xfd, 0x80, 0x6c, 0x22, 0xf2, 0x21 + ]; + + immutable ubyte[32] test3_key = HexDecode!"c46ec1b18ce8a878725a37e780dfb7351f68ed2e194c79fbc6aebee1a667975d"; + enum ulong test3_nonce = 0x218268cfd531da1a; + immutable ubyte[127] test3_input = 0; + immutable ubyte[127] test3_output = [ + 0xf6, 0x3a, 0x89, 0xb7, 0x5c, 0x22, 0x71, 0xf9,0x36, 0x88, 0x16, 0x54, 0x2b, 0xa5, 0x2f, 0x06, + 0xed, 0x49, 0x24, 0x17, 0x92, 0x30, 0x2b, 0x00,0xb5, 0xe8, 0xf8, 0x0a, 0xe9, 0xa4, 0x73, 0xaf, + 0xc2, 0x5b, 0x21, 0x8f, 0x51, 0x9a, 0xf0, 0xfd,0xd4, 0x06, 0x36, 0x2e, 0x8d, 0x69, 0xde, 0x7f, + 0x54, 0xc6, 0x04, 0xa6, 0xe0, 0x0f, 0x35, 0x3f,0x11, 0x0f, 0x77, 0x1b, 0xdc, 0xa8, 0xab, 0x92, + 0xe5, 0xfb, 0xc3, 0x4e, 0x60, 0xa1, 0xd9, 0xa9,0xdb, 0x17, 0x34, 0x5b, 0x0a, 0x40, 0x27, 0x36, + 0x85, 0x3b, 0xf9, 0x10, 0xb0, 0x60, 0xbd, 0xf1,0xf8, 0x97, 0xb6, 0x29, 0x0f, 0x01, 0xd1, 0x38, + 0xae, 0x2c, 0x4c, 0x90, 0x22, 0x5b, 0xa9, 0xea,0x14, 0xd5, 0x18, 0xf5, 0x59, 0x29, 0xde, 0xa0, + 0x98, 0xca, 0x7a, 0x6c, 0xcf, 0xe6, 0x12, 0x27,0x05, 0x3c, 0x84, 0xe4, 0x9a, 0x4a, 0x33 + ]; + + ubyte[375] output = void; + + size_t ret = test1_input.chacha_crypt(output, test1_key, test1_nonce); + assert(ret == test1_input.length); + assert(output[0 .. test1_output.length] == test1_output[]); + + ChaChaContext ctx; + ctx.chacha_init_context(test2_key, test2_nonce, 1); + ret = ctx.chacha_update(test2_input[0 .. 17], output[0 .. 17]); + ret += ctx.chacha_update(test2_input[17 .. $], output[17 .. $]); + assert(ret == test2_input.length); + assert(output[0 .. test2_output.length] == test2_output[]); + + ret = test3_input.chacha_crypt(output, test3_key, test3_nonce); + assert(ret == test3_input.length); + assert(output[0 .. test3_output.length] == test3_output[]); +} diff --git a/src/urt/digest/md5.d b/src/urt/digest/md5.d index ba1c4c7..e0a59d6 100644 --- a/src/urt/digest/md5.d +++ b/src/urt/digest/md5.d @@ -93,12 +93,12 @@ unittest MD5Context ctx; md5_init(ctx); auto digest = md5_finalise(ctx); - assert(digest == Hex!"d41d8cd98f00b204e9800998ecf8427e"); + assert(digest == HexDecode!"d41d8cd98f00b204e9800998ecf8427e"); md5_init(ctx); md5_update(ctx, "Hello, World!"); digest = md5_finalise(ctx); - assert(digest == Hex!"65a8e27d8879283831b664bd8b7f0ad4"); + assert(digest == HexDecode!"65a8e27d8879283831b664bd8b7f0ad4"); } diff --git a/src/urt/digest/sha.d b/src/urt/digest/sha.d index 197b7f4..671a6fa 100644 --- a/src/urt/digest/sha.d +++ b/src/urt/digest/sha.d @@ -117,22 +117,22 @@ unittest SHA1Context ctx; sha_init(ctx); auto digest = sha_finalise(ctx); - assert(digest == Hex!"da39a3ee5e6b4b0d3255bfef95601890afd80709"); + assert(digest == HexDecode!"da39a3ee5e6b4b0d3255bfef95601890afd80709"); sha_init(ctx); sha_update(ctx, "Hello, World!"); digest = sha_finalise(ctx); - assert(digest == Hex!"0a0a9f2a6772942557ab5355d76af442f8f65e01"); + assert(digest == HexDecode!"0a0a9f2a6772942557ab5355d76af442f8f65e01"); SHA256Context ctx2; sha_init(ctx2); auto digest2 = sha_finalise(ctx2); - assert(digest2 == Hex!"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + assert(digest2 == HexDecode!"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); sha_init(ctx2); sha_update(ctx2, "Hello, World!"); digest2 = sha_finalise(ctx2); - assert(digest2 == Hex!"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"); + assert(digest2 == HexDecode!"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"); } diff --git a/src/urt/encoding.d b/src/urt/encoding.d index 54eb70d..43fa85e 100644 --- a/src/urt/encoding.d +++ b/src/urt/encoding.d @@ -3,13 +3,17 @@ module urt.encoding; nothrow @nogc: -enum Hex(const char[] s) = (){ ubyte[s.length / 2] r; ptrdiff_t len = hex_decode(s, r); assert(len == r.sizeof, "Not a hex string!"); return r; }(); -enum Base64(const char[] s) = (){ ubyte[base64_decode_length(s)] r; ptrdiff_t len = base64_decode(s, r); assert(len == r.sizeof, "Not a base64 string!"); return r; }(); +enum Base64Decode(string str) = () { ubyte[base64_decode_length(str.length)] r; size_t len = base64_decode(str, r[]); assert(len == r.sizeof, "Not a base64 string: " ~ str); return r; }(); +enum HexDecode(string str) = () { ubyte[hex_decode_length(str.length)] r; size_t len = hex_decode(str, r[]); assert(len == r.sizeof, "Not a hex string: " ~ str); return r; }(); +enum URLDecode(string str) = () { char[url_decode_length(str)] r; size_t len = url_decode(str, r[]); assert(len == r.sizeof, "Not a URL encoded string: " ~ str); return r; }(); ptrdiff_t base64_encode_length(size_t source_length) pure => (source_length + 2) / 3 * 4; +ptrdiff_t base64_encode_length(const void[] data) pure + => base64_encode_length(data.length); + ptrdiff_t base64_encode(const void[] data, char[] result) pure { auto src = cast(const(ubyte)[])data; @@ -55,7 +59,10 @@ ptrdiff_t base64_encode(const void[] data, char[] result) pure } ptrdiff_t base64_decode_length(size_t source_length) pure -=> source_length / 4 * 3; + => source_length / 4 * 3; + +ptrdiff_t base64_decode_length(const char[] data) pure + => base64_decode_length(data.length); ptrdiff_t base64_decode(const char[] data, void[] result) pure { @@ -74,6 +81,9 @@ ptrdiff_t base64_decode(const char[] data, void[] result) pure size_t j = 0; while (i < len) { + if (i > len - 4) + return -1; + // TODO: this could be faster by using more memory, store a full 256-byte table and no comparisons... uint b0 = data[i++] - 43; uint b1 = data[i++] - 43; @@ -88,10 +98,10 @@ ptrdiff_t base64_decode(const char[] data, void[] result) pure if (b3 >= 80) return -1; - b0 = base64_map.ptr[b0]; - b1 = base64_map.ptr[b1]; - b2 = base64_map.ptr[b2]; - b3 = base64_map.ptr[b3]; + b0 = base64_map[b0]; + b1 = base64_map[b1]; + b2 = base64_map[b2]; + b3 = base64_map[b3]; dest[j++] = cast(ubyte)((b0 << 2) | (b1 >> 4)); if (b2 != 64) @@ -109,6 +119,8 @@ unittest char[16] encoded = void; ubyte[12] decoded = void; + static assert(Base64Decode!"AQIDBAUGBwgJCgsM" == data[]); + size_t len = base64_encode(data, encoded); assert(len == 16); assert(encoded == "AQIDBAUGBwgJCgsM"); @@ -129,10 +141,13 @@ unittest len = base64_decode(encoded, decoded); assert(len == 10); assert(data[0..10] == decoded[0..10]); - -// static assert(Base64!"012345" == [0x01, 0x23, 0x45]); } +ptrdiff_t hex_encode_length(size_t sourceLength) pure + => sourceLength * 2; + +ptrdiff_t hex_encode_length(const void[] data) pure + => data.length * 2; ptrdiff_t hex_encode(const void[] data, char[] result) pure { @@ -142,6 +157,12 @@ ptrdiff_t hex_encode(const void[] data, char[] result) pure return toHexString(data, result).length; } +ptrdiff_t hex_decode_length(size_t sourceLength) pure + => sourceLength / 2; + +ptrdiff_t hex_decode_length(const char[] data) pure + => data.length / 2; + ptrdiff_t hex_decode(const char[] data, void[] result) pure { import urt.string.ascii : is_hex; @@ -180,14 +201,14 @@ unittest char[24] encoded = void; ubyte[12] decoded = void; + static assert(HexDecode!"0102030405060708090A0B0C" == data); + size_t len = hex_encode(data, encoded); assert(len == 24); assert(encoded == "0102030405060708090A0B0C"); len = hex_decode(encoded, decoded); assert(len == 12); assert(data == decoded); - - static assert(Hex!"012345" == [0x01, 0x23, 0x45]); } @@ -290,6 +311,8 @@ ptrdiff_t url_decode(const char[] data, char[] result) pure unittest { + static assert(URLDecode!"Hello%2C+World%21" == "Hello, World!"); + char[13] data = "Hello, World!"; char[17] encoded = void; char[13] decoded = void; From fa63396fa3a486fe40afd455c78fc758e54f099a Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 26 Aug 2025 11:23:00 +1000 Subject: [PATCH 007/138] Remove a couple of dangling phobos references. --- src/urt/lifetime.d | 12 ++++++------ src/urt/math.d | 1 - src/urt/range/package.d | 9 +++------ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/urt/lifetime.d b/src/urt/lifetime.d index acb04d1..cb6cf40 100644 --- a/src/urt/lifetime.d +++ b/src/urt/lifetime.d @@ -3,8 +3,6 @@ module urt.lifetime; T* emplace(T)(T* chunk) @safe pure { - import core.internal.lifetime : emplaceRef; - emplaceRef!T(*chunk); return chunk; } @@ -12,8 +10,6 @@ T* emplace(T)(T* chunk) @safe pure T* emplace(T, Args...)(T* chunk, auto ref Args args) if (is(T == struct) || Args.length == 1) { - import core.internal.lifetime : emplaceRef; - emplaceRef!T(*chunk, forward!args); return chunk; } @@ -73,8 +69,7 @@ T emplace(T, Args...)(void[] chunk, auto ref Args args) T* emplace(T, Args...)(void[] chunk, auto ref Args args) if (!is(T == class)) { - import core.internal.traits : Unqual; - import core.internal.lifetime : emplaceRef; + import urt.traits : Unqual; assert(chunk.length >= T.sizeof, "chunk size too small."); assert((cast(size_t) chunk.ptr) % T.alignof == 0, "emplace: Chunk is not aligned."); @@ -84,6 +79,11 @@ T* emplace(T, Args...)(void[] chunk, auto ref Args args) } +// HACK: we should port this to our lib... +static import core.internal.lifetime; +alias emplaceRef = core.internal.lifetime.emplaceRef; + + /+ void copyEmplace(S, T)(ref S source, ref T target) @system if (is(immutable S == immutable T)) diff --git a/src/urt/math.d b/src/urt/math.d index 236c867..66217d9 100644 --- a/src/urt/math.d +++ b/src/urt/math.d @@ -2,7 +2,6 @@ module urt.math; import urt.intrinsic; import core.stdc.stdio; // For writeDebugf -import std.format; // For format version (LDC) version = GDC_OR_LDC; version (GNU) version = GDC_OR_LDC; diff --git a/src/urt/range/package.d b/src/urt/range/package.d index 3592b11..94af160 100644 --- a/src/urt/range/package.d +++ b/src/urt/range/package.d @@ -182,13 +182,10 @@ template reduce(fun...) Returns: the final result of the accumulator applied to the iterable - - Throws: `Exception` if `r` is empty +/ auto reduce(R)(R r) if (isIterable!R) { - import std.exception : enforce; alias E = Select!(is_input_range!R, ElementType!R, ForeachType!R); alias Args = STATIC_MAP!(ReduceSeedType!E, binfuns); @@ -199,7 +196,7 @@ template reduce(fun...) { static assert(r.length > 0); })) - enforce(!r.empty, "Cannot reduce an empty input range w/o an explicit seed value."); + assert(!r.empty, "Cannot reduce an empty input range w/o an explicit seed value."); Args result = r.front; r.popFront(); @@ -279,7 +276,7 @@ template reduce(fun...) static if (mustInitialize) if (initialized == false) { - import core.internal.lifetime : emplaceRef; + import urt.lifetime : emplaceRef; foreach (i, f; binfuns) emplaceRef!(Args[i])(args[i], e); initialized = true; @@ -323,7 +320,7 @@ template fold(fun...) } else { - import std.typecons : tuple; + import urt.meta : tuple; return reduce!fun(tuple(seeds), r); } } From da335b59da5351da6836dd9dc8a888607953930a Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 2 Sep 2025 13:10:33 +1000 Subject: [PATCH 008/138] Tweak the Promise API --- src/urt/async.d | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/urt/async.d b/src/urt/async.d index 5da7e61..81e0a19 100644 --- a/src/urt/async.d +++ b/src/urt/async.d @@ -60,7 +60,7 @@ Promise!(ReturnType!Fun)* async(size_t stackSize = DefaultStackSize, Fun, Args.. void freePromise(T)(ref Promise!T* promise) { - assert(promise.state() != Promise!T.State.Pending, "Promise still pending!"); + assert(promise.state() != PromiseState.Pending, "Promise still pending!"); defaultAllocator().freeT(promise); promise = null; } @@ -84,15 +84,15 @@ void asyncUpdate() } -struct Promise(Result) +enum PromiseState { - enum State - { - Pending, - Ready, - Failed, - } + Pending, + Ready, + Failed +} +struct Promise(Result) +{ // construct using `async()` functions... this() @disable; this(ref typeof(this)) @disable; // disable copy constructor @@ -117,29 +117,29 @@ struct Promise(Result) } } - State state() const + PromiseState state() const { if (async.fibre.wasAborted()) - return State.Failed; + return PromiseState.Failed; else if (async.fibre.isFinished()) - return State.Ready; + return PromiseState.Ready; else - return State.Pending; + return PromiseState.Pending; } bool finished() const - => state() != State.Pending; + => state() != PromiseState.Pending; ref Result result() { - assert(state() == State.Ready, "Promise not fulfilled!"); + assert(state() == PromiseState.Ready, "Promise not fulfilled!"); static if (!is(Result == void)) return value; } void abort() { - assert(state() == State.Pending, "Promise already fulfilled!"); + assert(state() == PromiseState.Pending, "Promise already fulfilled!"); async.fibre.abort(); } @@ -180,11 +180,11 @@ unittest } auto p = async!fun(1, 2); - assert(p.state() == p.State.Ready); + assert(p.state() == PromiseState.Ready); assert(p.result() == 3); freePromise(p); p = async!fun(10, 20); - assert(p.state() == p.State.Ready); + assert(p.state() == PromiseState.Ready); assert(p.result() == 30); freePromise(p); @@ -201,13 +201,13 @@ unittest } auto p_yield = async(&fun_yield); - assert(p_yield.state() == p_yield.State.Pending); + assert(p_yield.state() == PromiseState.Pending); assert(val == 1); asyncUpdate(); - assert(p_yield.state() == p_yield.State.Pending); + assert(p_yield.state() == PromiseState.Pending); assert(val == 2); asyncUpdate(); - assert(p_yield.state() == p_yield.State.Ready); + assert(p_yield.state() == PromiseState.Ready); assert(val == 3); assert(p_yield.result() == 4); freePromise(p_yield); From c50deb8756eb449657cee93c812331e5732f14de Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 3 Sep 2025 11:53:53 +1000 Subject: [PATCH 009/138] Add StringResult --- src/urt/result.d | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/urt/result.d b/src/urt/result.d index 3769e8b..2462386 100644 --- a/src/urt/result.d +++ b/src/urt/result.d @@ -19,6 +19,24 @@ nothrow @nogc: => systemCode != 0; } +struct StringResult +{ +nothrow @nogc: + enum success = StringResult(); + + const(char)[] message = null; + + bool opCast(T : bool)() const + => message is null; + + bool succeeded() const + => message is null; + bool failed() const + => message !is null; +} + +// TODO: should we have a way to convert Result to StringResult, so we can format error messages? + version (Windows) { From cccff2615a6c227f9af5ebf7234ed6e8fa93b83c Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 3 Sep 2025 17:00:17 +1000 Subject: [PATCH 010/138] Minor improvements to urt.time --- src/urt/time.d | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/urt/time.d b/src/urt/time.d index 901884a..d1d0e00 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -100,14 +100,22 @@ pure nothrow @nogc: import urt.string.format : FormatArg; ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const { - long ms = (ticks != 0 ? appTime(this) : Duration()).as!"msecs"; - if (!buffer.ptr) - return 2 + timeToString(ms, null); - if (buffer.length < 2) - return -1; - buffer[0..2] = "T+"; - ptrdiff_t len = timeToString(ms, buffer[2..$]); - return len < 0 ? len : 2 + len; + static if (clock == Clock.SystemTime) + { + DateTime dt = getDateTime(this); + return dt.toString(buffer, format, formatArgs); + } + else + { + long ms = (ticks != 0 ? appTime(this) : Duration()).as!"msecs"; + if (!buffer.ptr) + return 2 + timeToString(ms, null); + if (buffer.length < 2) + return -1; + buffer[0..2] = "T+"; + ptrdiff_t len = timeToString(ms, buffer[2..$]); + return len < 0 ? len : 2 + len; + } } auto __debugOverview() const @@ -410,7 +418,7 @@ DateTime getDateTime() static assert(false, "TODO"); } -DateTime getDateTime(SysTime time) +DateTime getDateTime(SysTime time) pure { version (Windows) return fileTimeToDateTime(time); @@ -553,13 +561,14 @@ unittest version (Windows) { - DateTime fileTimeToDateTime(SysTime ftime) + DateTime fileTimeToDateTime(SysTime ftime) pure { version (BigEndian) static assert(false, "Only works in little endian!"); SYSTEMTIME stime; - FileTimeToSystemTime(cast(FILETIME*)&ftime.ticks, &stime); + alias PureHACK = extern(Windows) BOOL function(const(FILETIME)*, LPSYSTEMTIME) pure nothrow @nogc; + (cast(PureHACK)&FileTimeToSystemTime)(cast(FILETIME*)&ftime.ticks, &stime); DateTime dt; dt.year = stime.wYear; @@ -578,10 +587,11 @@ version (Windows) } else version (Posix) { - DateTime realtimeToDateTime(timespec ts) + DateTime realtimeToDateTime(timespec ts) pure { tm t; - gmtime_r(&ts.tv_sec, &t); + alias PureHACK = extern(C) tm* function(time_t* timer, tm* buf) pure nothrow @nogc; + (cast(PureHACK)&gmtime_r)(&ts.tv_sec, &t); DateTime dt; dt.year = cast(short)(t.tm_year + 1900); From aa707f58c62f5043634bc77f4c64bbd95c629c5c Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 4 Sep 2025 10:37:44 +1000 Subject: [PATCH 011/138] Improved the compare function a bit --- src/urt/algorithm.d | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index 9191f28..1e603fa 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -10,36 +10,44 @@ nothrow @nogc: auto compare(T, U)(auto ref T a, auto ref U b) { - static if (__traits(compiles, lvalue_of!T.opCmp(lvalue_of!U))) + static if (__traits(compiles, a.opCmp(b))) return a.opCmp(b); - else static if (__traits(compiles, lvalue_of!U.opCmp(lvalue_of!T))) + else static if (__traits(compiles, b.opCmp(a))) return -b.opCmp(a); - else static if (is(T : A[], A)) + else static if (is(T : A[], A) || is(U : B[], B)) { import urt.traits : is_primitive; + static assert(is(T : A[], A) && is(U : B[], B), "TODO: compare an array with a not-array?"); + auto ai = a.ptr; auto bi = b.ptr; - size_t len = a.length < b.length ? a.length : b.length; - static if (is_primitive!A) + + // first compere the pointers... + if (ai !is bi) { - // compare strings - foreach (i; 0 .. len) + size_t len = a.length < b.length ? a.length : b.length; + static if (is_primitive!A) { - if (ai[i] != bi[i]) - return ai[i] < bi[i] ? -1 : 1; + // compare strings + foreach (i; 0 .. len) + { + if (ai[i] != bi[i]) + return ai[i] < bi[i] ? -1 : 1; + } } - } - else - { - // compare arrays - foreach (i; 0 .. len) + else { - auto cmp = compare(ai[i], bi[i]); - if (cmp != 0) - return cmp; + // compare arrays + foreach (i; 0 .. len) + { + if (auto cmp = compare(ai[i], bi[i])) + return cmp; + } } } + + // finally, compare the lengths if (a.length == b.length) return 0; return a.length < b.length ? -1 : 1; From f2311afaac943e95ede4a16ca9cec75f992affd3 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 4 Sep 2025 10:54:17 +1000 Subject: [PATCH 012/138] Made CacheString pure, since the cache is immutable. --- src/urt/mem/string.d | 91 ++++++++++++++++++++++------------------- src/urt/string/string.d | 7 ++-- 2 files changed, 52 insertions(+), 46 deletions(-) diff --git a/src/urt/mem/string.d b/src/urt/mem/string.d index b00618a..1cefa4e 100644 --- a/src/urt/mem/string.d +++ b/src/urt/mem/string.d @@ -18,40 +18,48 @@ struct StringCache struct CacheString { +nothrow @nogc: alias toString this; - this(typeof(null)) pure nothrow @nogc + this(typeof(null)) pure { offset = 0; } - string toString() const nothrow @nogc + string toString() const pure { - ushort len = *cast(ushort*)(stringHeap.ptr + offset); - return cast(string)stringHeap[offset + 2 .. offset + 2 + len]; + // HACK: deploy the pure hack! + static char[] pureHack() nothrow @nogc => stringHeap; + string heap = (cast(immutable(char[]) function() pure nothrow @nogc)&pureHack)(); + + ushort len = *cast(ushort*)(heap.ptr + offset); + return heap[offset + 2 .. offset + 2 + len]; } - immutable(char)* ptr() const nothrow @nogc - => cast(immutable(char)*)(stringHeap.ptr + offset + 2); + immutable(char)* ptr() const pure + => toString().ptr; + + size_t length() const pure + => toString().length; - size_t length() const nothrow @nogc - => *cast(ushort*)(stringHeap.ptr + offset); + string opIndex() const pure + => toString(); - bool opCast(T : bool)() const pure nothrow @nogc + bool opCast(T : bool)() const pure => offset != 0; - void opAssign(typeof(null)) pure nothrow @nogc + void opAssign(typeof(null)) pure { offset = 0; } - bool opEquals(const(char)[] rhs) const nothrow @nogc + bool opEquals(const(char)[] rhs) const pure { string s = toString(); - return s.length == rhs.length && (s.ptr == rhs.ptr || s[] == rhs[]); + return s.length == rhs.length && (s.ptr is rhs.ptr || s[] == rhs[]); } - size_t toHash() const nothrow @nogc + size_t toHash() const pure { import urt.hash; @@ -79,13 +87,11 @@ void initStringHeap(uint stringHeapSize) nothrow assert(stringHeapInitialised == false, "String heap already initialised!"); assert(stringHeapSize <= ushort.max, "String heap too large!"); - stringHeap = new char[stringHeapSize]; + stringHeap = defaultAllocator.allocArray!char(stringHeapSize); // write the null string to the start - stringHeapCursor = 0; - stringHeap[stringHeapCursor++] = 0; - stringHeap[stringHeapCursor++] = 0; - numStrings = 1; + stringHeap[0..2] = 0; + stringHeapCursor = 2; stringHeapInitialised = true; } @@ -104,44 +110,46 @@ uint getStringHeapRemaining() nothrow @nogc return cast(uint)stringHeap.length - stringHeapCursor; } -CacheString addString(const(char)[] str, bool dedup = true) nothrow @nogc +CacheString addString(const(char)[] str) pure nothrow @nogc { - // null string - if (str.length == 0) - return CacheString(0); + // HACK: even though this mutates global state, the string cache is immutable after it's emplaced + // so, multiple calls with the same source string will always return the same result! + static CacheString impl(const(char)[] str) nothrow @nogc + { + // null string + if (str.length == 0) + return CacheString(0); - assert(str.length < 2^^14, "String longer than max string len (32768 chars)"); + assert(str.length < 2^^14, "String longer than max string len (32768 chars)"); - if (dedup) - { // first we scan to see if it's already in here... - for (ushort i = 1; i < stringHeapCursor;) + for (ushort i = 2; i < stringHeapCursor;) { ushort offset = i; - ushort len = stringHeap[i++]; - if (len >= 128) - len = (len & 0x7F) | ((stringHeap[i++] << 7) & 0x7F); + ushort len = *cast(ushort*)(stringHeap.ptr + i); + i += 2; if (len == str.length && stringHeap[i .. i + len] == str[]) return CacheString(offset); - i += len; + i += len + (len & 1); } - } - if (stringHeapCursor & 1) - stringHeap[stringHeapCursor++] = '\0'; + // add the string to the heap... + assert(stringHeapCursor + str.length < stringHeap.length, "String heap overflow!"); - // add the string to the heap... - assert(stringHeapCursor + str.length < stringHeap.length, "String heap overflow!"); + char[] heap = stringHeap[stringHeapCursor .. $]; + ushort offset = stringHeapCursor; - ushort offset = stringHeapCursor; - str.makeString(stringHeap[stringHeapCursor .. $]); - stringHeapCursor += str.length + 2; - ++numStrings; + *cast(ushort*)heap.ptr = cast(ushort)str.length; + heap[2 .. 2 + str.length] = str[]; + stringHeapCursor += str.length + 2; + if (stringHeapCursor & 1) + stringHeap[stringHeapCursor++] = '\0'; - return CacheString(offset); + return CacheString(offset); + } + return (cast(CacheString function(const(char)[]) pure nothrow @nogc)&impl)(str); } - void* allocWithStringCache(size_t bytes, String[] cachedStrings, const(char[])[] strings) nothrow @nogc { import urt.mem.alloc; @@ -191,7 +199,6 @@ private: __gshared bool stringHeapInitialised = false; __gshared char[] stringHeap = null; __gshared ushort stringHeapCursor = 0; -__gshared uint numStrings = 0; unittest diff --git a/src/urt/string/string.d b/src/urt/string/string.d index a6d73d4..b2f7abf 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -23,8 +23,7 @@ enum StringAlloc : ubyte TempString, // allocates in the temp ring buffer; could be overwritten at any time! // these must be last... (because comparison logic) - StringCache, // writes to the immutable string cache - StringCacheDedup, // writes to the immutable string cache with de-duplication + StringCache, // writes to the immutable string cache with de-duplication } struct StringAllocator @@ -87,11 +86,11 @@ String makeString(const(char)[] s, StringAlloc allocator, void* userData = null) { return String(writeString(cast(char*)tempAllocator().alloc(2 + s.length, 2).ptr + 2, s), false); } - else if (allocator >= StringAlloc.StringCache) + else if (allocator == StringAlloc.StringCache) { import urt.mem.string : CacheString, addString; - CacheString cs = s.addString(allocator == StringAlloc.StringCacheDedup); + CacheString cs = s.addString(); return String(cs.ptr, false); } assert(false, "Invalid string allocator"); From 72fc423fb18cb0257d62585f2eea51fad4c1de82 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 4 Sep 2025 11:45:59 +1000 Subject: [PATCH 013/138] Improved user variants. - can resolve class variants to base classes - added pure hack for TypeDetails lookup, since the table is immutable after construction - const-correctness fixes - readability improvements --- src/urt/variant.d | 283 +++++++++++++++++++++++++++++++--------------- 1 file changed, 192 insertions(+), 91 deletions(-) diff --git a/src/urt/variant.d b/src/urt/variant.d index cc89cf5..e95a557 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -15,6 +15,7 @@ nothrow @nogc: enum ValidUserType(T) = (is(T == struct) || is(T == class)) && + is(Unqual!T == T) && !is(T == Variant) && !is(T == VariantKVP) && !is(T == Array!U, U) && @@ -213,6 +214,7 @@ nothrow @nogc: static if (is(T == class)) { count = UserTypeId!T; + alloc = type_detail_index!T(); ptr = cast(void*)thing; } else static if (EmbedUserType!T) @@ -547,15 +549,32 @@ nothrow @nogc: => flags == Flags.Array; bool isObject() const pure => flags == Flags.Map; + bool isUserType() const pure + => (flags & Flags.TypeMask) == Type.User; bool isUser(T)() const pure - if (ValidUserType!T) + if (ValidUserType!(Unqual!T)) { + alias U = Unqual!T; if ((flags & Flags.TypeMask) != Type.User) return false; - static if (EmbedUserType!T) - return alloc == UserTypeId!T; + static if (EmbedUserType!U) + return alloc == UserTypeId!U; else - return count == UserTypeId!T; + { + if (count == UserTypeId!U) + return true; + static if (is(T == class)) + { + immutable(TypeDetails)* td = &get_type_details(alloc); + while (td.super_type_id) + { + if (td.super_type_id == UserTypeId!U) + return true; + td = &find_type_details(td.super_type_id); + } + } + return false; + } } bool canFitInt(I)() const pure @@ -690,27 +709,30 @@ nothrow @nogc: } ref inout(T) asUser(T)() inout pure - if (ValidUserType!T && UserTypeReturnByRef!T) + if (ValidUserType!(Unqual!T) && UserTypeReturnByRef!T) { - if (!isUser!T) - assert(false, "Variant is not a " ~ T.stringof); - static assert(!is(T == class), "Should be impossible?"); - static if (EmbedUserType!T) + alias U = Unqual!T; + if (!isUser!U) + assert(false, "Variant is not a " ~ U.stringof); + static assert(!is(U == class), "Should be impossible?"); + static if (EmbedUserType!U) return *cast(inout(T)*)embed.ptr; else return *cast(inout(T)*)ptr; } inout(T) asUser(T)() inout pure - if (ValidUserType!T && !UserTypeReturnByRef!T) + if (ValidUserType!(Unqual!T) && !UserTypeReturnByRef!T) { - if (!isUser!T) - assert(false, "Variant is not a " ~ T.stringof); - static if (is(T == class)) + alias U = Unqual!T; + if (!isUser!U) + assert(false, "Variant is not a " ~ U.stringof); + static if (is(U == class)) return cast(inout(T))ptr; - else static if (EmbedUserType!T) + else static if (EmbedUserType!U) { - T r = void; - TypeDetailsFor!T.copy_emplace(embed.ptr, &r, false); + // make a copy on the stack and return by value + U r = void; + TypeDetailsFor!U.copy_emplace(embed.ptr, &r, false); return r; } else @@ -718,7 +740,7 @@ nothrow @nogc: } auto as(T)() inout pure - if (!ValidUserType!T || !UserTypeReturnByRef!T) + if (!ValidUserType!(Unqual!T) || !UserTypeReturnByRef!T) { static if (is_some_int!T) { @@ -765,13 +787,13 @@ nothrow @nogc: else return asString; } - else static if (ValidUserType!T) + else static if (ValidUserType!(Unqual!T)) return asUser!T; else static assert(false, "TODO!"); } ref inout(T) as(T)() inout pure - if (ValidUserType!T && UserTypeReturnByRef!T) + if (ValidUserType!(Unqual!T) && UserTypeReturnByRef!T) => asUser!T; size_t length() const pure @@ -882,7 +904,7 @@ nothrow @nogc: if (flags & Flags.Embedded) return find_type_details(alloc).stringify(cast(void*)embed.ptr, buffer, true); else - return type_details[alloc].stringify(cast(void*)ptr, buffer, true); + return g_type_details[alloc].stringify(cast(void*)ptr, buffer, true); } } @@ -949,28 +971,29 @@ nothrow @nogc: align(64) void[256] buffer = void; this = null; // clear the object since we'll probably use the embed buffer... - foreach (ushort i; 0 .. num_type_details) + foreach (ushort i; 0 .. g_num_type_details) { - debug assert(type_details[i].alignment <= 64 && type_details[i].size <= buffer.sizeof, "Buffer is too small for user type!"); - ptrdiff_t taken = type_details[i].stringify(type_details[i].embedded ? embed.ptr : buffer.ptr, cast(char[])s, false); + ref immutable TypeDetails td = get_type_details(i); + debug assert(td.alignment <= 64 && td.size <= buffer.sizeof, "Buffer is too small for user type!"); + ptrdiff_t taken = td.stringify(td.embedded ? embed.ptr : buffer.ptr, cast(char[])s, false); if (taken > 0) { flags = Flags.User; - if (type_details[i].destroy) + if (td.destroy) flags |= Flags.NeedDestruction; - if (type_details[i].embedded) + if (td.embedded) { flags |= Flags.Embedded; - alloc = cast(ushort)type_details[i].type_id; + alloc = cast(ushort)td.type_id; } else { - void* object = defaultAllocator().alloc(type_details[i].size, type_details[i].alignment).ptr; - type_details[i].copy_emplace(buffer.ptr, object, true); - if (type_details[i].destroy) - type_details[i].destroy(buffer.ptr); + void* object = defaultAllocator().alloc(td.size, td.alignment).ptr; + td.copy_emplace(buffer.ptr, object, true); + if (td.destroy) + td.destroy(buffer.ptr); ptr = object; - count = type_details[i].type_id; + count = td.type_id; alloc = i; } return taken; @@ -1060,7 +1083,7 @@ package: nodeArray.destroy!false(); else if (t == Type.User) { - ref const TypeDetails td = (flags & Flags.Embedded) ? find_type_details(alloc) : type_details[alloc]; + ref const TypeDetails td = (flags & Flags.Embedded) ? find_type_details(alloc) : g_type_details[alloc]; if (td.destroy) td.destroy(userPtr); if (!(flags & Flags.Embedded)) @@ -1169,22 +1192,25 @@ ptrdiff_t newline(char[] buffer, ref ptrdiff_t offset, int level) template MakeTypeDetails(T) { + static assert(is(Unqual!T == T), "Only instantiate for mutable types"); + // this is a hack which populates an array of user type details when the program starts // TODO: we can probably NOT do this for class types, and just use RTTI instead... shared static this() { - assert(num_type_details < type_details.length, "Too many user types!"); - type_details[num_type_details++] = TypeDetailsFor!T; + assert(g_num_type_details < g_type_details.length, "Too many user types!"); + g_type_details[g_num_type_details++] = TypeDetailsFor!T; } alias MakeTypeDetails = void; } -ushort type_detail_index(T)() +ushort type_detail_index(T)() pure if (ValidUserType!T) { - foreach (i; 0 .. num_type_details) - if (type_details[i].type_id == UserTypeId!T) + ushort count = (cast(ushort function() pure nothrow @nogc)&num_type_details)(); + foreach (ushort i; 0 .. count) + if (get_type_details(i).type_id == UserTypeId!T) return i; assert(false, "Why wasn't the type registered?"); } @@ -1192,6 +1218,7 @@ ushort type_detail_index(T)() struct TypeDetails { uint type_id; + uint super_type_id; ushort size; ubyte alignment; bool embedded; @@ -1200,68 +1227,142 @@ struct TypeDetails ptrdiff_t function(void* val, char[] buffer, bool format) nothrow @nogc stringify; int function(const void* a, const void* b, int type) pure nothrow @nogc cmp; } -__gshared TypeDetails[8] type_details; -__gshared ushort num_type_details = 0; +__gshared TypeDetails[8] g_type_details; +__gshared ushort g_num_type_details = 0; -ref TypeDetails find_type_details(uint type_id) +typeof(g_type_details)* type_details() => &g_type_details; +ushort num_type_details() => g_num_type_details; + +ref immutable(TypeDetails) find_type_details(uint type_id) pure { - foreach (i, ref td; type_details[0 .. num_type_details]) + auto tds = (cast(immutable(typeof(g_type_details)*) function() pure nothrow @nogc)&type_details)(); + ushort count = (cast(ushort function() pure nothrow @nogc)&num_type_details)(); + foreach (i, ref td; (*tds)[0 .. count]) { if (td.type_id == type_id) return td; } assert(false, "TypeDetails not found!"); } -ref TypeDetails get_type_details(uint index) +ref immutable(TypeDetails) get_type_details(uint index) pure { - debug assert(index < num_type_details); - return type_details[index]; + auto tds = (cast(immutable(typeof(g_type_details)*) function() pure nothrow @nogc)&type_details)(); + debug assert(index < g_num_type_details); + return (*tds)[index]; } -enum TypeDetailsFor(T) = TypeDetails(UserTypeId!T, - T.sizeof, - T.alignof, - EmbedUserType!T, - // moveEmplace - is(T == class) ? null : (void* src, void* dst, bool move) { - if (move) - moveEmplace(*cast(T*)src, *cast(T*)dst); - else - *cast(T*)dst = *cast(const T*)src; - }, - // destroy - is(T == class) ? null : (void* val) { - destroy!false(*cast(T*)val); - }, - // stringify - (void* val, char[] buffer, bool format) { - import urt.string.format : toString; - if (format) - return toString(*cast(const T*)val, buffer); - else - { - static if (__traits(compiles, { buffer.parse!T(*cast(T*)val); })) - return buffer.parse!T(*cast(T*)val); - else - return -1; - } - }, - // cmp - (const void* pa, const void* pb, int type) { - ref const T a = *cast(const T*)pa; - ref const T b = *cast(const T*)pb; - switch (type) - { - case 0: - static if (__traits(compiles, { a.opCmp(b); })) - return a.opCmp(b); - else - return a < b ? -1 : a > b ? 1 : 0; - case 1: - return a == b ? 1 : 0; - case 2: - return a is b ? 1 : 0; - default: - assert(false); - } - }); +public template TypeDetailsFor(T) + if (is(Unqual!T == T) && (is(T == struct) || is(T == class))) +{ + static if (is(T == class) && is(T S == super)) + { + alias Super = Unqual!S; + static if (!is(Super == Object)) + { + alias dummy = MakeTypeDetails!Super; + enum SuperTypeId = UserTypeId!Super; + } + else + enum ushort SuperTypeId = 0; + } + else + enum ushort SuperTypeId = 0; + + static if (!is(T == class)) + { + static void move_emplace_impl(void* src, void* dst, bool move) nothrow @nogc + { + if (move) + moveEmplace(*cast(T*)src, *cast(T*)dst); + else + { + static if (__traits(compiles, { *cast(T*)dst = *cast(const T*)src; })) + *cast(T*)dst = *cast(const T*)src; + else + assert(false, "Can't copy " ~ T.stringof); + } + } + enum move_emplace = &move_emplace_impl; + } + else + enum move_emplace = null; + + static if (!is(T == class) && is(typeof(destroy!(false, T)))) + { + static void destroy_impl(void* val) nothrow @nogc + { + destroy!false(*cast(T*)val); + } + enum destroy_fun = &destroy_impl; + } + else + enum destroy_fun = null; + + static ptrdiff_t stringify(void* val, char[] buffer, bool format) nothrow @nogc + { + import urt.string.format : toString; + if (format) + { + static if (is(typeof(toString!T))) + return toString(*cast(const T*)val, buffer); + else + return -1; + } + else + { + static if (is(typeof(parse!T))) + return buffer.parse!T(*cast(T*)val); + else + return -1; + } + } + + int compare(const void* pa, const void* pb, int type) pure nothrow @nogc + { + ref const T a = *cast(const T*)pa; + ref const T b = *cast(const T*)pb; + switch (type) + { + case 0: + static if (is(T == class) || is(T == U*, U) || is(T == V[], V)) + { + if (pa is pb) + return 0; + } + static if (__traits(compiles, { a.opCmp(b); })) + return a.opCmp(b); + else static if (__traits(compiles, { b.opCmp(a); })) + return -b.opCmp(a); + else + { + static if (is(T == class)) + { + ptrdiff_t r = cast(ptrdiff_t)pa - cast(ptrdiff_t)pb; + return r < 0 ? -1 : r > 0 ? 1 : 0; + } + else + return a < b ? -1 : a > b ? 1 : 0; + } + case 1: + static if (is(T == class) || is(T == U*, U) || is(T == V[], V)) + { + if (pa is pb) + return 1; + } + static if (__traits(compiles, { a.opEquals(b); })) + return a.opEquals(b); + else static if (__traits(compiles, { b.opEquals(a); })) + return b.opEquals(a); + else static if (!is(T == class) && !is(T == U*, U) && !is(T == V[], V)) + return a == b ? 1 : 0; + else + return 0; + case 2: + return pa is pb ? 1 : 0; + default: + assert(false); + } + } + + enum TypeDetailsFor = TypeDetails(UserTypeId!T, SuperTypeId, T.sizeof, T.alignof, EmbedUserType!T, move_emplace, destroy_fun, &stringify, &compare); +} From d1b1cfea3b369cc5f17ea73b2f7894fada4153ae Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 5 Sep 2025 15:13:24 +1000 Subject: [PATCH 014/138] The body of Array.concat was just completely missing! --- src/urt/array.d | 82 ++++++++++++++++++++----------------------------- 1 file changed, 34 insertions(+), 48 deletions(-) diff --git a/src/urt/array.d b/src/urt/array.d index 8d9858c..b83a65a 100644 --- a/src/urt/array.d +++ b/src/urt/array.d @@ -288,7 +288,18 @@ nothrow @nogc: } // manipulation - ref Array!(T, EmbedCount) concat(Things...)(auto ref Things things); + ref Array!(T, EmbedCount) concat(Things...)(auto ref Things things) + { + reserve(_length + things.length); + static foreach (i; 0 .. things.length) + { + static if (is(T == class) || is(T == interface)) + ptr[_length++] = things[i]; + else + emplace!T(&ptr[_length++], forward!(things[i])); + } + return this; + } bool empty() const => _length == 0; @@ -318,26 +329,20 @@ nothrow @nogc: { static if (is(T == class) || is(T == interface)) { - uint len = _length; - reserve(len + 1); - _length = len + 1; - for (uint i = len; i > 0; --i) + reserve(_length + 1); + for (uint i = _length++; i > 0; --i) ptr[i] = ptr[i-1]; - ptr[0] = item; - return ptr[0]; + return (ptr[0] = item); } else { - uint len = _length; - reserve(len + 1); - _length = len + 1; - for (uint i = len; i > 0; --i) + reserve(_length + 1); + for (uint i = _length++; i > 0; --i) { moveEmplace!T(ptr[i-1], ptr[i]); destroy!false(ptr[i-1]); } - emplace!T(&ptr[0], forward!item); - return ptr[0]; + return *emplace!T(&ptr[0], forward!item); } } @@ -351,45 +356,30 @@ nothrow @nogc: ref T pushBack(U)(auto ref U item) if (is(U : T)) { + reserve(_length + 1); static if (is(T == class) || is(T == interface)) - { - uint len = _length; - reserve(len + 1); - _length = len + 1; - ptr[len] = item; - return ptr[len]; - } + return (ptr[_length++] = item); else - { - uint len = _length; - reserve(len + 1); - _length = len + 1; - emplace!T(&ptr[len], forward!item); - return ptr[len]; - } + return *emplace!T(&ptr[_length++], forward!item); } ref T emplaceFront(Args...)(auto ref Args args) + if (!is(T == class) && !is(T == interface)) { - uint len = _length; - reserve(len + 1); - _length = len + 1; - for (uint i = len; i > 0; --i) + reserve(_length + 1); + for (uint i = _length++; i > 0; --i) { moveEmplace(ptr[i-1], ptr[i]); destroy!false(ptr[i-1]); } - emplace!T(&ptr[0], forward!args); - return ptr[0]; + retirn *emplace!T(&ptr[0], forward!args); } ref T emplaceBack(Args...)(auto ref Args args) + if (!is(T == class) && !is(T == interface)) { - uint len = _length; - reserve(len + 1); - _length = len + 1; - emplace!T(&ptr[len], forward!args); - return ptr[len]; + reserve(_length + 1); + return *emplace!T(&ptr[_length++], forward!args); } T popFront() @@ -414,7 +404,7 @@ nothrow @nogc: moveEmplace(ptr[i], ptr[i-1]); } destroy!false(ptr[--_length]); - return copy.move; + return copy; } } @@ -424,19 +414,15 @@ nothrow @nogc: static if (is(T == class) || is(T == interface)) { - uint last = _length-1; - T copy = ptr[last]; - ptr[last] = null; - _length = last; + T copy = ptr[--_length]; + ptr[_length] = null; return copy; } else { - uint last = _length-1; - T copy = ptr[last].move; - destroy!false(ptr[last]); - _length = last; - return copy.move; + T copy = ptr[--_length].move; + destroy!false(ptr[_length]); + return copy; } } From d2da65be3aca1384325b7cddab68adcd39eaee15 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 10 Sep 2025 17:41:25 +1000 Subject: [PATCH 015/138] Fix map range --- src/urt/map.d | 75 +++++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/src/urt/map.d b/src/urt/map.d index 732fc79..97a6f99 100644 --- a/src/urt/map.d +++ b/src/urt/map.d @@ -292,13 +292,13 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) => Range!(IterateBy.Keys, true)(pRoot); auto values() nothrow => Range!(IterateBy.Values)(pRoot); -// auto values() const nothrow -// => Range!(IterateBy.Values, true)(pRoot); + auto values() const nothrow + => Range!(IterateBy.Values, true)(pRoot); auto opIndex() nothrow => Range!(IterateBy.KVP)(pRoot); -// auto opIndex() const nothrow -// => Range!(IterateBy.KVP, true)(pRoot); + auto opIndex() const nothrow + => Range!(IterateBy.KVP, true)(pRoot); private: nothrow: @@ -657,9 +657,7 @@ public: PN n; ref const(K) key() @property const pure => n.kvp.key; -// ref const(V) value() @property const pure -// => n.kvp.value; - ref V value() @property pure + ref inout(V) value() @property inout pure => n.kvp.value; } return KV(n); @@ -900,7 +898,7 @@ unittest assert(map.get(2) is null); } - // Iteration (opApply) + // Iteration (range) { TestAVLTree map; map.insert(3, 30); @@ -908,25 +906,43 @@ unittest map.insert(2, 20); map.insert(4, 40); - int sumKeys = 0; - int sumValues = 0; - int count = 0; - // Iterate key-value pairs + int sumKeys = 0, sumValues = 0, count = 0; foreach (kv; map) { sumKeys += kv.key; sumValues += kv.value; count++; } + assert(count == 4); + assert(sumKeys == 1 + 2 + 3 + 4); + assert(sumValues == 10 + 20 + 30 + 40); + // Iterate const key-value pairs + ref const cmap = map; + sumKeys = sumValues = count = 0; + foreach (kv; cmap) + { + sumKeys += kv.key; + sumValues += kv.value; + count++; + } assert(count == 4); assert(sumKeys == 1 + 2 + 3 + 4); assert(sumValues == 10 + 20 + 30 + 40); - sumValues = 0; - count = 0; + // Iterate keys only + sumKeys = 0, count = 0; + foreach (v; map.keys) + { + sumKeys += v; + count++; + } + assert(count == 4); + assert(sumKeys == 1 + 2 + 3 + 4); + // Iterate values only + sumValues = 0, count = 0; foreach (v; map.values) { sumValues += v; @@ -935,6 +951,16 @@ unittest assert(count == 4); assert(sumValues == 10 + 20 + 30 + 40); + // Iterate const values only + sumValues = 0, count = 0; + foreach (v; cmap.values) + { + sumValues += v; + count++; + } + assert(count == 4); + assert(sumValues == 10 + 20 + 30 + 40); + // Test stopping iteration count = 0; foreach (k; map.keys) @@ -946,27 +972,6 @@ unittest assert(count == 2); // Should stop after 1 and 2 } - // Iteration (Iterator struct) - { - TestAVLTree map; - map.insert(3, 30); - map.insert(1, 10); - map.insert(2, 20); - map.insert(4, 40); - - foreach (kvp; map) // Uses Iterator internally - { - // Note: D's foreach over structs with opApply might not directly use the Iterator struct - // but opApply tests cover the iteration logic. - // This loop tests if the basic range primitives work. - // A direct Iterator test: - } - - // Test empty map iteration - TestAVLTree emptyMap; - assert(emptyMap[].empty); - } - // Test with string keys { alias StringMap = AVLTree!(const(char)[], int); From 760585b07c2c2bbffdb9883844b4ffd8a1cedb49 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 12 Sep 2025 10:01:56 +1000 Subject: [PATCH 016/138] Override accept inheriting blocking from listening socket. --- src/urt/socket.d | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/urt/socket.d b/src/urt/socket.d index e61e29c..304edee 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -285,6 +285,9 @@ Result accept(Socket socket, out Socket connection, InetAddress* connectingSocke return socket_getlasterror(); else if (connectingSocketAddress) *connectingSocketAddress = make_InetAddress(addr); + // platforms are inconsistent regarding whether accept inherits the listening socket's blocking mode + // for consistentency, we always set blocking on the accepted socket + connection.set_socket_option(SocketOption.non_blocking, false); return Result.success; } From cc271b9f6a3112bfbf4604908a140301a5048fd8 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 16 Sep 2025 13:57:28 +1000 Subject: [PATCH 017/138] Fixes to tstringz functions, which seemed to have 2 separate implementations! --- src/urt/mem/temp.d | 40 +++++++++++++------- src/urt/string/package.d | 80 +++++++++++----------------------------- 2 files changed, 47 insertions(+), 73 deletions(-) diff --git a/src/urt/mem/temp.d b/src/urt/mem/temp.d index aabe847..7c28cfc 100644 --- a/src/urt/mem/temp.d +++ b/src/urt/mem/temp.d @@ -16,11 +16,7 @@ void[] talloc(size_t size) nothrow @nogc assert(InFormatFunction == false, "It is illegal to use the temp allocator inside string conversion functions. Consider using stack or scratchpad."); } - if (size >= TempMemSize / 2) - { - assert(false, "Requested temp memory size is too large"); - return null; - } + assert(size <= TempMemSize / 2, "Requested temp memory size is too large"); if (alloc_offset + size > TempMemSize) alloc_offset = 0; @@ -73,17 +69,33 @@ void tfree(void[] mem) nothrow @nogc char* tstringz(const(char)[] str) nothrow @nogc { - if (str.length > TempMemSize / 2) - return null; - - size_t len = str.length; - if (alloc_offset + len + 1 > TempMemSize) - alloc_offset = 0; + char* r = cast(char*)talloc(str.length + 1).ptr; + r[0 .. str.length] = str[]; + r[str.length] = '\0'; + return r; +} +char* tstringz(const(wchar)[] str) nothrow @nogc +{ + import urt.string.uni : uni_convert; + char* r = cast(char*)talloc(str.length*3 + 1).ptr; + size_t len = uni_convert(str, r[0 .. str.length*3]); + r[len] = '\0'; + return r; +} - char* r = cast(char*)tempMem.ptr + alloc_offset; - r[0 .. len] = str[]; +wchar* twstringz(const(char)[] str) nothrow @nogc +{ + import urt.string.uni : uni_convert; + wchar* r = cast(wchar*)talloc(str.length*2 + 2).ptr; + size_t len = uni_convert(str, r[0 .. str.length]); r[len] = '\0'; - alloc_offset += len + 1; + return r; +} +wchar* twstringz(const(wchar)[] str) nothrow @nogc +{ + wchar* r = cast(wchar*)talloc(str.length*2 + 2).ptr; + r[0 .. str.length] = str[]; + r[str.length] = '\0'; return r; } diff --git a/src/urt/string/package.d b/src/urt/string/package.d index 6a103d0..4a807e2 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -7,50 +7,12 @@ public import urt.string.tailstring; // seful string operations defined elsewhere public import urt.array : empty, popFront, popBack, takeFront, takeBack; public import urt.mem : strlen; +public import urt.mem.temp : tstringz, twstringz; +nothrow @nogc: -enum TempStringBufferLen = 1024; -enum TempStringMaxLen = TempStringBufferLen / 2; -static char[TempStringBufferLen] s_tempStringBuffer; -static size_t s_tempStringBufferPos = 0; - - -char[] allocTempString(size_t len) nothrow @nogc -{ - assert(len <= TempStringMaxLen); - - if (len <= TempStringBufferLen - s_tempStringBufferPos) - { - char[] s = s_tempStringBuffer[s_tempStringBufferPos .. s_tempStringBufferPos + len]; - s_tempStringBufferPos += len; - return s; - } - s_tempStringBufferPos = len; - return s_tempStringBuffer[0 .. len]; -} - -char* tstringz(const(char)[] str) nothrow @nogc -{ - char[] buffer = allocTempString(str.length + 1); - buffer[0..str.length] = str[]; - buffer[str.length] = 0; - return buffer.ptr; -} - -wchar* twstringz(const(char)[] str) nothrow @nogc -{ - wchar[] buffer = cast(wchar[])allocTempString((str.length + 1) * 2); - - // TODO: actually decode UTF8 into UTF16!! - - foreach (i, c; str) - buffer[i] = c; - buffer[str.length] = 0; - return buffer.ptr; -} - -ptrdiff_t cmp(const(char)[] a, const(char)[] b) pure nothrow @nogc +ptrdiff_t cmp(const(char)[] a, const(char)[] b) pure { if (a.length != b.length) return a.length - b.length; @@ -63,7 +25,7 @@ ptrdiff_t cmp(const(char)[] a, const(char)[] b) pure nothrow @nogc return 0; } -ptrdiff_t icmp(const(char)[] a, const(char)[] b) pure nothrow @nogc +ptrdiff_t icmp(const(char)[] a, const(char)[] b) pure { if (a.length != b.length) return a.length - b.length; @@ -76,24 +38,24 @@ ptrdiff_t icmp(const(char)[] a, const(char)[] b) pure nothrow @nogc return 0; } -bool ieq(const(char)[] a, const(char)[] b) pure nothrow @nogc +bool ieq(const(char)[] a, const(char)[] b) pure => icmp(a, b) == 0; -bool startsWith(const(char)[] s, const(char)[] prefix) pure nothrow @nogc +bool startsWith(const(char)[] s, const(char)[] prefix) pure { if (s.length < prefix.length) return false; return s[0 .. prefix.length] == prefix[]; } -bool endsWith(const(char)[] s, const(char)[] suffix) pure nothrow @nogc +bool endsWith(const(char)[] s, const(char)[] suffix) pure { if (s.length < suffix.length) return false; return s[$ - suffix.length .. $] == suffix[]; } -inout(char)[] trim(bool Front = true, bool Back = true)(inout(char)[] s) pure nothrow @nogc +inout(char)[] trim(bool Front = true, bool Back = true)(inout(char)[] s) pure { size_t first = 0, last = s.length; static if (Front) @@ -113,7 +75,7 @@ alias trimFront = trim!(true, false); alias trimBack = trim!(false, true); -inout(char)[] trimComment(char Delimiter)(inout(char)[] s) +inout(char)[] trimComment(char Delimiter)(inout(char)[] s) pure { size_t i = 0; for (; i < s.length; ++i) @@ -126,7 +88,7 @@ inout(char)[] trimComment(char Delimiter)(inout(char)[] s) return s[0 .. i]; } -inout(char)[] takeLine(ref inout(char)[] s) pure nothrow @nogc +inout(char)[] takeLine(ref inout(char)[] s) pure { for (size_t i = 0; i < s.length; ++i) { @@ -148,7 +110,7 @@ inout(char)[] takeLine(ref inout(char)[] s) pure nothrow @nogc return t; } -inout(char)[] split(char Separator, bool HandleQuotes = true)(ref inout(char)[] s) +inout(char)[] split(char Separator, bool HandleQuotes = true)(ref inout(char)[] s) pure { static if (HandleQuotes) int inQuotes = 0; @@ -176,7 +138,7 @@ inout(char)[] split(char Separator, bool HandleQuotes = true)(ref inout(char)[] return t; } -inout(char)[] split(Separator...)(ref inout(char)[] s, out char sep) +inout(char)[] split(Separator...)(ref inout(char)[] s, out char sep) pure { sep = '\0'; int inQuotes = 0; @@ -204,7 +166,7 @@ inout(char)[] split(Separator...)(ref inout(char)[] s, out char sep) return t; } -char[] unQuote(const(char)[] s, char[] buffer) pure nothrow @nogc +char[] unQuote(const(char)[] s, char[] buffer) pure { // TODO: should this scan and match quotes rather than assuming there are no rogue closing quotes in the middle of the string? if (s.empty) @@ -223,18 +185,18 @@ char[] unQuote(const(char)[] s, char[] buffer) pure nothrow @nogc return buffer; } -char[] unQuote(char[] s) pure nothrow @nogc +char[] unQuote(char[] s) pure { return unQuote(s, s); } -char[] unQuote(const(char)[] s) nothrow @nogc +char[] unQuote(const(char)[] s) { import urt.mem.temp : talloc; return unQuote(s, cast(char[])talloc(s.length)); } -char[] unEscape(inout(char)[] s, char[] buffer) pure nothrow @nogc +char[] unEscape(inout(char)[] s, char[] buffer) pure { if (s.empty) return null; @@ -266,13 +228,13 @@ char[] unEscape(inout(char)[] s, char[] buffer) pure nothrow @nogc return buffer[0..len]; } -char[] unEscape(char[] s) pure nothrow @nogc +char[] unEscape(char[] s) pure { return unEscape(s, s); } -char[] toHexString(const(void[]) data, char[] buffer, uint group = 0, uint secondaryGroup = 0, const(char)[] seps = " -") pure nothrow @nogc +char[] toHexString(const(void[]) data, char[] buffer, uint group = 0, uint secondaryGroup = 0, const(char)[] seps = " -") pure { import urt.util : is_power_of_2; assert(group.is_power_of_2); @@ -307,7 +269,7 @@ char[] toHexString(const(void[]) data, char[] buffer, uint group = 0, uint secon } } -char[] toHexString(const(ubyte[]) data, uint group = 0, uint secondaryGroup = 0, const(char)[] seps = " -") nothrow @nogc +char[] toHexString(const(ubyte[]) data, uint group = 0, uint secondaryGroup = 0, const(char)[] seps = " -") { import urt.mem.temp; @@ -319,7 +281,7 @@ char[] toHexString(const(ubyte[]) data, uint group = 0, uint secondaryGroup = 0, unittest { - ubyte[] data = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]; + ubyte[8] data = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]; assert(data.toHexString(0) == "0123456789ABCDEF"); assert(data.toHexString(1) == "01 23 45 67 89 AB CD EF"); assert(data.toHexString(2) == "0123 4567 89AB CDEF"); @@ -329,7 +291,7 @@ unittest } -bool wildcardMatch(const(char)[] wildcard, const(char)[] value) +bool wildcardMatch(const(char)[] wildcard, const(char)[] value) pure { // TODO: write this function... From d225b3ac22fc768cd7ef882844bbc613e61ca778 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 15 Sep 2025 19:31:52 +1000 Subject: [PATCH 018/138] Properly detect integer variants fed via float (like numbers from command line). --- src/urt/math.d | 82 ++++++++++++++++++++++++++++++++++++++++++ src/urt/meta/package.d | 15 ++++++++ src/urt/variant.d | 32 ++++++++++++++--- 3 files changed, 125 insertions(+), 4 deletions(-) diff --git a/src/urt/math.d b/src/urt/math.d index 66217d9..3c66be8 100644 --- a/src/urt/math.d +++ b/src/urt/math.d @@ -3,6 +3,9 @@ module urt.math; import urt.intrinsic; import core.stdc.stdio; // For writeDebugf +// for arch where using FPU for int<->float conversions is preferred +//version = PreferFPUIntConv; + version (LDC) version = GDC_OR_LDC; version (GNU) version = GDC_OR_LDC; @@ -63,6 +66,85 @@ extern(C) double acos(double x); } +int float_is_integer(double f, out ulong i) +{ + version (PreferFPUIntConv) + { + if (!(f == f)) + return 0; // NaN + if (f < 0) + { + if (f < long.min) + return 0; // out of range + long t = cast(long)f; + if (cast(double)t != f) + return 0; // not an integer + i = cast(ulong)t; + return -1; + } + if (f >= ulong.max) + return 0; // out of range + ulong t = cast(ulong)f; + if (cast(double)t != f) + return 0; // not an integer + i = t; + return 1; + } + else + { + import urt.meta : bit_mask; + enum M = 52, E = 11, B = 1023; + + ulong u = *cast(const(ulong)*)&f; + int e = (u >> M) & bit_mask!E; + ulong m = u & bit_mask!M; + + if (e == bit_mask!E) + return 0; // NaN/Inf + if (e == 0) + { + if (m) + return 0; // denormal + i = 0; + return 1; // +/- 0 + } + int shift = e - B; + if (shift < 0) + return 0; // |f| < 1 + bool integral = shift >= M || (m & bit_mask(M - shift)) == 0; + if (!integral) + return 0; // not an integer + if (f < 0) + { + if (f < long.min) + return 0; // out of range + i = cast(ulong)cast(long)f; + return -1; + } + if (f >= ulong.max) + return 0; // out of range + i = cast(ulong)f; + return 1; + } +} + +unittest +{ + // this covers all the branches, but maybe test some extreme cases? + ulong i; + assert(float_is_integer(double.nan, i) == 0); + assert(float_is_integer(double.infinity, i) == 0); + assert(float_is_integer(-double.infinity, i) == 0); + assert(float_is_integer(double.max, i) == 0); + assert(float_is_integer(-double.max, i) == 0); + assert(float_is_integer(0.5, i) == 0); + assert(float_is_integer(1.5, i) == 0); + assert(float_is_integer(cast(double)ulong.max, i) == 0); + assert(float_is_integer(0.0, i) == 1 && i == 0); + assert(float_is_integer(-0.0, i) == 1 && i == 0); + assert(float_is_integer(200, i) == 1 && i == 200); + assert(float_is_integer(-200, i) == -1 && cast(long)i == -200); +} pragma(inline, true) bool addc(T = uint)(T a, T b, out T r, bool c_in) diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index 19f10f3..599d6c9 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -1,11 +1,26 @@ module urt.meta; +pure nothrow @nogc: alias Alias(alias a) = a; alias Alias(T) = T; alias AliasSeq(TList...) = TList; +ulong bit_mask(size_t bits) +{ + return (1UL << bits) - 1; +} + +template bit_mask(size_t bits, bool signed = false) +{ + static assert(bits <= 64, "bit_mask only supports up to 64 bits"); + static if (bits == 64) + enum IntForWidth!(64, signed) bit_mask = ~0UL; + else + enum IntForWidth!(bits, signed) bit_mask = (1UL << bits) - 1; +} + template IntForWidth(size_t bits, bool signed = false) { static if (bits <= 8 && !signed) diff --git a/src/urt/variant.d b/src/urt/variant.d index e95a557..52c004c 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -119,11 +119,35 @@ nothrow @nogc: this(F)(F f) if (is_some_float!F) { - static if (is(F == float)) - flags = Flags.NumberFloat; + import urt.math : float_is_integer; + + if (int sign = float_is_integer(f, value.ul)) + { + if (sign < 0) + { + flags = Flags.NumberInt64; + if (value.l >= int.min) + flags |= Flags.IntFlag; + } + else + { + flags = Flags.NumberUint64; + if (value.ul <= int.max) + flags |= Flags.IntFlag | Flags.UintFlag | Flags.Int64Flag; + else if (value.ul <= uint.max) + flags |= Flags.UintFlag | Flags.Int64Flag; + else if (value.ul <= long.max) + flags |= Flags.Int64Flag; + } + } else - flags = Flags.NumberDouble; - value.d = f; + { + static if (is(F == float)) + flags = Flags.NumberFloat; + else + flags = Flags.NumberDouble; + value.d = f; + } } this(E)(E e) From c54e6da75611e005f751984bf7e6ab5cd2188646 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 23 Sep 2025 02:18:28 +1000 Subject: [PATCH 019/138] Array was missing slice-assign operators. Added remove-range function. --- src/urt/array.d | 63 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/src/urt/array.d b/src/urt/array.d index b83a65a..1ef1377 100644 --- a/src/urt/array.d +++ b/src/urt/array.d @@ -273,6 +273,8 @@ nothrow @nogc: void opAssign(U)(U[] arr) if (is(U : T)) { + // TODO: WHAT IF arr IS A SLICE OF THIS?!!? + debug assert(arr.length <= uint.max); clear(); reserve(arr.length); @@ -452,8 +454,7 @@ nothrow @nogc: static if (is(T == class) || is(T == interface)) { - for (size_t j = i + 1; j < _length; ++j) - ptr[j-1] = ptr[j]; + ptr[i .. _length - 1] = ptr[i + 1 .. _length]; ptr[--_length] = null; } else @@ -461,13 +462,37 @@ nothrow @nogc: destroy!false(ptr[i]); for (size_t j = i + 1; j < _length; ++j) { - emplace!T(&ptr[j-1], ptr[j].move); + moveEmplace(ptr[j], ptr[j-1]); destroy!false(ptr[j]); } --_length; } } + void remove(size_t i, size_t count) + { + debug assert(i + count <= _length); + + static if (is(T == class) || is(T == interface)) + { + ptr[i .. _length - count] = ptr[i + count .. _length]; + ptr[_length - count .. _length] = null; + _length -= cast(uint)count; + } + else + { + for (size_t j = i; j < i + count; ++j) + destroy!false(ptr[j]); + for (size_t j = i + count; j < _length; ++j) + { + moveEmplace(ptr[j], ptr[j - count]); + destroy!false(ptr[j]); + } + } + _length -= cast(uint)count; + } + + void remove(const(T)* pItem) { remove(ptr[0 .. _length].indexOfElement(pItem)); } void removeFirst(U)(ref const U item) { remove(ptr[0 .. _length].findFirst(item)); } @@ -504,31 +529,43 @@ nothrow @nogc: return ptr ? ptr[0 .. allocCount()] : null; } - bool opCast(T : bool)() const + bool opCast(T : bool)() const pure => _length != 0; - size_t opDollar() const + size_t opDollar() const pure => _length; // full slice: arr[] - inout(T)[] opIndex() inout + inout(T)[] opIndex() inout pure => ptr[0 .. _length]; + void opIndexAssign(U)(U[] rh) + { + debug assert(rh.length == _length, "Range error"); + ptr[0 .. _length] = rh[]; + } + // array indexing: arr[i] - ref inout(T) opIndex(size_t i) inout + ref inout(T) opIndex(size_t i) inout pure { debug assert(i < _length, "Range error"); return ptr[i]; } // array slicing: arr[x .. y] - inout(T)[] opIndex(uint[2] i) inout + inout(T)[] opIndex(size_t[2] i) inout pure => ptr[i[0] .. i[1]]; - uint[2] opSlice(size_t dim : 0)(size_t x, size_t y) + void opIndexAssign(U)(U[] rh, size_t[2] i) + { + debug assert(i[1] <= _length && i[1] - i[0] == rh.length, "Range error"); + ptr[i[0] .. i[1]] = rh[]; + } + + size_t[2] opSlice(size_t dim : 0)(size_t x, size_t y) const pure { debug assert(y <= _length, "Range error"); - return [cast(uint)x, cast(uint)y]; + return [x, y]; } void opOpAssign(string op : "~", U)(auto ref U el) @@ -631,14 +668,14 @@ private: static if (EmbedCount > 0) T[EmbedCount] embed = void; - bool hasAllocation() const + bool hasAllocation() const pure { static if (EmbedCount > 0) return ptr && ptr != embed.ptr; else return ptr !is null; } - uint allocCount() const + uint allocCount() const pure => hasAllocation() ? (cast(uint*)ptr)[-1] : EmbedCount; T* allocate(uint count) @@ -659,7 +696,7 @@ private: } pragma(inline, true) - static uint numToAlloc(uint i) + static uint numToAlloc(uint i) pure { // TODO: i'm sure we can imagine a better heuristic... return i > 16 ? i * 2 : 16; From a7690671588b4e19f462b04b6fcb3c0389ef5517 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 25 Sep 2025 09:02:19 +1000 Subject: [PATCH 020/138] Added comparison functions for InetAddress --- src/urt/inet.d | 155 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 152 insertions(+), 3 deletions(-) diff --git a/src/urt/inet.d b/src/urt/inet.d index 4b41981..9f90c55 100644 --- a/src/urt/inet.d +++ b/src/urt/inet.d @@ -208,6 +208,18 @@ nothrow @nogc: bool opEquals(const(ushort)[8] words) const pure => s == words; + int opCmp(ref const IPv6Addr rhs) const pure + { + for (int i = 0; i < 8; i++) + { + if (s[i] < rhs.s[i]) + return -1; + else if (s[i] > rhs.s[i]) + return 1; + } + return 0; + } + IPv6Addr opUnary(string op : "~")() const pure { IPv6Addr r; @@ -428,8 +440,11 @@ nothrow @nogc: IPv6Addr r; int i, j = prefixLen / 16; while (i < j) r.s[i++] = 0xFFFF; - r.s[i++] = cast(ushort)(0xFFFF << (16 - (prefixLen % 16))); - while (i < 8) r.s[i++] = 0; + if (j < 8) + { + r.s[i++] = cast(ushort)(0xFFFF << (16 - (prefixLen % 16))); + while (i < 8) r.s[i++] = 0; + } return r; } @@ -449,6 +464,11 @@ nothrow @nogc: return true; } + IPv6Addr getNetwork(IPv6Addr ip) const pure + => ip & netMask(); + IPv6Addr getLocal(IPv6Addr ip) const pure + => ip & ~netMask(); + size_t toHash() const pure => addr.toHash() ^ prefixLen; @@ -532,11 +552,57 @@ nothrow @nogc: this._a.ipv4.port = port; } - this(IPv6Addr addr, ushort port) + this(IPv6Addr addr, ushort port, int flowInfo = 0, uint scopeId = 0) { family = AddressFamily.IPv6; this._a.ipv6.addr = addr; this._a.ipv6.port = port; + this._a.ipv6.flowInfo = flowInfo; + this._a.ipv6.scopeId = scopeId; + } + + bool opCast(T : bool)() const pure + => family > AddressFamily.Unspecified; + + bool opEquals(ref const InetAddress rhs) const pure + { + if (family != rhs.family) + return false; + switch (family) + { + case AddressFamily.IPv4: + return _a.ipv4 == rhs._a.ipv4; + case AddressFamily.IPv6: + return _a.ipv6 == rhs._a.ipv6; + default: + return true; + } + } + + int opCmp(ref const InetAddress rhs) const pure + { + if (family != rhs.family) + return family < rhs.family ? -1 : 1; + switch (family) + { + case AddressFamily.IPv4: + int c = _a.ipv4.addr.opCmp(rhs._a.ipv4.addr); + return c != 0 ? c : _a.ipv4.port - rhs._a.ipv4.port; + case AddressFamily.IPv6: + int c = _a.ipv6.addr.opCmp(rhs._a.ipv6.addr); + if (c != 0) + return c; + if (_a.ipv6.port == rhs._a.ipv6.port) + { + if (_a.ipv6.flowInfo == rhs._a.ipv6.flowInfo) + return _a.ipv6.scopeId - rhs._a.ipv6.scopeId; + return _a.ipv6.flowInfo - rhs._a.ipv6.flowInfo; + } + return _a.ipv6.port - rhs._a.ipv6.port; + default: + return 0; + } + return 0; } size_t toHash() const pure @@ -651,8 +717,12 @@ unittest char[64] tmp; assert(~IPAddr(255, 255, 248, 0) == IPAddr(0, 0, 7, 255)); + assert((IPAddr(255, 255, 248, 0) & IPAddr(255, 0, 255, 255)) == IPAddr(255, 0, 248, 0)); + assert((IPAddr(255, 255, 248, 0) | IPAddr(255, 0, 255, 255)) == IPAddr(255, 255, 255, 255)); assert((IPAddr(255, 255, 248, 0) ^ IPAddr(255, 0, 255, 255)) == IPAddr(0, 255, 7, 255)); assert(IPSubnet(IPAddr(), 21).netMask() == IPAddr(0xFF, 0xFF, 0xF8, 0)); + assert(IPSubnet(IPAddr(192, 168, 0, 0), 24).getNetwork(IPAddr(192, 168, 0, 10)) == IPAddr(192, 168, 0, 0)); + assert(IPSubnet(IPAddr(192, 168, 0, 0), 24).getLocal(IPAddr(192, 168, 0, 10)) == IPAddr(0, 0, 0, 10)); assert(tmp[0 .. IPAddr(192, 168, 0, 1).toString(tmp, null, null)] == "192.168.0.1"); assert(tmp[0 .. IPAddr(0, 0, 0, 0).toString(tmp, null, null)] == "0.0.0.0"); @@ -671,8 +741,13 @@ unittest assert(subnet.fromString("0.0.0.0/0") == 9 && subnet == IPSubnet(IPAddr(0, 0, 0, 0), 0)); assert(~IPv6Addr(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF0, 0, 0, 0) == IPv6Addr(0, 0, 0, 0, 0xF, 0xFFFF, 0xFFFF, 0xFFFF)); + assert((IPv6Addr(0xFFFF, 0, 1, 2, 3, 4, 5, 6) & IPv6Addr(0xFF00, 0, 3, 0, 0, 0, 0, 2)) == IPv6Addr(0xFF00, 0, 1, 0, 0, 0, 0, 2)); + assert((IPv6Addr(0xFFFF, 0, 1, 2, 3, 4, 5, 6) | IPv6Addr(0xFF00, 0, 3, 0, 0, 0, 0, 2)) == IPv6Addr(0xFFFF, 0, 3, 2, 3, 4, 5, 6)); assert((IPv6Addr(0xFFFF, 0, 1, 2, 3, 4, 5, 6) ^ IPv6Addr(0xFF00, 0, 3, 0, 0, 0, 0, 2)) == IPv6Addr(0xFF, 0, 2, 2, 3, 4, 5, 4)); assert(IPv6Subnet(IPv6Addr(), 21).netMask() == IPv6Addr(0xFFFF, 0xF800, 0, 0, 0, 0, 0, 0)); + assert(IPv6Subnet(IPv6Addr.any, 64).getNetwork(IPv6Addr.loopback) == IPv6Addr.any); + assert(IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0), 32).getNetwork(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1)) == IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)); + assert(IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0), 32).getLocal(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1)) == IPv6Addr(0, 0, 0, 1, 0, 0, 0, 1)); assert(tmp[0 .. IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1).toString(tmp, null, null)] == "2001:db8:0:1::1"); assert(tmp[0 .. IPv6Addr(0x2001, 0xdb8, 0, 0, 1, 0, 0, 1).toString(tmp, null, null)] == "2001:db8::1:0:0:1"); @@ -704,4 +779,78 @@ unittest // assert(address.fromString("[2001:db8:0:1::1]:12345") == 14 && address == InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1), 12345)); // assert(address.fromString("[::]:21") == 14 && address == InetAddress(IPv6Addr(), 21)); + + // IPAddr sorting tests + { + IPAddr[8] expected = [ + IPAddr(0, 0, 0, 0), + IPAddr(1, 2, 3, 4), + IPAddr(1, 2, 3, 5), + IPAddr(1, 2, 4, 4), + IPAddr(10, 0, 0, 1), + IPAddr(127, 0, 0, 1), + IPAddr(192, 168, 1, 1), + IPAddr(255, 255, 255, 255), + ]; + + for (size_t i = 0; i < expected.length - 1; ++i) + { + assert(expected[i].opCmp(expected[i]) == 0, "IPAddr self-comparison failed"); + assert(expected[i].opCmp(expected[i+1]) < 0, "IPAddr sorting is incorrect"); + assert(expected[i+1].opCmp(expected[i]) > 0, "IPAddr sorting is incorrect"); + } + } + + // IPv6Addr sorting tests + { + IPv6Addr[14] expected = [ + IPv6Addr(0, 0, 0, 0, 0, 0, 0, 0), // :: + IPv6Addr(0, 0, 0, 0, 0, 0, 0, 1), // ::1 + IPv6Addr(0, 0, 0, 0, 0, 0, 0, 2), // ::2 + IPv6Addr(0, 0, 0, 0, 0, 0, 9, 0), // ::9:0 + IPv6Addr(0, 0, 0, 0, 0, 8, 0, 0), // ::8:0:0 + IPv6Addr(0, 0, 0, 0, 7, 0, 0, 0), // ::7:0:0:0 + IPv6Addr(0, 0, 0, 6, 0, 0, 0, 0), // 0:0:0:6:: + IPv6Addr(0, 0, 5, 0, 0, 0, 0, 0), // 0:0:5:: + IPv6Addr(0, 4, 0, 0, 0, 0, 0, 0), // 0:4:: + IPv6Addr(1, 0, 0, 0, 0, 0, 0, 0), // 1:: + IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), + IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2), + IPv6Addr(0xfe80, 0, 0, 0, 0, 0, 0, 1), + IPv6Addr(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff), + ]; + + for (size_t i = 0; i < expected.length - 1; ++i) + { + assert(expected[i].opCmp(expected[i]) == 0, "IPv6Addr self-comparison failed"); + assert(expected[i].opCmp(expected[i+1]) < 0, "IPv6Addr sorting is incorrect"); + assert(expected[i+1].opCmp(expected[i]) > 0, "IPv6Addr sorting is incorrect"); + } + } + + // InetAddress sorting tests + { + InetAddress[10] expected = [ + // IPv4 sorted first + InetAddress(IPAddr(10, 0, 0, 1), 80), + InetAddress(IPAddr(127, 0, 0, 1), 8080), + InetAddress(IPAddr(192, 168, 1, 1), 80), + InetAddress(IPAddr(192, 168, 1, 1), 443), + + // IPv6 sorted next + InetAddress(IPv6Addr(1, 0, 0, 0, 0, 0, 0, 0), 1024), + InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 80, 0, 0), + InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 433, 1, 1), + InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 0, 0), // flow=0, scope=0 + InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 0, 1), // flow=0, scope=1 + InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 1, 0), // flow=1, scope=0 + ]; + + for (size_t i = 0; i < expected.length - 1; ++i) + { + assert(expected[i].opCmp(expected[i]) == 0, "InetAddress self-comparison failed"); + assert(expected[i].opCmp(expected[i+1]) < 0, "InetAddress sorting is incorrect"); + assert(expected[i+1].opCmp(expected[i]) > 0, "InetAddress sorting is incorrect"); + } + } } From 42c8de0b91dad846bce7cb22fadcf8b82b0ab76a Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 7 Oct 2025 15:35:48 +1000 Subject: [PATCH 021/138] Flesh out unicode implementation, including functions to perform case-insensitive comparison. --- src/urt/array.d | 60 +++- src/urt/mem/temp.d | 12 + src/urt/socket.d | 2 +- src/urt/string/package.d | 230 ++++++++++-- src/urt/string/uni.d | 755 ++++++++++++++++++++++++++++++++++++++- 5 files changed, 1017 insertions(+), 42 deletions(-) diff --git a/src/urt/array.d b/src/urt/array.d index 1ef1377..702c861 100644 --- a/src/urt/array.d +++ b/src/urt/array.d @@ -1,7 +1,7 @@ module urt.array; import urt.mem; - +import urt.traits : is_some_char; nothrow @nogc: @@ -79,22 +79,9 @@ ref inout(T)[N] takeBack(size_t N, T)(ref inout(T)[] arr) pure return t[0..N]; } -bool exists(T)(const(T)[] arr, auto ref const T el, size_t *pIndex = null) -{ - foreach (i, ref e; arr) - { - if (e.elCmp(el)) - { - if (pIndex) - *pIndex = i; - return true; - } - } - return false; -} - // TODO: I'd like it if these only had one arg (T) somehow... size_t findFirst(T, U)(const(T)[] arr, auto ref const U el) + if (!is_some_char!T) { size_t i = 0; while (i < arr.length && !arr[i].elCmp(el)) @@ -104,15 +91,17 @@ size_t findFirst(T, U)(const(T)[] arr, auto ref const U el) // TODO: I'd like it if these only had one arg (T) somehow... size_t findLast(T, U)(const(T)[] arr, auto ref const U el) + if (!is_some_char!T) { - ptrdiff_t last = length-1; + ptrdiff_t last = arr.length-1; while (last >= 0 && !arr[last].elCmp(el)) --last; - return last < 0 ? length : last; + return last < 0 ? arr.length : last; } // TODO: I'd like it if these only had one arg (T) somehow... size_t findFirst(T, U)(const(T)[] arr, U[] seq) + if (!is_some_char!T) { if (seq.length == 0) return 0; @@ -129,6 +118,7 @@ size_t findFirst(T, U)(const(T)[] arr, U[] seq) // TODO: I'd like it if these only had one arg (T) somehow... size_t findLast(T, U)(const(T)[] arr, U[] seq) + if (!is_some_char!T) { if (seq.length == 0) return arr.length; @@ -143,7 +133,8 @@ size_t findLast(T, U)(const(T)[] arr, U[] seq) return arr.length; } -size_t findFirst(T)(const(T)[] arr, bool delegate(auto ref const T) nothrow @nogc pred) +size_t findFirst(T)(const(T)[] arr, bool delegate(ref const T) nothrow @nogc pred) + if (!is_some_char!T) { size_t i = 0; while (i < arr.length && !pred(arr[i])) @@ -151,6 +142,39 @@ size_t findFirst(T)(const(T)[] arr, bool delegate(auto ref const T) nothrow @nog return i; } +bool contains(T, U)(const(T)[] arr, auto ref const U el, size_t *index = null) + if (!is_some_char!T) +{ + size_t i = findFirst(arr, el); + if (i == arr.length) + return false; + if (index) + *index = i; + return true; +} + +bool contains(T, U)(const(T)[] arr, U[] seq, size_t *index = null) + if (!is_some_char!T) +{ + size_t i = findFirst(arr, seq); + if (i == arr.length) + return false; + if (index) + *index = i; + return true; +} + +bool contains(T)(const(T)[] arr, bool delegate(ref const T) nothrow @nogc pred, size_t *index = null) + if (!is_some_char!T) +{ + size_t i = findFirst(arr, pred); + if (i == arr.length) + return false; + if (index) + *index = i; + return true; +} + ptrdiff_t indexOfElement(T, U)(const(T)[] arr, const(U)* el) { if (el < arr.ptr || el >= arr.ptr + arr.length) diff --git a/src/urt/mem/temp.d b/src/urt/mem/temp.d index 7c28cfc..f470dff 100644 --- a/src/urt/mem/temp.d +++ b/src/urt/mem/temp.d @@ -118,6 +118,18 @@ char[] tstring(T)(auto ref T value) return result; } +dchar[] tdstring(T)(auto ref T value) nothrow @nogc +{ + static if (is(T : const(char)[]) || is(T : const(wchar)[]) || is(T : const(dchar)[])) + alias s = value; + else + char[] s = tstring(value); + import urt.string.uni : uni_convert; + dchar* r = cast(dchar*)talloc(s[].length*4).ptr; + size_t len = uni_convert(s[], r[0 .. s.length]); + return r[0 .. len]; +} + char[] tconcat(Args...)(ref Args args) { import urt.string.format : concat; diff --git a/src/urt/socket.d b/src/urt/socket.d index 304edee..f27664c 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -746,7 +746,7 @@ Result get_hostname(char* name, size_t len) Result get_address_info(const(char)[] nodeName, const(char)[] service, AddressInfo* hints, out AddressInfoResolver result) { - import urt.array : findFirst; + import urt.string : findFirst; import urt.mem.temp : tstringz; size_t colon = nodeName.findFirst(':'); diff --git a/src/urt/string/package.d b/src/urt/string/package.d index 4a807e2..3210e03 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -1,58 +1,218 @@ module urt.string; +import urt.string.uni; +import urt.traits : is_some_char; + public import urt.string.ascii; public import urt.string.string; public import urt.string.tailstring; // seful string operations defined elsewhere public import urt.array : empty, popFront, popBack, takeFront, takeBack; -public import urt.mem : strlen; +public import urt.mem : strlen, wcslen; public import urt.mem.temp : tstringz, twstringz; nothrow @nogc: -ptrdiff_t cmp(const(char)[] a, const(char)[] b) pure +ptrdiff_t cmp(bool case_insensitive = false, T, U)(const(T)[] a, const(U)[] b) pure { - if (a.length != b.length) - return a.length - b.length; - for (size_t i = 0; i < a.length; ++i) + static if (case_insensitive) + return uni_compare_i(a, b); + else { - ptrdiff_t diff = a[i] - b[i]; - if (diff) - return diff; + static if (is(T == U)) + { + if (a.length != b.length) + return a.length - b.length; + } + return uni_compare(a, b); } - return 0; } -ptrdiff_t icmp(const(char)[] a, const(char)[] b) pure +ptrdiff_t icmp(T, U)(const(T)[] a, const(U)[] b) pure + => cmp!true(a, b); + +bool eq(const(char)[] a, const(char)[] b) pure + => cmp(a, b) == 0; + +bool ieq(const(char)[] a, const(char)[] b) pure + => cmp!true(a, b) == 0; + +size_t findFirst(bool case_insensitive = false, T, U)(const(T)[] s, const U c) + if (is_some_char!T && is_some_char!U) { - if (a.length != b.length) - return a.length - b.length; - for (size_t i = 0; i < a.length; ++i) + static if (is(U == char)) + assert(c <= 0x7F, "Invalid UTF character"); + else static if (is(U == wchar)) + assert(c < 0xD800 || c >= 0xE000, "Invalid UTF character"); + + // TODO: what if `c` is 'ß'? do we find "ss" in case-insensitive mode? + // and if `c` is 's', do we match 'ß'? + + static if (case_insensitive) + const U lc = cast(U)c.uni_case_fold(); + else + alias lc = c; + + size_t i = 0; + while (i < s.length) { - ptrdiff_t diff = to_lower(a[i]) - to_lower(b[i]); - if (diff) - return diff; + static if (U.sizeof <= T.sizeof) + { + enum l = 1; + dchar d = s[i]; + } + else + { + size_t l; + dchar d = next_dchar(s[i..$], l); + } + static if (case_insensitive) + { + static if (is(U == char)) + { + // only fold the ascii characters, since lc is known to be ascii + if (uint(d - 'A') < 26) + d |= 0x20; + } + else + d = d.uni_case_fold(); + } + if (d == lc) + break; + i += l; } - return 0; + return i; } -bool ieq(const(char)[] a, const(char)[] b) pure - => icmp(a, b) == 0; +size_t find_first_i(T, U)(const(T)[] s, U c) + if (is_some_char!T && is_some_char!U) + => findFirst!true(s, c); + +size_t findLast(bool case_insensitive = false, T, U)(const(T)[] s, const U c) + if (is_some_char!T && is_some_char!U) +{ + static assert(case_insensitive == false, "TODO"); + + static if (is(U == char)) + assert(c <= 0x7F, "Invalid unicode character"); + else static if (is(U == wchar)) + assert(c >= 0xD800 && c < 0xE000, "Invalid unicode character"); + + ptrdiff_t last = s.length-1; + while (last >= 0) + { + static if (U.sizeof <= T.sizeof) + { + if (s[last] == c) + return cast(size_t)last; + } + else + { + // this is tricky, because we need to seek backwards to the start of surrogate sequences + assert(false, "TODO"); + } + } + return s.length; +} + +size_t find_last_i(T, U)(const(T)[] s, U c) + if (is_some_char!T && is_some_char!U) + => findLast!true(s, c); + +size_t findFirst(bool case_insensitive = false, T, U)(const(T)[] s, const(U)[] t) + if (is_some_char!T && is_some_char!U) +{ + if (t.length == 0) + return 0; + + // fast-path for one-length tokens + size_t l = t.uni_seq_len(); + if (l == t.length) + { + dchar c = t.next_dchar(l); + if (c < 0x80) + return findFirst!case_insensitive(s, cast(char)c); + if (c < 0x10000) + return findFirst!case_insensitive(s, cast(wchar)c); + return findFirst!case_insensitive(s, c); + } + + size_t offset = 0; + while (offset < s.length) + { + + static if (case_insensitive) + int c = uni_compare_i(s[offset .. $], t); + else + int c = uni_compare(s[offset .. $], t); + if (c == int.max || c == 0) + return offset; + if (c == int.min) + return s.length; + offset += s[offset .. $].uni_seq_len(); + } + return s.length; +} + +size_t find_first_i(T, U)(const(T)[] s, const(U)[] t) + if (is_some_char!T && is_some_char!U) + => findFirst!true(s, t); + +size_t findLast(bool case_insensitive = false, T, U)(const(T)[] s, const(U)[] t) + if (is_some_char!T && is_some_char!U) +{ + // this is tricky, because we need to seek backwards to the start of surrogate sequences + assert(false, "TODO"); +} + +size_t find_last_i(T, U)(const(T)[] s, const(U)[] t) + if (is_some_char!T && is_some_char!U) + => findLast!true(s, t); + +bool contains(bool case_insensitive = false, T, U)(const(T)[] s, U c, size_t *offset = null) + if (is_some_char!T && is_some_char!U) +{ + size_t i = findFirst!case_insensitive(s, c); + if (i == s.length) + return false; + if (offset) + *offset = i; + return true; +} + +bool contains(bool case_insensitive = false, T, U)(const(T)[] s, const(U)[] t, size_t *offset = null) + if (is_some_char!T && is_some_char!U) +{ + size_t i = findFirst!case_insensitive(s, t); + if (i == s.length) + return false; + if (offset) + *offset = i; + return true; +} + +bool contains_i(T, U)(const(T)[] s, U c, size_t *offset = null) + if (is_some_char!T && is_some_char!U) + => contains!true(s, c, offset); + +bool contains_i(T, U)(const(T)[] s, const(U)[] t, size_t *offset = null) + if (is_some_char!T && is_some_char!U) + => contains!true(s, t, offset); bool startsWith(const(char)[] s, const(char)[] prefix) pure { if (s.length < prefix.length) return false; - return s[0 .. prefix.length] == prefix[]; + return cmp(s[0 .. prefix.length], prefix) == 0; } bool endsWith(const(char)[] s, const(char)[] suffix) pure { if (s.length < suffix.length) return false; - return s[$ - suffix.length .. $] == suffix[]; + return cmp(s[$ - suffix.length .. $], suffix) == 0; } inout(char)[] trim(bool Front = true, bool Back = true)(inout(char)[] s) pure @@ -305,3 +465,31 @@ bool wildcardMatch(const(char)[] wildcard, const(char)[] value) pure } return wildcard.length == value.length; } + + +unittest +{ + // test findFirst + assert("hello".findFirst('e') == 1); + assert("hello".findFirst('a') == 5); + assert("hello".findFirst("e") == 1); + assert("hello".findFirst("ll") == 2); + assert("hello".findFirst("lo") == 3); + assert("hello".findFirst("la") == 5); + assert("hello".findFirst("low") == 5); + assert("héllo".findFirst('é') == 1); + assert("héllo"w.findFirst('é') == 1); + assert("héllo".findFirst("éll") == 1); + assert("héllo".findFirst('a') == 6); + assert("héllo".findFirst("la") == 6); + assert("hello".find_first_i('E') == 1); + assert("HELLO".find_first_i("e") == 1); + assert("hello".find_first_i("LL") == 2); + assert("héllo".find_first_i('É') == 1); + assert("HÉLLO".find_first_i("é") == 1); + assert("HÉLLO".find_first_i("éll") == 1); + + assert("HÉLLO".contains('É')); + assert(!"HÉLLO".contains('A')); + assert("HÉLLO".contains_i("éll")); +} diff --git a/src/urt/string/uni.d b/src/urt/string/uni.d index bc3e44c..ce7f7d1 100644 --- a/src/urt/string/uni.d +++ b/src/urt/string/uni.d @@ -1,7 +1,142 @@ module urt.string.uni; -nothrow @nogc: +import urt.string.ascii : to_lower, to_upper; +import urt.traits : is_some_char; +pure nothrow @nogc: + + +size_t uni_seq_len(const(char)[] s) +{ + if (s.length == 0) + return 0; + if (s[0] < 0x80) // 1-byte sequence: 0xxxxxxx + return 1; + else if ((s[0] & 0xE0) == 0xC0) // 2-byte sequence: 110xxxxx 10xxxxxx + return (s.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + else if ((s[0] & 0xF0) == 0xE0) // 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx + return (s.length >= 3 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) ? 3 : + (s.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + else if ((s[0] & 0xF8) == 0xF0) // 4-byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + return (s.length >= 4 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80 && (s[3] & 0xC0) == 0x80) ? 4 : + (s.length >= 3 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) ? 3 : + (s.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + return 1; // Invalid UTF-8 sequence +} + +size_t uni_seq_len(const(wchar)[] s) +{ + if (s.length == 0) + return 0; + if (s[0] >= 0xD800 && s[0] < 0xDC00 && s.length >= 2 && s[1] >= 0xDC00 && s[1] < 0xE000) + return 2; // Surrogate pair: 110110xxxxxxxxxx 110111xxxxxxxxxx + return 1; +} + +pragma(inline, true) +size_t uni_seq_len(const(dchar)[] s) + => s.length > 0; + +size_t uni_strlen(C)(const(C)[] s) + if (is_some_char!C) +{ + static if (is(C == dchar)) + { + pragma(inline, true); + return s.length; + } + else + { + size_t count = 0; + while (s.length) + { + size_t l = s.uni_seq_len; + s = s[l .. $]; + ++count; + } + return count; + } +} + +dchar next_dchar(const(char)[] s, out size_t seq_len) +{ + assert(s.length > 0); + + const(char)* p = s.ptr; + if ((*p & 0x80) == 0) // 1-byte sequence: 0xxxxxxx + { + seq_len = 1; + return *p; + } + else if ((*p & 0xE0) == 0xC0) // 2-byte sequence: 110xxxxx 10xxxxxx + { + if (s.length >= 2 && (p[1] & 0xC0) == 0x80) + { + seq_len = 2; + return ((p[0] & 0x1F) << 6) | (p[1] & 0x3F); + } + } + else if ((*p & 0xF0) == 0xE0) // 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx + { + if (s.length >= 3 && (p[1] & 0xC0) == 0x80 && (p[2] & 0xC0) == 0x80) + { + seq_len = 3; + return ((p[0] & 0x0F) << 12) | ((p[1] & 0x3F) << 6) | (p[2] & 0x3F); + } + // check for seq_len == 2 error cases + if (s.length >= 2 && (p[1] & 0xC0) == 0x80) + { + seq_len = 2; + return 0xFFFD; + } + } + else if ((*p & 0xF8) == 0xF0) // 4-byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + { + if (s.length >= 4 && (p[1] & 0xC0) == 0x80 && (p[2] & 0xC0) == 0x80 && (p[3] & 0xC0) == 0x80) + { + seq_len = 4; + return ((p[0] & 0x07) << 18) | ((p[1] & 0x3F) << 12) | ((p[2] & 0x3F) << 6) | (p[3] & 0x3F); + } + // check for seq_len == 2..3 error cases + if (s.length >= 2 && (p[1] & 0xC0) == 0x80) + { + if (s.length == 2 || (p[2] & 0xC0) != 0x80) + seq_len = 2; + else + seq_len = 3; + return 0xFFFD; + } + } + seq_len = 1; + return 0xFFFD; // Invalid UTF-8 sequence +} + +dchar next_dchar(const(wchar)[] s, out size_t seq_len) +{ + assert(s.length > 0); + + const(wchar)* p = s.ptr; + if (p[0] < 0xD800 || p[0] >= 0xE000) + { + seq_len = 1; + return p[0]; + } + if (p[0] < 0xDC00 && s.length >= 2 && p[1] >= 0xDC00 && p[1] < 0xE000) // Surrogate pair: 110110xxxxxxxxxx 110111xxxxxxxxxx + { + seq_len = 2; + return 0x10000 + ((p[0] - 0xD800) << 10) + (p[1] - 0xDC00); + } + seq_len = 1; + return 0xFFFD; // Invalid UTF-16 sequence +} + +pragma(inline, true) +dchar next_dchar(const(dchar)[] s, out size_t seq_len) +{ + assert(s.length > 0); + seq_len = 1; + return s[0]; +} size_t uni_convert(const(char)[] s, wchar[] buffer) { @@ -242,10 +377,504 @@ size_t uni_convert(const(dchar)[] s, wchar[] buffer) return b - buffer.ptr; } +char uni_to_lower(char c) + => c.to_lower(); + +dchar uni_to_lower(dchar c) +{ + if (uint(c - 'A') < 26) + return c | 0x20; + + // TODO: this is a deep rabbit-hole! (and the approach might not be perfect) + + if (c < 0xFF) + { + if (c >= 0xC0) // Latin-1 Supplement + return g_to_lower_latin_1.ptr[c - 0xC0]; + } + else if (c <= 0x556) + { + if (c >= 0x370) + { + if (c < 0x400) // Greek and Coptic + return 0x300 | g_to_lower_greek.ptr[c - 0x370]; + else if (c < 0x460) // Cyrillic + { + if (c < 0x410) + return c + 0x50; + else if (c < 0x430) + return c + 0x20; + } + else if (c < 0x530) // Cyrillic Supplement + { + if (c >= 0x482 && c < 0x48A) // exceptions + return c; + return c | 1; + } + else if (c >= 0x531) // Armenian + return c + 0x30; + } + else if (c < 0x180) // Latin Extended-A + return (0x100 | g_to_lower_latin_extended_a.ptr[c - 0xFF]) - 1; + } + else if (c <= 0x1CB0) // Georgian + { + if (c >= 0x1C90) + return c - 0x0BC0; // Mtavruli -> Mkhedruli + else if (c >= 0x10A0 && c <= 0x10C5) + return c + 0x1C60; // Asomtavruli -> Nuskhuri + } + else if (c >= 0x1E00) + { + if (c < 0x1F00) // Latin Extended Additional + { + if (c >= 0x1E96 && c < 0x1EA0) // exceptions + { + if (c == 0x1E9E) // 'ẞ' -> 'ß' + return 0xDF; + return c; + } + return c | 1; + } + else if (c <= 0x1FFC) // Greek Extended + { + if (c < 0x1F70) + return c & ~0x8; + return 0x1F00 | g_to_lower_greek_extended.ptr[c - 0x1F70]; + } + else if (c < 0x2CE4) + { + if (c >= 0x2C80) // Coptic + return c | 1; + } + } + return c; +} + +char uni_to_upper(char c) + => c.to_upper(); + +dchar uni_to_upper(dchar c) +{ + if (uint(c - 'a') < 26) + return c ^ 0x20; + + // TODO: this is a deep rabbit-hole! (and the approach might not be perfect) + + if (c < 0xFF) + { + if (c == 0xDF) // 'ß' -> 'ẞ' + return 0x1E9E; + if (c >= 0xC0) // Latin-1 Supplement + return g_to_upper_latin_1.ptr[c - 0xC0]; + } + else if (c <= 0x586) + { + if (c >= 0x370) + { + if (c < 0x400) // Greek and Coptic + return 0x300 | g_to_upper_greek.ptr[c - 0x370]; + else if (c < 0x460) // Cyrillic + { + if (c >= 0x450) + return c - 0x50; + else if (c >= 0x430) + return c - 0x20; + } + else if (c < 0x530) // Cyrillic Supplement + { + if (c >= 0x482 && c < 0x48A) // exceptions + return c; + return c & ~1; + } + else if (c >= 0x561) // Armenian + return c - 0x30; + } + else if (c < 0x180) // Latin Extended-A + return 0x100 | g_to_upper_latin_extended_a.ptr[c - 0xFF]; + } + else if (c <= 0x10F0) // Georgian + { + if (c >= 0x10D0) + return c + 0x0BC0; // Mkhedruli -> Mtavruli + } + else if (c >= 0x1E00) + { + if (c < 0x1F00) // Latin Extended Additional + { + if (c >= 0x1E96 && c < 0x1EA0) // exceptions + return c; + return c & ~1; + } + else if (c <= 0x1FFC) // Greek Extended + { + if (c < 0x1F70) + return c | 0x8; + return 0x1F00 | g_to_upper_greek_extended.ptr[c - 0x1F70]; + } + else if (c < 0x2CE4) + { + if (c >= 0x2C80) // Coptic + return c & ~1; + } + else if (c <= 0x2D25) // Georgian + { + if(c >= 0x2D00) + return c - 0x1C60; // Nuskhuri -> Asomtavruli + } + } + return c; +} + +char uni_case_fold(char c) + => c.to_lower(); + +dchar uni_case_fold(dchar c) +{ + // case-folding is stronger than to_lower, there may be many misc cases... + if (c >= 0x3C2) // Greek has bonus case-folding... + { + if (c < 0x3FA) + return 0x300 | g_case_fold_greek.ptr[c - 0x3C2]; + } + else if (c == 'ſ') // TODO: pointless? it's in the spec... + return 's'; + return uni_to_lower(c); +} + +int uni_compare(T, U)(const(T)[] s1, const(U)[] s2) + if (is_some_char!T && is_some_char!U) +{ + const(T)* p1 = s1.ptr; + const(T)* p1end = p1 + s1.length; + const(U)* p2 = s2.ptr; + const(U)* p2end = p2 + s2.length; + + // TODO: this is crude and insufficient; doesn't handle compound diacritics, etc (needs a NFKC normalisation step) + + while (p1 < p1end && p2 < p2end) + { + dchar a = *p1; + if (a < 0x80) + { + dchar b = *p2; + if (a != b) + { + if (b >= 0x80) + { + size_t _; + b = next_dchar(p2[0 .. p2end - p2], _); + } + return cast(int)a - cast(int)b; + } + ++p1; + ++p2; + } + else + { + size_t al, bl; + a = next_dchar(p1[0 .. p1end - p1], al); + dchar b = next_dchar(p2[0 .. p2end - p2], bl); + if (a != b) + return cast(int)a - cast(int)b; + p1 += al; + p2 += al; + } + } + + // return int.min/max in the case that the strings are a sub-string of the other so the caller can detect this case + return (p1 < p1end) ? int.max : (p2 < p2end) ? int.min : 0; +} + +int uni_compare_i(T, U)(const(T)[] s1, const(U)[] s2) + if (is_some_char!T && is_some_char!U) +{ + const(T)* p1 = s1.ptr; + const(T)* p1end = p1 + s1.length; + const(U)* p2 = s2.ptr; + const(U)* p2end = p2 + s2.length; + + // TODO: this is crude and insufficient; doesn't handle compound diacritics, etc (needs a NFKC normalisation step) + // that said, it's also overkill for embedded use! + + size_t al, bl; + while (p1 < p1end && p2 < p2end) + { + dchar a = *p1; + dchar b = void; + if (a < 0x80) + { + // ascii fast-path + a = (cast(char)a).to_lower; + b = *p2; + if (uint(b - 'A') < 26) + b |= 0x20; + if (a != b) + { + if (b >= 0x80) + { + // `b` is not ascii; break-out to the slow path... + al = 1; + goto uni_compare_load_b; + } + return cast(int)a - cast(int)b; + } + ++p1; + ++p2; + } + else + { + a = next_dchar(p1[0 .. p1end - p1], al).uni_case_fold; + uni_compare_load_b: + b = next_dchar(p2[0 .. p2end - p2], bl).uni_case_fold; + uni_compare_a_b: + if (a != b) + { + // it is _SO UNFORTUNATE_ that the ONLY special-case letter in all of unicode is german 'ß' (0xDF)!! + if (b == 0xDF) + { + if (a != 's') + return cast(int)a - cast(int)'s'; + if (++p1 == p1end) + return -1; // only one 's', so the a-side is a shorter string + a = next_dchar(p1[0 .. p1end - p1], al).uni_case_fold; + b = 's'; + p2 += bl - 1; + bl = 1; + goto uni_compare_a_b; + } + else if (a == 0xDF) + { + if (b != 's') + return cast(int)'s' - cast(int)b; + if (++p2 == p2end) + return 1; // only one 's', so the b-side is a shorter string + a = 's'; + p1 += al - 1; + al = 1; + goto uni_compare_load_b; + } + return cast(int)a - cast(int)b; + } + p1 += al; + p2 += bl; + } + } + + // return int.min/max in the case that the strings are a sub-string of the other so the caller can detect this case + return (p1 < p1end) ? int.max : (p2 < p2end) ? int.min : 0; +} + + +private: + +// this is a helper to crush character maps into single byte arrays... +ubyte[N] map_chars(size_t N)(ubyte function(wchar) pure nothrow @nogc translate, wchar[N] map) +{ + if (__ctfe) + { + ubyte[N] result; + foreach (i; 0 .. N) + result[i] = translate(map[i]); + return result; + } + else + assert(false, "Not for runtime!"); +} + +// lookup tables for case conversion +__gshared immutable g_to_lower_latin_1 = map_chars(c => cast(ubyte)c, to_lower_latin[0 .. 0x3F]); +__gshared immutable g_to_upper_latin_1 = map_chars(c => cast(ubyte)c, to_upper_latin[0 .. 0x3F]); +__gshared immutable g_to_lower_latin_extended_a = map_chars(c => cast(ubyte)(c + 1), to_lower_latin[0x3F .. 0xC0]); // calculate `(0x100 | table[n]) - 1` at runtime +__gshared immutable g_to_upper_latin_extended_a = map_chars(c => cast(ubyte)c, to_upper_latin[0x3F .. 0xC0]); // calculate `0x100 | table[n]` at runtime +__gshared immutable g_to_lower_greek = map_chars(c => cast(ubyte)c, to_lower_greek); // calculate `0x300 | table[n]` at runtime +__gshared immutable g_to_upper_greek = map_chars(c => cast(ubyte)c, to_upper_greek); // calculate `0x300 | table[n]` at runtime +__gshared immutable g_case_fold_greek = map_chars(c => cast(ubyte)c, case_fold_greek); // calculate `0x300 | table[n]` at runtime +__gshared immutable g_to_lower_greek_extended = map_chars(c => cast(ubyte)c, to_lower_greek_extended); // calculate `0x1F00 | table[n]` at runtime +__gshared immutable g_to_upper_greek_extended = map_chars(c => cast(ubyte)c, to_upper_greek_extended); // calculate `0x1F00 | table[n]` at runtime + +// Latin-1 Supplement and Latin Extended-A +enum wchar[0x180 - 0xC0] to_lower_latin = [ + 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', + 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 0xD7,'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ß', + 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', + 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 0xF7,'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ÿ', + 'ā', 'ā', 'ă', 'ă', 'ą', 'ą', 'ć', 'ć', 'ĉ', 'ĉ', 'ċ', 'ċ', 'č', 'č', 'ď', 'ď', + 'đ', 'đ', 'ē', 'ē', 'ĕ', 'ĕ', 'ė', 'ė', 'ę', 'ę', 'ě', 'ě', 'ĝ', 'ĝ', 'ğ', 'ğ', + 'ġ', 'ġ', 'ģ', 'ģ', 'ĥ', 'ĥ', 'ħ', 'ħ', 'ĩ', 'ĩ', 'ī', 'ī', 'ĭ', 'ĭ', 'į', 'į', + 0x130,0x131,'ij', 'ij', 'ĵ', 'ĵ', 'ķ', 'ķ',0x138,'ĺ', 'ĺ', 'ļ', 'ļ', 'ľ', 'ľ', 'ŀ', + 'ŀ', 'ł', 'ł', 'ń', 'ń', 'ņ', 'ņ', 'ň', 'ň',0x149,'ŋ', 'ŋ', 'ō', 'ō', 'ŏ', 'ŏ', + 'ő', 'ő', 'œ', 'œ', 'ŕ', 'ŕ', 'ŗ', 'ŗ', 'ř', 'ř', 'ś', 'ś', 'ŝ', 'ŝ', 'ş', 'ş', + 'š', 'š', 'ţ', 'ţ', 'ť', 'ť', 'ŧ', 'ŧ', 'ũ', 'ũ', 'ū', 'ū', 'ŭ', 'ŭ', 'ů', 'ů', + 'ű', 'ű', 'ų', 'ų', 'ŵ', 'ŵ', 'ŷ', 'ŷ', 'ÿ', 'ź', 'ź', 'ż', 'ż', 'ž', 'ž', 'ſ' +]; + +enum wchar[0x180 - 0xC0] to_upper_latin = [ + 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', + 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 0xD7,'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ẞ', + 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', + 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 0xF7,'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'Ÿ', + 'Ā', 'Ā', 'Ă', 'Ă', 'Ą', 'Ą', 'Ć', 'Ć', 'Ĉ', 'Ĉ', 'Ċ', 'Ċ', 'Č', 'Č', 'Ď', 'Ď', + 'Đ', 'Đ', 'Ē', 'Ē', 'Ĕ', 'Ĕ', 'Ė', 'Ė', 'Ę', 'Ę', 'Ě', 'Ě', 'Ĝ', 'Ĝ', 'Ğ', 'Ğ', + 'Ġ', 'Ġ', 'Ģ', 'Ģ', 'Ĥ', 'Ĥ', 'Ħ', 'Ħ', 'Ĩ', 'Ĩ', 'Ī', 'Ī', 'Ĭ', 'Ĭ', 'Į', 'Į', + 0x130,0x131,'IJ', 'IJ', 'Ĵ', 'Ĵ', 'Ķ', 'Ķ',0x138,'Ĺ', 'Ĺ', 'Ļ', 'Ļ', 'Ľ', 'Ľ', 'Ŀ', + 'Ŀ', 'Ł', 'Ł', 'Ń', 'Ń', 'Ņ', 'Ņ', 'Ň', 'Ň',0x149,'Ŋ', 'Ŋ', 'Ō', 'Ō', 'Ŏ', 'Ŏ', + 'Ő', 'Ő', 'Œ', 'Œ', 'Ŕ', 'Ŕ', 'Ŗ', 'Ŗ', 'Ř', 'Ř', 'Ś', 'Ś', 'Ŝ', 'Ŝ', 'Ş', 'Ş', + 'Š', 'Š', 'Ţ', 'Ţ', 'Ť', 'Ť', 'Ŧ', 'Ŧ', 'Ũ', 'Ũ', 'Ū', 'Ū', 'Ŭ', 'Ŭ', 'Ů', 'Ů', + 'Ű', 'Ű', 'Ų', 'Ų', 'Ŵ', 'Ŵ', 'Ŷ', 'Ŷ', 'Ÿ', 'Ź', 'Ź', 'Ż', 'Ż', 'Ž', 'Ž', 'S' +]; + +// Greek and Coptic +enum wchar[0x400 - 0x370] to_lower_greek = [ + 'ͱ', 'ͱ', 'ͳ', 'ͳ',0x374,0x375,'ͷ','ͷ',0x378,0x379,0x37A,'ͻ','ͼ','ͽ',0x37E,'ϳ', +0x380,0x381,0x382,0x383,0x384,0x385,'ά',0x387,'έ','ή','ί',0x38B,'ό',0x38D,'ύ', 'ώ', + 0x390,'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', + 'π', 'ρ',0x3A2,'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω', 'ϊ', 'ϋ', 'ά', 'έ', 'ή', 'ί', + 0x3B0,'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', + 'π', 'ρ', 'ς', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω', 'ϊ', 'ϋ', 'ό', 'ύ', 'ώ', 'ϗ', + 'ϐ', 'ϑ', 'υ', 'ύ', 'ϋ', 'ϕ', 'ϖ', 'ϗ', 'ϙ', 'ϙ', 'ϛ', 'ϛ', 'ϝ', 'ϝ', 'ϟ', 'ϟ', + 'ϡ', 'ϡ', 'ϣ', 'ϣ', 'ϥ', 'ϥ', 'ϧ', 'ϧ', 'ϩ', 'ϩ', 'ϫ', 'ϫ', 'ϭ', 'ϭ', 'ϯ', 'ϯ', + 'ϰ', 'ϱ', 'ϲ', 'ϳ', 'θ', 'ϵ',0x3F6,'ϸ', 'ϸ', 'ϲ', 'ϻ', 'ϻ',0x3FC,'ͻ', 'ͼ', 'ͽ' +]; + +enum wchar[0x400 - 0x370] to_upper_greek = [ + 'Ͱ', 'Ͱ', 'Ͳ', 'Ͳ',0x374,0x375,'Ͷ','Ͷ',0x378,0x379,0x37A,'Ͻ','Ͼ','Ͽ',0x37E,'Ϳ', +0x380,0x381,0x382,0x383,0x384,0x385,'Ά',0x387,'Έ','Ή','Ί',0x38B,'Ό',0x38D,'Ύ', 'Ώ', + 0x390,'Α', 'Β', 'Γ', 'Δ', 'Ε', 'Ζ', 'Η', 'Θ', 'Ι', 'Κ', 'Λ', 'Μ', 'Ν', 'Ξ', 'Ο', + 'Π', 'Ρ',0x3A2,'Σ', 'Τ', 'Υ', 'Φ', 'Χ', 'Ψ', 'Ω', 'Ϊ', 'Ϋ', 'Ά', 'Έ', 'Ή', 'Ί', + 0x3B0,'Α', 'Β', 'Γ', 'Δ', 'Ε', 'Ζ', 'Η', 'Θ', 'Ι', 'Κ', 'Λ', 'Μ', 'Ν', 'Ξ', 'Ο', + 'Π', 'Ρ', 'Σ', 'Σ', 'Τ', 'Υ', 'Φ', 'Χ', 'Ψ', 'Ω', 'Ϊ', 'Ϋ', 'Ό', 'Ύ', 'Ώ', 'Ϗ', + 'Β','Θ',0x3D2,0x3D3,0x3D4,'Φ','Π', 'Ϗ', 'Ϙ', 'Ϙ', 'Ϛ', 'Ϛ', 'Ϝ', 'Ϝ', 'Ϟ', 'Ϟ', + 'Ϡ', 'Ϡ', 'Ϣ', 'Ϣ', 'Ϥ', 'Ϥ', 'Ϧ', 'Ϧ', 'Ϩ', 'Ϩ', 'Ϫ', 'Ϫ', 'Ϭ', 'Ϭ', 'Ϯ', 'Ϯ', + 'Κ', 'Ρ', 'Ϲ', 'Ϳ', 'ϴ', 'Ε',0x3F6,'Ϸ', 'Ϸ', 'Ϲ', 'Ϻ', 'Ϻ',0x3FC,'Ͻ', 'Ͼ', 'Ͽ' +]; + +enum wchar[0x3FA - 0x3C2] case_fold_greek = [ + 'σ', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω', 'ϊ', 'ϋ', 'ό', 'ύ', 'ώ', 'ϗ', + 'β', 'θ', 'υ', 'ύ', 'ϋ', 'φ', 'π', 'ϗ', 'ϙ', 'ϙ', 'ϛ', 'ϛ', 'ϝ', 'ϝ', 'ϟ', 'ϟ', + 'ϡ', 'ϡ', 'ϣ', 'ϣ', 'ϥ', 'ϥ', 'ϧ', 'ϧ', 'ϩ', 'ϩ', 'ϫ', 'ϫ', 'ϭ', 'ϭ', 'ϯ', 'ϯ', + 'κ', 'ρ', 'σ', 'ϳ', 'θ', 'ε',0x3F6,'ϸ', 'ϸ', 'σ' +]; + +enum wchar[0x1FFD - 0x1F70] to_lower_greek_extended = [ + 'ὰ', 'ά', 'ὲ', 'έ', 'ὴ', 'ή', 'ὶ', 'ί', 'ὸ', 'ό', 'ὺ', 'ύ', 'ὼ', 'ώ', 0x1F7E,0x1F7F, + 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', + 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', + 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', + 'ᾰ', 'ᾱ', 'ᾲ', 'ᾳ', 'ᾴ', 0x1FB5, 'ᾶ', 'ᾷ', 'ᾰ', 'ᾱ', 'ὰ', 'ά', 'ᾳ', 0x1FBD,0x1FBE,0x1FBF, +0x1FC0,0x1FC1,'ῂ', 'ῃ', 'ῄ', 0x1FC5, 'ῆ', 'ῇ', 'ὲ', 'έ', 'ὴ', 'ή', 'ῃ', 0x1FCD,0x1FCE,0x1FCF, + 'ῐ', 'ῑ', 'ῒ', 'ΐ', 0x1FD4,0x1FD5, 'ῖ', 'ῗ', 'ῐ', 'ῑ', 'ὶ', 'ί',0x1FDC,0x1FDD,0x1FDE,0x1FDF, + 'ῠ', 'ῡ', 'ῢ', 'ΰ', 'ῤ', 'ῥ', 'ῦ', 'ῧ', 'ῠ', 'ῡ', 'ὺ', 'ύ', 'ῥ', 0x1FED,0x1FEE,0x1FEF, +0x1FF0,0x1FF1,'ῲ', 'ῳ', 'ῴ', 0x1FF5, 'ῶ', 'ῷ', 'ὸ', 'ό', 'ὼ', 'ώ', 'ῳ' +]; + +enum wchar[0x1FFD - 0x1F70] to_upper_greek_extended = [ + 'Ὰ', 'Ά', 'Ὲ', 'Έ', 'Ὴ', 'Ή', 'Ὶ', 'Ί', 'Ὸ', 'Ό', 'Ὺ', 'Ύ', 'Ὼ', 'Ώ', 0x1F7E,0x1F7F, + 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', + 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', + 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', + 'Ᾰ', 'Ᾱ', 0x1FB2, 'ᾼ', 0x1FB4,0x1FB5,0x1FB6,0x1FB7, 'Ᾰ', 'Ᾱ', 'Ὰ', 'Ά', 'ᾼ', 0x1FBD,0x1FBE,0x1FBF, +0x1FC0,0x1FC1,0x1FC2, 'ῌ', 0x1FC4,0x1FC5,0x1FC6,0x1FC7, 'Ὲ', 'Έ', 'Ὴ', 'Ή', 'ῌ', 0x1FCD,0x1FCE,0x1FCF, + 'Ῐ', 'Ῑ', 0x1FD2,0x1FD3,0x1FD4,0x1FD5,0x1FD6,0x1FD7, 'Ῐ', 'Ῑ', 'Ὶ', 'Ί',0x1FDC,0x1FDD,0x1FDE,0x1FDF, + 'Ῠ', 'Ῡ', 0x1FE2,0x1FE3,0x1FE4, 'Ῥ', 0x1FE6,0x1FE7, 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', 'Ῥ', 0x1FED,0x1FEE,0x1FEF, +0x1FF0,0x1FF1,0x1FF2, 'ῼ', 0x1FF4,0x1FF5,0x1FF6,0x1FF7, 'Ὸ', 'Ό', 'Ὼ', 'Ώ', 'ῼ' +]; + +// NOTE: Cyrillic is runtime calculable, no tables required! + + unittest { + immutable ushort[5] surrogates = [ 0xD800, 0xD800, 0xDC00, 0xD800, 0x0020 ]; + + // test uni_seq_len functions + assert(uni_seq_len("Hello, World!") == 1); + assert(uni_seq_len("ñowai!") == 2); + assert(uni_seq_len("你好") == 3); + assert(uni_seq_len("😊wow!") == 4); + assert(uni_seq_len("\xFFHello") == 1); + assert(uni_seq_len("\xC2") == 1); + assert(uni_seq_len("\xC2Hello") == 1); + assert(uni_seq_len("\xE2") == 1); + assert(uni_seq_len("\xE2Hello") == 1); + assert(uni_seq_len("\xE2\x82") == 2); + assert(uni_seq_len("\xE2\x82Hello") == 2); + assert(uni_seq_len("\xF0") == 1); + assert(uni_seq_len("\xF0Hello") == 1); + assert(uni_seq_len("\xF0\x9F") == 2); + assert(uni_seq_len("\xF0\x9FHello") == 2); + assert(uni_seq_len("\xF0\x9F\x98") == 3); + assert(uni_seq_len("\xF0\x9F\x98Hello") == 3); + assert(uni_seq_len("Hello, World!"w) == 1); + assert(uni_seq_len("ñowai!"w) == 1); + assert(uni_seq_len("你好"w) == 1); + assert(uni_seq_len("😊wow!"w) == 2); + assert(uni_seq_len(cast(wchar[])surrogates[0..1]) == 1); + assert(uni_seq_len(cast(wchar[])surrogates[0..2]) == 1); + assert(uni_seq_len(cast(wchar[])surrogates[2..3]) == 1); + assert(uni_seq_len(cast(wchar[])surrogates[3..5]) == 1); + assert(uni_seq_len("😊wow!"d) == 1); + + // test uni_strlen + assert(uni_strlen("Hello, World!") == 13); + assert(uni_strlen("ñowai!") == 6); + assert(uni_strlen("你好") == 2); + assert(uni_strlen("😊wow!") == 5); + assert(uni_strlen("\xFFHello") == 6); + assert(uni_strlen("\xC2") == 1); + assert(uni_strlen("\xC2Hello") == 6); + assert(uni_strlen("\xE2") == 1); + assert(uni_strlen("\xE2Hello") == 6); + assert(uni_strlen("\xE2\x82") == 1); + assert(uni_strlen("\xE2\x82Hello") == 6); + assert(uni_strlen("\xF0") == 1); + assert(uni_strlen("\xF0Hello") == 6); + assert(uni_strlen("\xF0\x9F") == 1); + assert(uni_strlen("\xF0\x9FHello") == 6); + assert(uni_strlen("\xF0\x9F\x98") == 1); + assert(uni_strlen("\xF0\x9F\x98Hello") == 6); + assert(uni_strlen("Hello, World!"w) == 13); + assert(uni_strlen("ñowai!"w) == 6); + assert(uni_strlen("你好"w) == 2); + assert(uni_strlen("😊wow!"w) == 5); + assert(uni_strlen(cast(wchar[])surrogates[0..1]) == 1); + assert(uni_strlen(cast(wchar[])surrogates[0..2]) == 2); + assert(uni_strlen(cast(wchar[])surrogates[2..3]) == 1); + assert(uni_strlen(cast(wchar[])surrogates[3..5]) == 2); + assert(uni_strlen("😊wow!"d) == 5); + + // test next_dchar functions + size_t sl; + assert(next_dchar("Hello, World!", sl) == 'H' && sl == 1); + assert(next_dchar("ñowai!", sl) == 'ñ' && sl == 2); + assert(next_dchar("你好", sl) == '你' && sl == 3); + assert(next_dchar("😊wow!", sl) == '😊' && sl == 4); + assert(next_dchar("\xFFHello", sl) == '�' && sl == 1); + assert(next_dchar("\xC2", sl) == '�' && sl == 1); + assert(next_dchar("\xC2Hello", sl) == '�' && sl == 1); + assert(next_dchar("\xE2", sl) == '�' && sl == 1); + assert(next_dchar("\xE2Hello", sl) == '�' && sl == 1); + assert(next_dchar("\xE2\x82", sl) == '�' && sl == 2); + assert(next_dchar("\xE2\x82Hello", sl) == '�' && sl == 2); + assert(next_dchar("\xF0", sl) == '�' && sl == 1); + assert(next_dchar("\xF0Hello", sl) == '�' && sl == 1); + assert(next_dchar("\xF0\x9F", sl) == '�' && sl == 2); + assert(next_dchar("\xF0\x9FHello", sl) == '�' && sl == 2); + assert(next_dchar("\xF0\x9F\x98", sl) == '�' && sl == 3); + assert(next_dchar("\xF0\x9F\x98Hello", sl) == '�' && sl == 3); + assert(next_dchar("Hello, World!"w, sl) == 'H' && sl == 1); + assert(next_dchar("ñowai!"w, sl) == 'ñ' && sl == 1); + assert(next_dchar("你好"w, sl) == '你' && sl == 1); + assert(next_dchar("😊wow!"w, sl) == '😊' && sl == 2); + assert(next_dchar(cast(wchar[])surrogates[0..1], sl) == '�' && sl == 1); + assert(next_dchar(cast(wchar[])surrogates[0..2], sl) == '�' && sl == 1); + assert(next_dchar(cast(wchar[])surrogates[2..3], sl) == '�' && sl == 1); + assert(next_dchar(cast(wchar[])surrogates[3..5], sl) == '�' && sl == 1); + assert(next_dchar("😊wow!"d, sl) == '😊' && sl == 1); + immutable dstring unicode_test = "Basic ASCII: Hello, World!\n" ~ + "Extended Latin: Café, résumé, naïve, jalapeño\n" ~ "BMP Examples: 你好, مرحبا, שלום, 😊, ☂️\n" ~ "Supplementary Planes: 𐍈, 𝒜, 🀄, 🚀\n" ~ "Surrogate Pair Test: 😀👨‍👩‍👧‍👦\n" ~ @@ -272,5 +901,127 @@ unittest // TODO: test all the error cases; invalid characters, buffer overflows, truncated inputs, etc... //... -} + // test uni_to_lower and uni_to_upper + assert(uni_to_lower('A') == 'a'); + assert(uni_to_lower('Z') == 'z'); + assert(uni_to_lower('a') == 'a'); + assert(uni_to_lower('z') == 'z'); + assert(uni_to_lower('À') == 'à'); + assert(uni_to_lower('Ý') == 'ý'); + assert(uni_to_lower('Ÿ') == 'ÿ'); + assert(uni_to_lower('ÿ') == 'ÿ'); + assert(uni_to_lower('ß') == 'ß'); + assert(uni_to_lower('ẞ') == 'ß'); + assert(uni_to_lower('Α') == 'α'); + assert(uni_to_lower('Ω') == 'ω'); + assert(uni_to_lower('α') == 'α'); + assert(uni_to_lower('ω') == 'ω'); + assert(uni_to_lower('Ḁ') == 'ḁ'); + assert(uni_to_lower('ḁ') == 'ḁ'); + assert(uni_to_lower('ẝ') == 'ẝ'); + assert(uni_to_lower('😊') == '😊'); + assert(uni_to_upper('a') == 'A'); + assert(uni_to_upper('z') == 'Z'); + assert(uni_to_upper('A') == 'A'); + assert(uni_to_upper('Z') == 'Z'); + assert(uni_to_upper('à') == 'À'); + assert(uni_to_upper('ý') == 'Ý'); + assert(uni_to_upper('ÿ') == 'Ÿ'); + assert(uni_to_upper('Ÿ') == 'Ÿ'); + assert(uni_to_upper('ß') == 'ẞ'); + assert(uni_to_upper('ẞ') == 'ẞ'); + assert(uni_to_upper('α') == 'Α'); + assert(uni_to_upper('ω') == 'Ω'); + assert(uni_to_upper('Α') == 'Α'); + assert(uni_to_upper('Ω') == 'Ω'); + assert(uni_to_upper('ḁ') == 'Ḁ'); + assert(uni_to_upper('Ḁ') == 'Ḁ'); + assert(uni_to_upper('😊') == '😊'); + assert(uni_to_upper('џ') == 'Џ'); + assert(uni_to_upper('Џ') == 'Џ'); + assert(uni_to_upper('д') == 'Д'); + assert(uni_to_upper('Д') == 'Д'); + assert(uni_to_upper('ѻ') == 'Ѻ'); + assert(uni_to_upper('Ѻ') == 'Ѻ'); + assert(uni_to_upper('ԫ') == 'Ԫ'); + assert(uni_to_upper('Ԫ') == 'Ԫ'); + assert(uni_to_upper('ա') == 'Ա'); + assert(uni_to_upper('Ա') == 'Ա'); + + // test uni_compare + assert(uni_compare("Hello", "Hello") == 0); + assert(uni_compare("Hello", "hello") < 0); + assert(uni_compare("hello", "Hello") > 0); + assert(uni_compare("Hello", "Hello, World!") < 0); + assert(uni_compare("Café", "Café") == 0); + assert(uni_compare("Café", "CafÉ") > 0); + assert(uni_compare("CafÉ", "Café") < 0); + assert(uni_compare("Hello, 世界", "Hello, 世界") == 0); + assert(uni_compare("Hello, 世界", "Hello, 世") > 0); + assert(uni_compare("Hello, 世", "Hello, 世界") < 0); + assert(uni_compare("Hello, 😊", "Hello, 😊") == 0); + assert(uni_compare("Hello, 😊", "Hello, 😢") < 0); + assert(uni_compare("😊A", "😊a") < 0); + + // test uni_compare_i + assert(uni_compare_i("Hello", "Hello") == 0); + assert(uni_compare_i("Hello", "hello") == 0); + assert(uni_compare_i("hello", "Hello") == 0); + assert(uni_compare_i("Hello", "Hello, World!") < 0); + assert(uni_compare_i("hello", "HORLD") < 0); + assert(uni_compare_i("Hello, 世界", "hello, 世界") == 0); + assert(uni_compare_i("Hello, 世界", "hello, 世") > 0); + assert(uni_compare_i("Hello, 世", "hello, 世界") < 0); + assert(uni_compare_i("Hello, 😊", "hello, 😊") == 0); + assert(uni_compare_i("Hello, 😊", "hello, 😢") < 0); + assert(uni_compare_i("😊a", "😊B") < 0); + assert(uni_compare_i("AZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ", "azàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþ") == 0); // basic latin + latin-1 supplement + assert(uni_compare_i("ŸĀĦĬIJĹŁŇŊŒŠŦŶŹŽ", "ÿāħĭijĺłňŋœšŧŷźž") == 0); // more latin extended-a characters + assert(uni_compare_i("ḀṤẔẠỸỺỼỾ", "ḁṥẕạỹỻỽỿ") == 0); // just the extended latin + + // test various language pangrams! + assert(uni_compare_i("The quick brown fox jumps over the lazy dog", "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG") == 0); // english + assert(uni_compare_i("Sævör grét áðan því úlpan var ónýt", "SÆVÖR GRÉT ÁÐAN ÞVÍ ÚLPAN VAR ÓNÝT") == 0); // icelandic + assert(uni_compare_i("Příliš žluťoučký kůň úpěl ďábelské ódy.", "PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ ÓDY.") == 0); // czech + assert(uni_compare_i("Zażółć gęślą jaźń.", "ZAŻÓŁĆ GĘŚLĄ JAŹŃ.") == 0); // polish + assert(uni_compare_i("Φλεγματικά χρώματα που εξοβελίζουν ψευδαισθήσεις.", "ΦΛΕΓΜΑΤΙΚΆ ΧΡΏΜΑΤΑ ΠΟΥ ΕΞΟΒΕΛΊΖΟΥΝ ΨΕΥΔΑΙΣΘΉΣΕΙΣ.") == 0); // greek + assert(uni_compare_i("Любя, съешь щипцы, — вздохнёт мэр, — Кайф жгуч!", "ЛЮБЯ, СЪЕШЬ ЩИПЦЫ, — ВЗДОХНЁТ МЭР, — КАЙФ ЖГУЧ!") == 0); // russian + assert(uni_compare_i("Բել դղյակի ձախ ժամն օֆ ազգությանը ցպահանջ չճշտած վնաս էր եւ փառք։", "ԲԵԼ ԴՂՅԱԿԻ ՁԱԽ ԺԱՄՆ ՕՖ ԱԶԳՈՒԹՅԱՆԸ ՑՊԱՀԱՆՋ ՉՃՇՏԱԾ ՎՆԱՍ ԷՐ ԵՒ ՓԱՌՔ։") == 0); // armenian + assert(uni_compare_i("აბგად ევზეთ იკალ მანო, პაჟა რასტა უფქა ღაყაშ, ჩაცა ძაწა ჭახა ჯაჰო", "ᲐᲑᲒᲐᲓ ᲔᲕᲖᲔᲗ ᲘᲙᲐᲚ ᲛᲐᲜᲝ, ᲞᲐᲟᲐ ᲠᲐᲡᲢᲐ ᲣᲤᲥᲐ ᲦᲐᲧᲐᲨ, ᲩᲐᲪᲐ ᲫᲐᲬᲐ ᲭᲐᲮᲐ ᲯᲐᲰᲝ") == 0); // georgian modern + assert(uni_compare_i("ⴘⴄⴅ ⴟⴓ ⴠⴡⴢ ⴣⴤⴥ ⴇⴍⴚ ⴞⴐⴈ ⴝⴋⴊⴈ.", "ႸႤႥ ႿႳ ჀჁჂ ჃჄჅ ႧႭႺ ႾႰႨ ႽႫႪႨ.") == 0); // georgian ecclesiastical + assert(uni_compare_i("Ⲁⲛⲟⲕ ⲡⲉ ϣⲏⲙ ⲛ̄ⲕⲏⲙⲉ.", "ⲀⲚⲞⲔ ⲠⲈ ϢⲎⲘ Ⲛ̄ⲔⲎⲘⲈ.") == 0); // coptic + + // test the special-cases around german 'ß' (0xDF) and 'ẞ' (0x1E9E) + // check sort order + assert(uni_compare_i("ß", "sr") > 0); + assert(uni_compare_i("ß", "ss") == 0); + assert(uni_compare_i("ß", "st") < 0); + assert(uni_compare_i("sr", "ß") < 0); + assert(uni_compare_i("ss", "ß") == 0); + assert(uni_compare_i("st", "ß") > 0); + // check truncated comparisons + assert(uni_compare_i("ß", "s") > 0); + assert(uni_compare_i("ß", "r") > 0); + assert(uni_compare_i("ß", "t") < 0); + assert(uni_compare_i("s", "ß") < 0); + assert(uni_compare_i("r", "ß") < 0); + assert(uni_compare_i("t", "ß") > 0); + assert(uni_compare_i("ä", "ß") > 0); + assert(uni_compare_i("sß", "ss") > 0); + assert(uni_compare_i("sß", "ß") > 0); + assert(uni_compare_i("sß", "ßß") < 0); + assert(uni_compare_i("ss", "sß") < 0); + assert(uni_compare_i("ß", "sß") < 0); + assert(uni_compare_i("ßß", "sß") > 0); + // check uneven/recursive comparisons + assert(uni_compare_i("ßẞ", "ẞß") == 0); + assert(uni_compare_i("sß", "ẞs") == 0); + assert(uni_compare_i("ẞs", "sß") == 0); + assert(uni_compare_i("ẞß", "sßs") == 0); + assert(uni_compare_i("sẞs", "ẞß") == 0); + assert(uni_compare_i("sẞsß", "ßsßs") == 0); + assert(uni_compare_i("ẞsßs", "sßsß") == 0); + assert(uni_compare_i("ßßßs", "sßßß") == 0); + assert(uni_compare_i("sßßß", "ßßßs") == 0); +} From 78124e8a4511921f495e05e75d01ff0ca2934ead Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 14 Oct 2025 10:47:54 +1000 Subject: [PATCH 022/138] Several socket/inet improvements. --- src/urt/inet.d | 94 ++++++++-- src/urt/socket.d | 447 +++++++++++++++++++++++++++++++---------------- 2 files changed, 375 insertions(+), 166 deletions(-) diff --git a/src/urt/inet.d b/src/urt/inet.d index 9f90c55..e4c6d1a 100644 --- a/src/urt/inet.d +++ b/src/urt/inet.d @@ -35,7 +35,7 @@ enum WellKnownPort : ushort } enum IPAddr IPAddrLit(string addr) = () { IPAddr a; size_t taken = a.fromString(addr); assert(taken == addr.length, "Not an IPv4 address"); return a; }(); -//enum IPv6Addr IPv6AddrLit(string addr) = () { IPv6Addr a; size_t taken = a.fromString(addr); assert(taken == addr.length, "Not an IPv6 address"); return a; }(); +enum IPv6Addr IPv6AddrLit(string addr) = () { IPv6Addr a; size_t taken = a.fromString(addr); assert(taken == addr.length, "Not an IPv6 address"); return a; }(); struct IPAddr { @@ -313,9 +313,45 @@ nothrow @nogc: ptrdiff_t fromString(const(char)[] str) { - ushort[8] t; - size_t offset = 0; - assert(false); + ushort[8][2] t = void; + ubyte[2] count; + int part = 0; + + size_t offset = 0, len; + while (offset < str.length) + { + if (offset < str.length - 1 && str[offset] == ':' && str[offset + 1] == ':') + { + if (part != 0) + return -1; + part = 1; + offset += 2; + if (offset == str.length) + break; + } + else if (count[part] > 0) + { + if (str[offset] != ':') + break; + if (++offset == str.length) + return -1; + } + if (str[offset] == ':') + return -1; + ulong i = str[offset..$].parse_int(&len, 16); + if (len == 0) + break; + if (i > ushort.max || count[0] + count[1] == 8) + return -1; + t[part][count[part]++] = cast(ushort)i; + offset += len; + } + if (part == 0 && count[0] != 8) + return -1; + + s[0 .. count[0]] = t[0][0 .. count[0]]; + s[count[0] .. 8 - count[1]] = 0; + s[8 - count[1] .. 8] = t[1][0 .. count[1]]; return offset; } @@ -498,7 +534,7 @@ nothrow @nogc: return -1; size_t t; ulong plen = s[taken..$].parse_int(&t); - if (t == 0 || plen > 32) + if (t == 0 || plen > 128) return -1; addr = a; prefixLen = cast(ubyte)plen; @@ -679,7 +715,7 @@ nothrow @nogc: { size_t t; ulong p = s[++taken..$].parse_int(&t); - if (t == 0 || p > 0xFFFF) + if (t == 0 || p > ushort.max) return -1; taken += t; port = cast(ushort)p; @@ -739,6 +775,9 @@ unittest IPSubnet subnet; assert(subnet.fromString("192.168.0.0/24") == 14 && subnet == IPSubnet(IPAddr(192, 168, 0, 0), 24)); assert(subnet.fromString("0.0.0.0/0") == 9 && subnet == IPSubnet(IPAddr(0, 0, 0, 0), 0)); + assert(subnet.fromString("1.2.3.4") == -1); + assert(subnet.fromString("1.2.3.4/33") == -1); + assert(subnet.fromString("1.2.3.4/a") == -1); assert(~IPv6Addr(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF0, 0, 0, 0) == IPv6Addr(0, 0, 0, 0, 0xF, 0xFFFF, 0xFFFF, 0xFFFF)); assert((IPv6Addr(0xFFFF, 0, 1, 2, 3, 4, 5, 6) & IPv6Addr(0xFF00, 0, 3, 0, 0, 0, 0, 2)) == IPv6Addr(0xFF00, 0, 1, 0, 0, 0, 0, 2)); @@ -755,17 +794,40 @@ unittest assert(tmp[0 .. IPv6Addr(0, 0, 0, 0, 0, 0, 0, 1).toString(tmp, null, null)] == "::1"); assert(tmp[0 .. IPv6Addr(0, 0, 0, 0, 0, 0, 0, 0).toString(tmp, null, null)] == "::"); -// IPv6Addr addr6; -// assert(addr6.fromString("::2") == 3 && addr6 == IPv6Addr(0, 0, 0, 0, 0, 0, 0, 2)); -// assert(addr6.fromString("1::2") == 3 && addr6 == IPv6Addr(1, 0, 0, 0, 0, 0, 0, 2)); -// assert(addr6.fromString("2001:db8::1/24") == 14 && addr6 == IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); + IPv6Addr addr6; + assert(addr6.fromString("::") == 2 && addr6.s[] == [0,0,0,0,0,0,0,0]); + assert(addr6.fromString("1::") == 3 && addr6.s[] == [1,0,0,0,0,0,0,0]); + assert(addr6.fromString("::2") == 3 && addr6.s[] == [0,0,0,0,0,0,0,2]); + assert(addr6.fromString("1::2") == 4 && addr6.s[] == [1,0,0,0,0,0,0,2]); + assert(addr6.fromString("1:FFFF::2") == 9 && addr6.s[] == [1,0xFFFF,0,0,0,0,0,2]); + assert(addr6.fromString("1:2:3:4:5:6:7:8") == 15 && addr6.s[] == [1,2,3,4,5,6,7,8]); + assert(addr6.fromString("1:2:3:4") == -1); + assert(addr6.fromString("1:2:3:4:5:6:7:8:9") == -1); + assert(addr6.fromString("1:2:3:4:5:6:7:8:") == -1); + assert(addr6.fromString("10000::2") == -1); + assert(addr6.fromString(":2") == -1); + assert(addr6.fromString("2:") == -1); + assert(addr6.fromString(":2:") == -1); + assert(addr6.fromString(":2::") == -1); + assert(addr6.fromString(":2::1") == -1); + assert(addr6.fromString("::2:") == -1); + assert(addr6.fromString("1::2:") == -1); + assert(addr6.fromString("1:::2") == -1); + assert(addr6.fromString("::G") == 2 && addr6 == IPv6Addr(0, 0, 0, 0, 0, 0, 0, 0)); + assert(addr6.fromString("1::2.3") == 4 && addr6 == IPv6Addr(1, 0, 0, 0, 0, 0, 0, 2)); + assert(addr6.fromString("1::2 4") == 4 && addr6 == IPv6Addr(1, 0, 0, 0, 0, 0, 0, 2)); + assert(addr6.fromString("2001:db8::1/24") == 11 && addr6 == IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); + assert(tmp[0 .. IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 24).toString(tmp, null, null)] == "2001:db8::1/24"); assert(tmp[0 .. IPv6Subnet(IPv6Addr(), 0).toString(tmp, null, null)] == "::/0"); -// IPv6Subnet subnet6; -// assert(subnet6.fromString("2001:db8::1/24") == 14 && subnet6 == IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 24)); -// assert(subnet6.fromString("::/0") == 4 && subnet6 == IPv6Subnet(IPv6Addr(), 0)); + IPv6Subnet subnet6; + assert(subnet6.fromString("2001:db8::1/24") == 14 && subnet6 == IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 24)); + assert(subnet6.fromString("::/0") == 4 && subnet6 == IPv6Subnet(IPv6Addr(), 0)); + assert(subnet6.fromString("1::2") == -1); + assert(subnet6.fromString("1::2/129") == -1); + assert(subnet6.fromString("1::2/a") == -1); assert(tmp[0 .. InetAddress(IPAddr(192, 168, 0, 1), 12345).toString(tmp, null, null)] == "192.168.0.1:12345"); assert(tmp[0 .. InetAddress(IPAddr(10, 0, 0, 0), 21).toString(tmp, null, null)] == "10.0.0.0:21"); @@ -777,8 +839,10 @@ unittest assert(address.fromString("192.168.0.1:21") == 14 && address == InetAddress(IPAddr(192, 168, 0, 1), 21)); assert(address.fromString("10.0.0.1:12345") == 14 && address == InetAddress(IPAddr(10, 0, 0, 1), 12345)); -// assert(address.fromString("[2001:db8:0:1::1]:12345") == 14 && address == InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1), 12345)); -// assert(address.fromString("[::]:21") == 14 && address == InetAddress(IPv6Addr(), 21)); + assert(address.fromString("[2001:db8:0:1::1]:12345") == 23 && address == InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1), 12345)); + assert(address.fromString("[::]:21") == 7 && address == InetAddress(IPv6Addr(), 21)); + assert(address.fromString("[::]:a") == -1); + assert(address.fromString("[::]:65536") == -1); // IPAddr sorting tests { diff --git a/src/urt/socket.d b/src/urt/socket.d index 304edee..7123163 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -20,6 +20,9 @@ version (Windows) version = HasIPv6; alias SocketHandle = SOCKET; + + enum IPV6_RECVPKTINFO = 49; + enum IPV6_PKTINFO = 50; } else version (Posix) { @@ -28,7 +31,7 @@ else version (Posix) import core.sys.posix.poll; import core.sys.posix.unistd : close, gethostname; import urt.internal.os; // use ImportC to import system C headers... - import core.sys.posix.netinet.in_ : sockaddr_in6; + import core.sys.posix.netinet.in_ : in6_addr, sockaddr_in6; alias _bind = urt.internal.os.bind, _listen = urt.internal.os.listen, _connect = urt.internal.os.connect, _accept = urt.internal.os.accept, _send = urt.internal.os.send, _sendto = urt.internal.os.sendto, @@ -116,12 +119,14 @@ enum SocketOption : ubyte multicast = first_ip_option, multicast_loopback, multicast_ttl, + ip_pktinfo, // IPv6 options first_ipv6_option, + ipv6_pktinfo = first_ipv6_option, // ICMP options - first_icmp_option = first_ipv6_option, + first_icmp_option, // ICMPv6 options first_icmpv6_option = first_icmp_option, @@ -134,7 +139,6 @@ enum SocketOption : ubyte tcp_keep_alive, // Apple: similar to KeepIdle tcp_no_delay, - // UDP options first_udp_option, } @@ -195,6 +199,7 @@ Result create_socket(AddressFamily af, SocketType type, Protocol proto, out Sock socket.handle = .socket(s_addressFamily[af], s_socketType[type], s_protocol[proto]); if (socket == Socket.invalid) return socket_getlasterror(); + return Result.success; } @@ -247,10 +252,10 @@ Result bind(Socket socket, ref const InetAddress address) { ubyte[512] buffer = void; size_t addrLen; - sockaddr* sockAddr = make_sockaddr(address, buffer, addrLen); - assert(sockAddr, "Invalid socket address"); + sockaddr* sock_addr = make_sockaddr(address, buffer, addrLen); + assert(sock_addr, "Invalid socket address"); - if (_bind(socket.handle, sockAddr, cast(int)addrLen) < 0) + if (_bind(socket.handle, sock_addr, cast(int)addrLen) < 0) return socket_getlasterror(); return Result.success; } @@ -266,15 +271,15 @@ Result connect(Socket socket, ref const InetAddress address) { ubyte[512] buffer = void; size_t addrLen; - sockaddr* sockAddr = make_sockaddr(address, buffer, addrLen); - assert(sockAddr, "Invalid socket address"); + sockaddr* sock_addr = make_sockaddr(address, buffer, addrLen); + assert(sock_addr, "Invalid socket address"); - if (_connect(socket.handle, sockAddr, cast(int)addrLen) < 0) + if (_connect(socket.handle, sock_addr, cast(int)addrLen) < 0) return socket_getlasterror(); return Result.success; } -Result accept(Socket socket, out Socket connection, InetAddress* connectingSocketAddress = null) +Result accept(Socket socket, out Socket connection, InetAddress* remote_address = null, InetAddress* local_address = null) { char[sockaddr_storage.sizeof] buffer = void; sockaddr* addr = cast(sockaddr*)buffer.ptr; @@ -283,15 +288,21 @@ Result accept(Socket socket, out Socket connection, InetAddress* connectingSocke connection.handle = _accept(socket.handle, addr, &size); if (connection == Socket.invalid) return socket_getlasterror(); - else if (connectingSocketAddress) - *connectingSocketAddress = make_InetAddress(addr); + if (remote_address) + *remote_address = make_InetAddress(addr); + if (local_address) + { + if (getsockname(connection.handle, addr, &size) < 0) + return socket_getlasterror(); + *local_address = make_InetAddress(addr); + } // platforms are inconsistent regarding whether accept inherits the listening socket's blocking mode // for consistentency, we always set blocking on the accepted socket connection.set_socket_option(SocketOption.non_blocking, false); return Result.success; } -Result send(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.none, size_t* bytesSent = null) +Result send(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.none, size_t* bytes_sent = null) { Result r = Result.success; @@ -301,43 +312,43 @@ Result send(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.none r = socket_getlasterror(); sent = 0; } - if (bytesSent) - *bytesSent = sent; + if (bytes_sent) + *bytes_sent = sent; return r; } -Result sendto(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.none, const InetAddress* address = null, size_t* bytesSent = null) +Result sendto(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.none, const InetAddress* address = null, size_t* bytes_sent = null) { ubyte[sockaddr_storage.sizeof] tmp = void; size_t addrLen; - sockaddr* sockAddr = null; + sockaddr* sock_addr = null; if (address) { - sockAddr = make_sockaddr(*address, tmp, addrLen); - assert(sockAddr, "Invalid socket address"); + sock_addr = make_sockaddr(*address, tmp, addrLen); + assert(sock_addr, "Invalid socket address"); } Result r = Result.success; - ptrdiff_t sent = _sendto(socket.handle, message.ptr, cast(int)message.length, map_message_flags(flags), sockAddr, cast(int)addrLen); + ptrdiff_t sent = _sendto(socket.handle, message.ptr, cast(int)message.length, map_message_flags(flags), sock_addr, cast(int)addrLen); if (sent < 0) { r = socket_getlasterror(); sent = 0; } - if (bytesSent) - *bytesSent = sent; + if (bytes_sent) + *bytes_sent = sent; return r; } -Result recv(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.none, size_t* bytesReceived) +Result recv(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.none, size_t* bytes_received) { Result r = Result.success; ptrdiff_t bytes = _recv(socket.handle, buffer.ptr, cast(int)buffer.length, map_message_flags(flags)); if (bytes > 0) - *bytesReceived = bytes; + *bytes_received = bytes; else { - *bytesReceived = 0; + *bytes_received = 0; if (bytes == 0) { // if we request 0 bytes, we receive 0 bytes, and it doesn't imply end-of-stream @@ -362,30 +373,89 @@ Result recv(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.none, size_t return r; } -Result recvfrom(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.none, InetAddress* senderAddress = null, size_t* bytesReceived) +Result recvfrom(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.none, InetAddress* sender_address = null, size_t* bytes_received, InetAddress* local_address = null) { - char[sockaddr_storage.sizeof] addrBuffer = void; - sockaddr* addr = cast(sockaddr*)addrBuffer.ptr; - socklen_t size = addrBuffer.sizeof; + char[sockaddr_storage.sizeof] addr_buffer = void; + sockaddr* addr = cast(sockaddr*)addr_buffer.ptr; - Result r = Result.success; - ptrdiff_t bytes = _recvfrom(socket.handle, buffer.ptr, cast(int)buffer.length, map_message_flags(flags), addr, &size); - if (bytes >= 0) - *bytesReceived = bytes; + if (local_address) + { + version (Windows) + { + assert(WSARecvMsg, "WSARecvMsg not available!"); + + void[1500] ctrl = void; // HUGE BUFFER! + + WSABUF msg_buf; + msg_buf.buf = cast(char*)buffer.ptr; + msg_buf.len = cast(uint)buffer.length; + + WSAMSG msg; + msg.name = addr; + msg.namelen = addr_buffer.sizeof; + msg.lpBuffers = &msg_buf; + msg.dwBufferCount = 1; + msg.Control.buf = cast(char*)ctrl.ptr; + msg.Control.len = cast(uint)ctrl.length; + msg.dwFlags = 0; + uint bytes; + int r = WSARecvMsg(socket.handle, &msg, &bytes, null, null); + if (r == 0) + *bytes_received = bytes; + else + { + *bytes_received = 0; + goto fail; + } + + // parse the control messages + *local_address = InetAddress(); + for (WSACMSGHDR* c = WSA_CMSG_FIRSTHDR(&msg); c != null; c = WSA_CMSG_NXTHDR(&msg, c)) + { + if (c.cmsg_level == IPPROTO_IP && c.cmsg_type == IP_PKTINFO) + { + IN_PKTINFO* pk = cast(IN_PKTINFO*)WSA_CMSG_DATA(c); + *local_address = InetAddress(make_IPAddr(pk.ipi_addr), 0); // TODO: be nice to populate the listening port... + // pk.ipi_ifindex = receiving interface index + } + if (c.cmsg_level == IPPROTO_IPV6 && c.cmsg_type == IPV6_PKTINFO) + { + IN6_PKTINFO* pk6 = cast(IN6_PKTINFO*)WSA_CMSG_DATA(c); + *local_address = InetAddress(make_IPv6Addr(pk6.ipi6_addr), 0); // TODO: be nice to populate the listening port... + // pk6.ipi6_ifindex = receiving interface index + } + } + } + else + { + assert(false, "TODO: call recvmsg and all that..."); + } + } else { - *bytesReceived = 0; - - Result error = socket_getlasterror(); - SocketResult sockRes = socket_result(error); - if (sockRes != SocketResult.no_buffer && // buffers full - sockRes != SocketResult.connection_refused && // posix error - sockRes != SocketResult.connection_reset) // !!! windows may report this error, but it appears to mean something different on posix - r = error; + socklen_t size = addr_buffer.sizeof; + ptrdiff_t bytes = _recvfrom(socket.handle, buffer.ptr, cast(int)buffer.length, map_message_flags(flags), addr, &size); + if (bytes >= 0) + *bytes_received = bytes; + else + { + *bytes_received = 0; + goto fail; + } } - if (r && senderAddress) - *senderAddress = make_InetAddress(addr); - return r; + + if (sender_address) + *sender_address = make_InetAddress(addr); + return Result.success; + +fail: + Result error = socket_getlasterror(); + SocketResult sockRes = socket_result(error); + if (sockRes != SocketResult.no_buffer && // buffers full + sockRes != SocketResult.connection_refused && // posix error + sockRes != SocketResult.connection_reset) // !!! windows may report this error, but it appears to mean something different on posix + return error; + return Result.success; } Result set_socket_option(Socket socket, SocketOption option, const(void)* optval, size_t optlen) @@ -393,9 +463,9 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval Result r = Result.success; // check the option appears to be the proper datatype - const OptInfo* optInfo = &s_socketOptions[option]; - assert(optInfo.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); - assert(optlen == s_optTypeRtSize[optInfo.rt_type], "Socket option has incorrect payload size!"); + const OptInfo* opt_info = &s_socketOptions[option]; + assert(opt_info.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); + assert(optlen == s_optTypeRtSize[opt_info.rt_type], "Socket option has incorrect payload size!"); // special case for non-blocking // this is not strictly a 'socket option', but this rather simplifies our API @@ -423,7 +493,7 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval // LockGuard!SharedMutex lock(s_noSignalMut); // s_noSignal.InsertOrAssign(socket.handle, *cast(const(bool)*)optval); // -// if (optInfo.platform_type == OptType.unsupported) +// if (opt_info.platform_type == OptType.unsupported) // return r; // } @@ -436,15 +506,15 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval const(void)* arg = optval; int itmp = void; linger ling = void; - if (optInfo.rt_type != optInfo.platform_type) + if (opt_info.rt_type != opt_info.platform_type) { - switch (optInfo.rt_type) + switch (opt_info.rt_type) { // TODO: there are more converstions necessary as options/platforms are added case OptType.bool_: { const bool value = *cast(const(bool)*)optval; - switch (optInfo.platform_type) + switch (opt_info.platform_type) { case OptType.int_: itmp = value ? 1 : 0; @@ -457,7 +527,7 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval case OptType.duration: { const Duration value = *cast(const(Duration)*)optval; - switch (optInfo.platform_type) + switch (opt_info.platform_type) { case OptType.seconds: itmp = cast(int)value.as!"seconds"; @@ -482,53 +552,53 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval } // set the option - r.systemCode = setsockopt(socket.handle, s_sockOptLevel[level], optInfo.option, cast(const(char)*)arg, s_optTypePlatformSize[optInfo.platform_type]); + r.systemCode = setsockopt(socket.handle, s_sockOptLevel[level], opt_info.option, cast(const(char)*)arg, s_optTypePlatformSize[opt_info.platform_type]); return r; } Result set_socket_option(Socket socket, SocketOption option, bool value) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.bool_, "Incorrect value type for option"); + assert(opt_info.rt_type == OptType.bool_, "Incorrect value type for option"); return set_socket_option(socket, option, &value, bool.sizeof); } Result set_socket_option(Socket socket, SocketOption option, int value) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.int_, "Incorrect value type for option"); + assert(opt_info.rt_type == OptType.int_, "Incorrect value type for option"); return set_socket_option(socket, option, &value, int.sizeof); } Result set_socket_option(Socket socket, SocketOption option, Duration value) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.duration, "Incorrect value type for option"); + assert(opt_info.rt_type == OptType.duration, "Incorrect value type for option"); return set_socket_option(socket, option, &value, Duration.sizeof); } Result set_socket_option(Socket socket, SocketOption option, IPAddr value) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.inet_addr, "Incorrect value type for option"); + assert(opt_info.rt_type == OptType.inet_addr, "Incorrect value type for option"); return set_socket_option(socket, option, &value, IPAddr.sizeof); } Result set_socket_option(Socket socket, SocketOption option, ref MulticastGroup value) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.multicast_group, "Incorrect value type for option"); + assert(opt_info.rt_type == OptType.multicast_group, "Incorrect value type for option"); return set_socket_option(socket, option, &value, MulticastGroup.sizeof); } @@ -537,9 +607,9 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ Result r = Result.success; // check the option appears to be the proper datatype - const OptInfo* optInfo = &s_socketOptions[option]; - assert(optInfo.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); - assert(outputlen == s_optTypeRtSize[optInfo.rt_type], "Socket option has incorrect payload size!"); + const OptInfo* opt_info = &s_socketOptions[option]; + assert(opt_info.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); + assert(outputlen == s_optTypeRtSize[opt_info.rt_type], "Socket option has incorrect payload size!"); assert(option != SocketOption.non_blocking, "Socket option NonBlocking cannot be get"); @@ -552,9 +622,9 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ void* arg = output; int itmp = 0; linger ling = { 0, 0 }; - if (optInfo.rt_type != optInfo.platform_type) + if (opt_info.rt_type != opt_info.platform_type) { - switch (optInfo.platform_type) + switch (opt_info.platform_type) { case OptType.int_: case OptType.seconds: @@ -573,19 +643,19 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ } } - socklen_t writtenLen = s_optTypePlatformSize[optInfo.platform_type]; + socklen_t writtenLen = s_optTypePlatformSize[opt_info.platform_type]; // get the option - r.systemCode = getsockopt(socket.handle, s_sockOptLevel[level], optInfo.option, cast(char*)arg, &writtenLen); + r.systemCode = getsockopt(socket.handle, s_sockOptLevel[level], opt_info.option, cast(char*)arg, &writtenLen); - if (optInfo.rt_type != optInfo.platform_type) + if (opt_info.rt_type != opt_info.platform_type) { - switch (optInfo.rt_type) + switch (opt_info.rt_type) { // TODO: there are more converstions necessary as options/platforms are added case OptType.bool_: { bool* value = cast(bool*)output; - switch (optInfo.platform_type) + switch (opt_info.platform_type) { case OptType.int_: *value = !!itmp; @@ -597,7 +667,7 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ case OptType.duration: { Duration* value = cast(Duration*)output; - switch (optInfo.platform_type) + switch (opt_info.platform_type) { case OptType.seconds: *value = seconds(itmp); @@ -617,10 +687,10 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ } } - assert(optInfo.rt_type != OptType.inet_addr, "TODO: uncomment this block... for some reason, this block causes DMD to do a bad codegen!"); + assert(opt_info.rt_type != OptType.inet_addr, "TODO: uncomment this block... for some reason, this block causes DMD to do a bad codegen!"); /+ // Options expected in network-byte order - switch (optInfo.rt_type) + switch (opt_info.rt_type) { case OptType.INAddress: { @@ -637,37 +707,37 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ Result get_socket_option(Socket socket, SocketOption option, out bool output) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.bool_, "Incorrect value type for option"); + assert(opt_info.rt_type == OptType.bool_, "Incorrect value type for option"); return get_socket_option(socket, option, &output, bool.sizeof); } Result get_socket_option(Socket socket, SocketOption option, out int output) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.int_, "Incorrect value type for option"); + assert(opt_info.rt_type == OptType.int_, "Incorrect value type for option"); return get_socket_option(socket, option, &output, int.sizeof); } Result get_socket_option(Socket socket, SocketOption option, out Duration output) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.duration, "Incorrect value type for option"); + assert(opt_info.rt_type == OptType.duration, "Incorrect value type for option"); return get_socket_option(socket, option, &output, Duration.sizeof); } Result get_socket_option(Socket socket, SocketOption option, out IPAddr output) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rt_type == OptType.unsupported) + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) return InternalResult.unsupported; - assert(optInfo.rt_type == OptType.inet_addr, "Incorrect value type for option"); + assert(opt_info.rt_type == OptType.inet_addr, "Incorrect value type for option"); return get_socket_option(socket, option, &output, IPAddr.sizeof); } @@ -978,7 +1048,7 @@ SocketResult socket_result(Result result) sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_t addrLen) { - sockaddr* sockAddr = cast(sockaddr*)buffer.ptr; + sockaddr* sock_addr = cast(sockaddr*)buffer.ptr; switch (address.family) { @@ -988,7 +1058,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ if (buffer.length < sockaddr_in.sizeof) return null; - sockaddr_in* ain = cast(sockaddr_in*)sockAddr; + sockaddr_in* ain = cast(sockaddr_in*)sock_addr; memzero(ain, sockaddr_in.sizeof); ain.sin_family = s_addressFamily[AddressFamily.IPv4]; version (Windows) @@ -1013,7 +1083,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ if (buffer.length < sockaddr_in6.sizeof) return null; - sockaddr_in6* ain6 = cast(sockaddr_in6*)sockAddr; + sockaddr_in6* ain6 = cast(sockaddr_in6*)sock_addr; memzero(ain6, sockaddr_in6.sizeof); ain6.sin6_family = s_addressFamily[AddressFamily.IPv6]; storeBigEndian(&ain6.sin6_port, cast(ushort)address._a.ipv6.port); @@ -1041,7 +1111,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ // if (buffer.length < sockaddr_un.sizeof) // return null; // -// sockaddr_un* aun = cast(sockaddr_un*)sockAddr; +// sockaddr_un* aun = cast(sockaddr_un*)sock_addr; // memzero(aun, sockaddr_un.sizeof); // aun.sun_family = s_addressFamily[AddressFamily.Unix]; // @@ -1053,7 +1123,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ } default: { - sockAddr = null; + sock_addr = null; addrLen = 0; assert(false, "Unsupported address family"); @@ -1061,52 +1131,33 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ } } - return sockAddr; + return sock_addr; } -InetAddress make_InetAddress(const(sockaddr)* sockAddress) +InetAddress make_InetAddress(const(sockaddr)* sock_address) { InetAddress addr; - addr.family = map_address_family(sockAddress.sa_family); + addr.family = map_address_family(sock_address.sa_family); switch (addr.family) { case AddressFamily.IPv4: { - const sockaddr_in* ain = cast(const(sockaddr_in)*)sockAddress; + const sockaddr_in* ain = cast(const(sockaddr_in)*)sock_address; addr._a.ipv4.port = loadBigEndian(&ain.sin_port); - version (Windows) - { - addr._a.ipv4.addr.b[0] = ain.sin_addr.S_un.S_un_b.s_b1; - addr._a.ipv4.addr.b[1] = ain.sin_addr.S_un.S_un_b.s_b2; - addr._a.ipv4.addr.b[2] = ain.sin_addr.S_un.S_un_b.s_b3; - addr._a.ipv4.addr.b[3] = ain.sin_addr.S_un.S_un_b.s_b4; - } - else version (Posix) - addr._a.ipv4.addr.address = ain.sin_addr.s_addr; - else - assert(false, "Not implemented!"); + addr._a.ipv4.addr = make_IPAddr(ain.sin_addr); break; } case AddressFamily.IPv6: { version (HasIPv6) { - const sockaddr_in6* ain6 = cast(const(sockaddr_in6)*)sockAddress; + const sockaddr_in6* ain6 = cast(const(sockaddr_in6)*)sock_address; addr._a.ipv6.port = loadBigEndian(&ain6.sin6_port); addr._a.ipv6.flowInfo = loadBigEndian(cast(const(uint)*)&ain6.sin6_flowinfo); addr._a.ipv6.scopeId = loadBigEndian(cast(const(uint)*)&ain6.sin6_scope_id); - - for (int a = 0; a < 8; ++a) - { - version (Windows) - addr._a.ipv6.addr.s[a] = loadBigEndian(&ain6.sin6_addr.in6_u.u6_addr16[a]); - else version (Posix) - addr._a.ipv6.addr.s[a] = loadBigEndian(cast(const(ushort)*)ain6.sin6_addr.s6_addr + a); - else - assert(false, "Not implemented!"); - } + addr._a.ipv6.addr = make_IPv6Addr(ain6.sin6_addr); } else assert(false, "Platform does not support IPv6!"); @@ -1116,7 +1167,7 @@ InetAddress make_InetAddress(const(sockaddr)* sockAddress) { // version (HasUnixSocket) // { -// const sockaddr_un* aun = cast(const(sockaddr_un)*)sockAddress; +// const sockaddr_un* aun = cast(const(sockaddr_un)*)sock_address; // // memcpy(addr.un.path, aun.sun_path, UNIX_PATH_LEN); // if (UNIX_PATH_LEN < UnixPathLen) @@ -1134,6 +1185,37 @@ InetAddress make_InetAddress(const(sockaddr)* sockAddress) return addr; } +IPAddr make_IPAddr(ref const in_addr in4) +{ + IPAddr addr; + version (Windows) + { + addr.b[0] = in4.S_un.S_un_b.s_b1; + addr.b[1] = in4.S_un.S_un_b.s_b2; + addr.b[2] = in4.S_un.S_un_b.s_b3; + addr.b[3] = in4.S_un.S_un_b.s_b4; + } + else version (Posix) + addr.address = in4.s_addr; + else + assert(false, "Not implemented!"); + return addr; +} + +IPv6Addr make_IPv6Addr(ref const in6_addr in6) +{ + IPv6Addr addr; + for (int a = 0; a < 8; ++a) + { + version (Windows) + addr.s[a] = loadBigEndian(&in6.in6_u.u6_addr16[a]); + else version (Posix) + addr.s[a] = loadBigEndian(cast(const(ushort)*)in6.s6_addr + a); + else + assert(false, "Not implemented!"); + } + return addr; +} private: @@ -1277,6 +1359,8 @@ version (Windows) // BS_NETWORK_WINDOWS_VERSION >= _WIN32_WINNT_VISTA OptInfo( IP_ADD_MEMBERSHIP, OptType.multicast_group, OptType.multicast_group ), OptInfo( IP_MULTICAST_LOOP, OptType.bool_, OptType.int_ ), OptInfo( IP_MULTICAST_TTL, OptType.int_, OptType.int_ ), + OptInfo( IP_PKTINFO, OptType.bool_, OptType.int_ ), + OptInfo( IPV6_RECVPKTINFO, OptType.bool_, OptType.int_ ), OptInfo( -1, OptType.unsupported, OptType.unsupported ), OptInfo( -1, OptType.unsupported, OptType.unsupported ), OptInfo( -1, OptType.unsupported, OptType.unsupported ), @@ -1299,6 +1383,8 @@ else version (linux) // BS_NETWORK_WINDOWS_VERSION >= _WIN32_WINNT_VISTA OptInfo( IP_ADD_MEMBERSHIP, OptType.multicast_group, OptType.multicast_group ), OptInfo( IP_MULTICAST_LOOP, OptType.bool_, OptType.int_ ), OptInfo( IP_MULTICAST_TTL, OptType.int_, OptType.int_ ), + OptInfo( IP_PKTINFO, OptType.bool_, OptType.int_ ), + OptInfo( IPV6_RECVPKTINFO, OptType.bool_, OptType.int_ ), OptInfo( TCP_KEEPIDLE, OptType.duration, OptType.seconds ), OptInfo( TCP_KEEPINTVL, OptType.duration, OptType.seconds ), OptInfo( TCP_KEEPCNT, OptType.int_, OptType.int_ ), @@ -1321,6 +1407,8 @@ else version (Darwin) OptInfo( IP_ADD_MEMBERSHIP, OptType.multicast_group, OptType.multicast_group ), OptInfo( IP_MULTICAST_LOOP, OptType.bool_, OptType.int_ ), OptInfo( IP_MULTICAST_TTL, OptType.int_, OptType.int_ ), + OptInfo( IP_PKTINFO, OptType.bool_, OptType.int_ ), + OptInfo( IPV6_RECVPKTINFO, OptType.bool_, OptType.int_ ), OptInfo( -1, OptType.unsupported, OptType.unsupported ), OptInfo( -1, OptType.unsupported, OptType.unsupported ), OptInfo( -1, OptType.unsupported, OptType.unsupported ), @@ -1379,6 +1467,28 @@ version (Windows) WSADATA wsaData; int result = WSAStartup(MAKEWORD(2, 2), &wsaData); // what if this fails??? + + // this is truly the worst thing I ever wrote!! + enum SIO_GET_EXTENSION_FUNCTION_POINTER = 0xC8000006; + struct GUID { uint Data1; ushort Data2, Data3; ubyte[8] Data4; } + __gshared immutable GUID WSAID_WSARECVMSG = GUID(0xF689D7C8, 0x6F1F, 0x436B, [0x8A,0x53,0xE5,0x4F,0xE3,0x51,0xC3,0x22]); + + Socket dummy; + uint bytes = 0; + if (!create_socket(AddressFamily.IPv4, SocketType.datagram, Protocol.udp, dummy)) + goto FAIL; + if (WSAIoctl(dummy.handle, SIO_GET_EXTENSION_FUNCTION_POINTER, cast(void*)&WSAID_WSARECVMSG, cast(uint)GUID.sizeof, + &WSARecvMsg, cast(uint)WSARecvMsgFn.sizeof, &bytes, null, null) != 0) + goto FAIL; + assert(bytes == WSARecvMsgFn.sizeof); + dummy.close(); + if (!WSARecvMsg) + goto FAIL; + return; + + FAIL: + import urt.log; + writeWarning("Failed to get WSARecvMsg function pointer - recvfrom() won't be able to report the dst address"); } pragma(crt_destructor) @@ -1397,43 +1507,78 @@ version (Windows) { // stuff that's missing from the windows headers... - enum: int { + enum : int + { AI_NUMERICSERV = 0x0008, AI_ALL = 0x0100, AI_V4MAPPED = 0x0800, AI_FQDN = 0x4000, } - struct pollfd + struct ip_mreq { - SOCKET fd; // Socket handle - SHORT events; // Requested events to monitor - SHORT revents; // Returned events indicating status + in_addr imr_multiaddr; + in_addr imr_interface; } - alias WSAPOLLFD = pollfd; - alias PWSAPOLLFD = pollfd*; - alias LPWSAPOLLFD = pollfd*; - - enum: short { - POLLRDNORM = 0x0100, - POLLRDBAND = 0x0200, - POLLIN = (POLLRDNORM | POLLRDBAND), - POLLPRI = 0x0400, - - POLLWRNORM = 0x0010, - POLLOUT = (POLLWRNORM), - POLLWRBAND = 0x0020, - - POLLERR = 0x0001, - POLLHUP = 0x0002, - POLLNVAL = 0x0004 + + struct WSAMSG + { + LPSOCKADDR name; + int namelen; + LPWSABUF lpBuffers; + uint dwBufferCount; + WSABUF Control; + uint dwFlags; } + alias LPWSAMSG = WSAMSG*; - extern(Windows) int WSAPoll(LPWSAPOLLFD fdArray, uint fds, int timeout); + struct WSABUF + { + uint len; + char* buf; + } + alias LPWSABUF = WSABUF*; - struct ip_mreq + alias WSARecvMsgFn = extern(Windows) int function(SOCKET s, LPWSAMSG lpMsg, uint* lpdwNumberOfBytesRecvd, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); + __gshared WSARecvMsgFn WSARecvMsg; + + struct IN_PKTINFO { - in_addr imr_multiaddr; - in_addr imr_interface; + in_addr ipi_addr; + uint ipi_ifindex; + } + struct IN6_PKTINFO + { + in6_addr ipi6_addr; + uint ipi6_ifindex; + } + + struct WSACMSGHDR + { + size_t cmsg_len; + int cmsg_level; + int cmsg_type; } + alias LPWSACMSGHDR = WSACMSGHDR*; + + LPWSACMSGHDR WSA_CMSG_FIRSTHDR(LPWSAMSG msg) + => msg.Control.len >= WSACMSGHDR.sizeof ? cast(LPWSACMSGHDR)msg.Control.buf : null; + + LPWSACMSGHDR WSA_CMSG_NXTHDR(LPWSAMSG msg, LPWSACMSGHDR cmsg) + { + if (!cmsg) + return WSA_CMSG_FIRSTHDR(msg); + if (cast(ubyte*)cmsg + WSA_CMSGHDR_ALIGN(cmsg.cmsg_len) + WSACMSGHDR.sizeof > cast(ubyte*)msg.Control.buf + msg.Control.len) + return null; + return cast(LPWSACMSGHDR)(cast(ubyte*)cmsg + WSA_CMSGHDR_ALIGN(cmsg.cmsg_len)); + } + + void* WSA_CMSG_DATA(LPWSACMSGHDR cmsg) + => cast(ubyte*)cmsg + WSA_CMSGDATA_ALIGN(WSACMSGHDR.sizeof); + + size_t WSA_CMSGHDR_ALIGN(size_t length) + => (length + WSACMSGHDR.alignof-1) & ~(WSACMSGHDR.alignof-1); + + size_t WSA_CMSGDATA_ALIGN(size_t length) + => (length + size_t.alignof-1) & ~(size_t.alignof-1); } From bf85e900f6de66d072e340bcce29b75784758439 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 19 Oct 2025 14:23:38 +1000 Subject: [PATCH 023/138] Renamed IPSubnet to IPNetworkAddress (because it stores a full address!) Also snake_case fixes, and some other minor tweaks. --- src/urt/inet.d | 242 ++++++++++++++++++++++++----------------------- src/urt/socket.d | 38 ++++---- 2 files changed, 143 insertions(+), 137 deletions(-) diff --git a/src/urt/inet.d b/src/urt/inet.d index e4c6d1a..b2d37bb 100644 --- a/src/urt/inet.d +++ b/src/urt/inet.d @@ -11,11 +11,11 @@ nothrow @nogc: enum AddressFamily : byte { - Unknown = -1, - Unspecified = 0, - Unix, - IPv4, - IPv6, + unknown = -1, + unspecified = 0, + unix, + ipv4, + ipv6, } enum WellKnownPort : ushort @@ -34,8 +34,8 @@ enum WellKnownPort : ushort MDNS = 5353, } -enum IPAddr IPAddrLit(string addr) = () { IPAddr a; size_t taken = a.fromString(addr); assert(taken == addr.length, "Not an IPv4 address"); return a; }(); -enum IPv6Addr IPv6AddrLit(string addr) = () { IPv6Addr a; size_t taken = a.fromString(addr); assert(taken == addr.length, "Not an IPv6 address"); return a; }(); +enum IPAddr IPAddrLit(string addr) = () { IPAddr a; size_t taken = a.fromString(addr); assert(taken == addr.length, "Not an ipv4 address"); return a; }(); +enum IPv6Addr IPv6AddrLit(string addr) = () { IPv6Addr a; size_t taken = a.fromString(addr); assert(taken == addr.length, "Not an ipv6 address"); return a; }(); struct IPAddr { @@ -56,13 +56,13 @@ nothrow @nogc: this.b = b; } - bool isMulticast() const pure + bool is_multicast() const pure => (b[0] & 0xF0) == 224; - bool isLoopback() const pure + bool is_loopback() const pure => b[0] == 127; - bool isLinkLocal() const pure + bool is_link_local() const pure => (b[0] == 169 && b[1] == 254); - bool isPrivate() const pure + bool is_private() const pure => (b[0] == 192 && b[1] == 168) || b[0] == 10 || (b[0] == 172 && (b[1] & 0xF) == 16); bool opCast(T : bool)() const pure @@ -114,10 +114,10 @@ nothrow @nogc: return fnv1a(b[]); } - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure + ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] format_args) const pure { - char[15] stackBuffer = void; - char[] tmp = buffer.length < stackBuffer.sizeof ? stackBuffer : buffer; + char[15] stack_buffer = void; + char[] tmp = buffer.length < stack_buffer.sizeof ? stack_buffer : buffer; size_t offset = 0; for (int i = 0; i < 4; i++) { @@ -126,7 +126,7 @@ nothrow @nogc: offset += b[i].format_int(tmp[offset..$]); } - if (buffer.ptr && tmp.ptr == stackBuffer.ptr) + if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { if (buffer.length < offset) return -1; @@ -190,13 +190,13 @@ nothrow @nogc: this.s = s; } - bool isGlobal() const pure + bool is_global() const pure => (s[0] & 0xE000) == 0x2000; - bool isLinkLocal() const pure + bool is_link_local() const pure => (s[0] & 0xFFC0) == 0xFE80; - bool isMulticast() const pure + bool is_multicast() const pure => (s[0] & 0xFF00) == 0xFF00; - bool isUniqueLocal() const pure + bool is_unique_local() const pure => (s[0] & 0xFE00) == 0xFC00; bool opCast(T : bool)() const pure @@ -228,7 +228,7 @@ nothrow @nogc: return r; } - IPv6Addr opBinary(string op)(const IPv6Addr rhs) pure + IPv6Addr opBinary(string op)(const IPv6Addr rhs) const pure if (op == "&" || op == "|" || op == "^") { IPv6Addr t; @@ -253,12 +253,12 @@ nothrow @nogc: return fnv1a(cast(ubyte[])s[]); } - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure + ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] format_args) const pure { import urt.string.ascii; // find consecutive zeroes... - int skipFrom = 0; + int skip_from = 0; int[8] z; for (int i = 0; i < 8; i++) { @@ -269,15 +269,15 @@ nothrow @nogc: if (z[j] != 0) { ++z[j]; - if (z[j] > z[skipFrom]) - skipFrom = j; + if (z[j] > z[skip_from]) + skip_from = j; } else break; } z[i] = 1; - if (z[i] > z[skipFrom]) - skipFrom = i; + if (z[i] > z[skip_from]) + skip_from = i; } } @@ -288,11 +288,11 @@ nothrow @nogc: { if (i > 0) tmp[offset++] = ':'; - if (z[skipFrom] > 1 && i == skipFrom) + if (z[skip_from] > 1 && i == skip_from) { if (i == 0) tmp[offset++] = ':'; - i += z[skipFrom]; + i += z[skip_from]; if (i == 8) tmp[offset++] = ':'; continue; @@ -365,25 +365,25 @@ nothrow @nogc: auto __debugExpanded() => s[]; } -struct IPSubnet +struct IPNetworkAddress { nothrow @nogc: - enum multicast = IPSubnet(IPAddr(224, 0, 0, 0), 4); - enum loopback = IPSubnet(IPAddr(127, 0, 0, 0), 8); - enum linkLocal = IPSubnet(IPAddr(169, 254, 0, 0), 16); - enum privateA = IPSubnet(IPAddr(10, 0, 0, 0), 8); - enum privateB = IPSubnet(IPAddr(172, 16, 0, 0), 12); - enum privateC = IPSubnet(IPAddr(192, 168, 0, 0), 16); + enum multicast = IPNetworkAddress(IPAddr(224, 0, 0, 0), 4); + enum loopback = IPNetworkAddress(IPAddr(127, 0, 0, 0), 8); + enum linklocal = IPNetworkAddress(IPAddr(169, 254, 0, 0), 16); + enum private_a = IPNetworkAddress(IPAddr(10, 0, 0, 0), 8); + enum private_b = IPNetworkAddress(IPAddr(172, 16, 0, 0), 12); + enum private_c = IPNetworkAddress(IPAddr(192, 168, 0, 0), 16); // TODO: ya know, this is gonna align to 4-bytes anyway... // we could store the actual mask in the native endian, and then clz to recover the prefix len in one opcode IPAddr addr; IPAddr mask; - ubyte prefixLen() @property const pure + ubyte prefix_len() @property const pure => clz(~loadBigEndian(&mask.address)); - void prefixLen(ubyte len) @property pure + void prefix_len(ubyte len) @property pure { if (len == 0) mask.address = 0; @@ -391,36 +391,36 @@ nothrow @nogc: storeBigEndian(&mask.address, 0xFFFFFFFF << (32 - len)); } - this(IPAddr addr, ubyte prefixLen) + this(IPAddr addr, ubyte prefix_len) { this.addr = addr; - this.prefixLen = prefixLen; + this.prefix_len = prefix_len; } - IPAddr netMask() const pure + IPAddr net_mask() const pure => mask; bool contains(IPAddr ip) const pure - => (ip & netMask()) == addr; + => (ip & mask) == get_network(); - IPAddr getNetwork(IPAddr ip) const pure - => ip & mask; - IPAddr getLocal(IPAddr ip) const pure - => ip & ~mask; + IPAddr get_network() const pure + => addr & mask; + IPAddr get_local() const pure + => addr & ~mask; size_t toHash() const pure - => addr.toHash() ^ prefixLen; + => addr.toHash() ^ prefix_len; - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure + ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] format_args) const pure { - char[18] stackBuffer = void; - char[] tmp = buffer.length < stackBuffer.sizeof ? stackBuffer : buffer; + char[18] stack_buffer = void; + char[] tmp = buffer.length < stack_buffer.sizeof ? stack_buffer : buffer; size_t offset = addr.toString(tmp, null, null); tmp[offset++] = '/'; - offset += prefixLen.format_int(tmp[offset..$]); + offset += prefix_len.format_int(tmp[offset..$]); - if (buffer.ptr && tmp.ptr == stackBuffer.ptr) + if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { if (buffer.length < offset) return -1; @@ -440,7 +440,7 @@ nothrow @nogc: if (t == 0 || plen > 32) return -1; addr = a; - prefixLen = cast(ubyte)plen; + prefix_len = cast(ubyte)plen; return taken + t; } @@ -453,32 +453,32 @@ nothrow @nogc: } } -struct IPv6Subnet +struct IPv6NetworkAddress { nothrow @nogc: - enum global = IPv6Subnet(IPv6Addr(0x2000, 0, 0, 0, 0, 0, 0, 0), 3); - enum linkLocal = IPv6Subnet(IPv6Addr(0xFE80, 0, 0, 0, 0, 0, 0, 0), 10); - enum multicast = IPv6Subnet(IPv6Addr(0xFF00, 0, 0, 0, 0, 0, 0, 0), 8); - enum uniqueLocal = IPv6Subnet(IPv6Addr(0xFC00, 0, 0, 0, 0, 0, 0, 0), 7); + enum global = IPv6NetworkAddress(IPv6Addr(0x2000, 0, 0, 0, 0, 0, 0, 0), 3); + enum linklocal = IPv6NetworkAddress(IPv6Addr(0xFE80, 0, 0, 0, 0, 0, 0, 0), 10); + enum multicast = IPv6NetworkAddress(IPv6Addr(0xFF00, 0, 0, 0, 0, 0, 0, 0), 8); + enum uniquelocal = IPv6NetworkAddress(IPv6Addr(0xFC00, 0, 0, 0, 0, 0, 0, 0), 7); IPv6Addr addr; - ubyte prefixLen; + ubyte prefix_len; - this(IPv6Addr addr, ubyte prefixLen) + this(IPv6Addr addr, ubyte prefix_len) { this.addr = addr; - this.prefixLen = prefixLen; + this.prefix_len = prefix_len; } - IPv6Addr netMask() const pure + IPv6Addr net_mask() const pure { IPv6Addr r; - int i, j = prefixLen / 16; + int i, j = prefix_len / 16; while (i < j) r.s[i++] = 0xFFFF; if (j < 8) { - r.s[i++] = cast(ushort)(0xFFFF << (16 - (prefixLen % 16))); + r.s[i++] = cast(ushort)(0xFFFF << (16 - (prefix_len % 16))); while (i < 8) r.s[i++] = 0; } return r; @@ -486,38 +486,38 @@ nothrow @nogc: bool contains(IPv6Addr ip) const pure { - uint n = prefixLen / 16; + uint n = prefix_len / 16; uint i = 0; for (; i < n; ++i) if (ip.s[i] != addr.s[i]) return false; - if (prefixLen % 16) + if (prefix_len % 16) { - uint s = 16 - (prefixLen % 16); + uint s = 16 - (prefix_len % 16); if (ip.s[i] >> s != addr.s[i] >> s) return false; } return true; } - IPv6Addr getNetwork(IPv6Addr ip) const pure - => ip & netMask(); - IPv6Addr getLocal(IPv6Addr ip) const pure - => ip & ~netMask(); + IPv6Addr get_network() const pure + => addr & net_mask(); + IPv6Addr get_local() const pure + => addr & ~net_mask(); size_t toHash() const pure - => addr.toHash() ^ prefixLen; + => addr.toHash() ^ prefix_len; - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure + ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] format_args) const pure { - char[42] stackBuffer = void; - char[] tmp = buffer.length < stackBuffer.sizeof ? stackBuffer : buffer; + char[42] stack_buffer = void; + char[] tmp = buffer.length < stack_buffer.sizeof ? stack_buffer : buffer; size_t offset = addr.toString(tmp, null, null); tmp[offset++] = '/'; - offset += prefixLen.format_int(tmp[offset..$]); + offset += prefix_len.format_int(tmp[offset..$]); - if (buffer.ptr && tmp.ptr == stackBuffer.ptr) + if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { if (buffer.length < offset) return -1; @@ -537,7 +537,7 @@ nothrow @nogc: if (t == 0 || plen > 128) return -1; addr = a; - prefixLen = cast(ubyte)plen; + prefix_len = cast(ubyte)plen; return taken + t; } @@ -569,7 +569,7 @@ nothrow @nogc: { IPv6Addr addr; ushort port; - uint flowInfo; + uint flow_info; uint scopeId; } struct Addr @@ -583,20 +583,26 @@ nothrow @nogc: this(IPAddr addr, ushort port) { - family = AddressFamily.IPv4; + family = AddressFamily.ipv4; this._a.ipv4.addr = addr; this._a.ipv4.port = port; } - this(IPv6Addr addr, ushort port, int flowInfo = 0, uint scopeId = 0) + this(IPv6Addr addr, ushort port, int flow_info = 0, uint scopeId = 0) { - family = AddressFamily.IPv6; + family = AddressFamily.ipv6; this._a.ipv6.addr = addr; this._a.ipv6.port = port; - this._a.ipv6.flowInfo = flowInfo; + this._a.ipv6.flow_info = flow_info; this._a.ipv6.scopeId = scopeId; } + inout(IPv4)* as_ipv4() inout pure + => family == AddressFamily.ipv4 ? &_a.ipv4 : null; + + inout(IPv6)* as_ipv6() inout pure + => family == AddressFamily.ipv6 ? &_a.ipv6 : null; + bool opCast(T : bool)() const pure => family > AddressFamily.Unspecified; @@ -606,9 +612,9 @@ nothrow @nogc: return false; switch (family) { - case AddressFamily.IPv4: + case AddressFamily.ipv4: return _a.ipv4 == rhs._a.ipv4; - case AddressFamily.IPv6: + case AddressFamily.ipv6: return _a.ipv6 == rhs._a.ipv6; default: return true; @@ -621,18 +627,18 @@ nothrow @nogc: return family < rhs.family ? -1 : 1; switch (family) { - case AddressFamily.IPv4: + case AddressFamily.ipv4: int c = _a.ipv4.addr.opCmp(rhs._a.ipv4.addr); return c != 0 ? c : _a.ipv4.port - rhs._a.ipv4.port; - case AddressFamily.IPv6: + case AddressFamily.ipv6: int c = _a.ipv6.addr.opCmp(rhs._a.ipv6.addr); if (c != 0) return c; if (_a.ipv6.port == rhs._a.ipv6.port) { - if (_a.ipv6.flowInfo == rhs._a.ipv6.flowInfo) + if (_a.ipv6.flow_info == rhs._a.ipv6.flow_info) return _a.ipv6.scopeId - rhs._a.ipv6.scopeId; - return _a.ipv6.flowInfo - rhs._a.ipv6.flowInfo; + return _a.ipv6.flow_info - rhs._a.ipv6.flow_info; } return _a.ipv6.port - rhs._a.ipv6.port; default: @@ -643,19 +649,19 @@ nothrow @nogc: size_t toHash() const pure { - if (family == AddressFamily.IPv4) + if (family == AddressFamily.ipv4) return _a.ipv4.addr.toHash() ^ _a.ipv4.port; else return _a.ipv6.addr.toHash() ^ _a.ipv6.port; } - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure + ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] format_args) const pure { - char[47] stackBuffer = void; - char[] tmp = buffer.length < stackBuffer.sizeof ? stackBuffer : buffer; + char[47] stack_buffer = void; + char[] tmp = buffer.length < stack_buffer.sizeof ? stack_buffer : buffer; size_t offset = void; - if (family == AddressFamily.IPv4) + if (family == AddressFamily.ipv4) { offset = _a.ipv4.addr.toString(tmp, null, null); tmp[offset++] = ':'; @@ -670,7 +676,7 @@ nothrow @nogc: offset += _a.ipv6.port.format_int(tmp[offset..$]); } - if (buffer.ptr && tmp.ptr == stackBuffer.ptr) + if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { if (buffer.length < offset) return -1; @@ -689,10 +695,10 @@ nothrow @nogc: // take address if (s.length >= 4 && (s[1] == '.' || s[2] == '.' || s[3] == '.')) - af = AddressFamily.IPv4; + af = AddressFamily.ipv4; else - af = AddressFamily.IPv6; - if (af == AddressFamily.IPv4) + af = AddressFamily.ipv6; + if (af == AddressFamily.ipv4) { taken = a4.fromString(s); if (taken < 0) @@ -723,7 +729,7 @@ nothrow @nogc: // success! store results.. family = af; - if (af == AddressFamily.IPv4) + if (af == AddressFamily.ipv4) { _a.ipv4.addr = a4; _a.ipv4.port = port; @@ -732,7 +738,7 @@ nothrow @nogc: { _a.ipv6.addr = a6; _a.ipv6.port = port; - _a.ipv6.flowInfo = 0; + _a.ipv6.flow_info = 0; _a.ipv6.scopeId = 0; } return taken; @@ -756,9 +762,9 @@ unittest assert((IPAddr(255, 255, 248, 0) & IPAddr(255, 0, 255, 255)) == IPAddr(255, 0, 248, 0)); assert((IPAddr(255, 255, 248, 0) | IPAddr(255, 0, 255, 255)) == IPAddr(255, 255, 255, 255)); assert((IPAddr(255, 255, 248, 0) ^ IPAddr(255, 0, 255, 255)) == IPAddr(0, 255, 7, 255)); - assert(IPSubnet(IPAddr(), 21).netMask() == IPAddr(0xFF, 0xFF, 0xF8, 0)); - assert(IPSubnet(IPAddr(192, 168, 0, 0), 24).getNetwork(IPAddr(192, 168, 0, 10)) == IPAddr(192, 168, 0, 0)); - assert(IPSubnet(IPAddr(192, 168, 0, 0), 24).getLocal(IPAddr(192, 168, 0, 10)) == IPAddr(0, 0, 0, 10)); + assert(IPNetworkAddress(IPAddr(), 21).net_mask() == IPAddr(0xFF, 0xFF, 0xF8, 0)); + assert(IPNetworkAddress(IPAddr(192, 168, 0, 10), 24).get_network() == IPAddr(192, 168, 0, 0)); + assert(IPNetworkAddress(IPAddr(192, 168, 0, 10), 24).get_local() == IPAddr(0, 0, 0, 10)); assert(tmp[0 .. IPAddr(192, 168, 0, 1).toString(tmp, null, null)] == "192.168.0.1"); assert(tmp[0 .. IPAddr(0, 0, 0, 0).toString(tmp, null, null)] == "0.0.0.0"); @@ -769,12 +775,12 @@ unittest addr |= IPAddr(1, 2, 3, 4); assert(addr == IPAddr(1, 2, 3, 4)); - assert(tmp[0 .. IPSubnet(IPAddr(192, 168, 0, 0), 24).toString(tmp, null, null)] == "192.168.0.0/24"); - assert(tmp[0 .. IPSubnet(IPAddr(0, 0, 0, 0), 0).toString(tmp, null, null)] == "0.0.0.0/0"); + assert(tmp[0 .. IPNetworkAddress(IPAddr(192, 168, 0, 0), 24).toString(tmp, null, null)] == "192.168.0.0/24"); + assert(tmp[0 .. IPNetworkAddress(IPAddr(0, 0, 0, 0), 0).toString(tmp, null, null)] == "0.0.0.0/0"); - IPSubnet subnet; - assert(subnet.fromString("192.168.0.0/24") == 14 && subnet == IPSubnet(IPAddr(192, 168, 0, 0), 24)); - assert(subnet.fromString("0.0.0.0/0") == 9 && subnet == IPSubnet(IPAddr(0, 0, 0, 0), 0)); + IPNetworkAddress subnet; + assert(subnet.fromString("192.168.0.0/24") == 14 && subnet == IPNetworkAddress(IPAddr(192, 168, 0, 0), 24)); + assert(subnet.fromString("0.0.0.0/0") == 9 && subnet == IPNetworkAddress(IPAddr(0, 0, 0, 0), 0)); assert(subnet.fromString("1.2.3.4") == -1); assert(subnet.fromString("1.2.3.4/33") == -1); assert(subnet.fromString("1.2.3.4/a") == -1); @@ -783,10 +789,10 @@ unittest assert((IPv6Addr(0xFFFF, 0, 1, 2, 3, 4, 5, 6) & IPv6Addr(0xFF00, 0, 3, 0, 0, 0, 0, 2)) == IPv6Addr(0xFF00, 0, 1, 0, 0, 0, 0, 2)); assert((IPv6Addr(0xFFFF, 0, 1, 2, 3, 4, 5, 6) | IPv6Addr(0xFF00, 0, 3, 0, 0, 0, 0, 2)) == IPv6Addr(0xFFFF, 0, 3, 2, 3, 4, 5, 6)); assert((IPv6Addr(0xFFFF, 0, 1, 2, 3, 4, 5, 6) ^ IPv6Addr(0xFF00, 0, 3, 0, 0, 0, 0, 2)) == IPv6Addr(0xFF, 0, 2, 2, 3, 4, 5, 4)); - assert(IPv6Subnet(IPv6Addr(), 21).netMask() == IPv6Addr(0xFFFF, 0xF800, 0, 0, 0, 0, 0, 0)); - assert(IPv6Subnet(IPv6Addr.any, 64).getNetwork(IPv6Addr.loopback) == IPv6Addr.any); - assert(IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0), 32).getNetwork(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1)) == IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)); - assert(IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0), 32).getLocal(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1)) == IPv6Addr(0, 0, 0, 1, 0, 0, 0, 1)); + assert(IPv6NetworkAddress(IPv6Addr(), 21).net_mask() == IPv6Addr(0xFFFF, 0xF800, 0, 0, 0, 0, 0, 0)); + assert(IPv6NetworkAddress(IPv6Addr.loopback, 64).get_network() == IPv6Addr.any); + assert(IPv6NetworkAddress(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1), 32).get_network() == IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)); + assert(IPv6NetworkAddress(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1), 32).get_local() == IPv6Addr(0, 0, 0, 1, 0, 0, 0, 1)); assert(tmp[0 .. IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1).toString(tmp, null, null)] == "2001:db8:0:1::1"); assert(tmp[0 .. IPv6Addr(0x2001, 0xdb8, 0, 0, 1, 0, 0, 1).toString(tmp, null, null)] == "2001:db8::1:0:0:1"); @@ -819,12 +825,12 @@ unittest assert(addr6.fromString("2001:db8::1/24") == 11 && addr6 == IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); - assert(tmp[0 .. IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 24).toString(tmp, null, null)] == "2001:db8::1/24"); - assert(tmp[0 .. IPv6Subnet(IPv6Addr(), 0).toString(tmp, null, null)] == "::/0"); + assert(tmp[0 .. IPv6NetworkAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 24).toString(tmp, null, null)] == "2001:db8::1/24"); + assert(tmp[0 .. IPv6NetworkAddress(IPv6Addr(), 0).toString(tmp, null, null)] == "::/0"); - IPv6Subnet subnet6; - assert(subnet6.fromString("2001:db8::1/24") == 14 && subnet6 == IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 24)); - assert(subnet6.fromString("::/0") == 4 && subnet6 == IPv6Subnet(IPv6Addr(), 0)); + IPv6NetworkAddress subnet6; + assert(subnet6.fromString("2001:db8::1/24") == 14 && subnet6 == IPv6NetworkAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 24)); + assert(subnet6.fromString("::/0") == 4 && subnet6 == IPv6NetworkAddress(IPv6Addr(), 0)); assert(subnet6.fromString("1::2") == -1); assert(subnet6.fromString("1::2/129") == -1); assert(subnet6.fromString("1::2/a") == -1); @@ -895,13 +901,13 @@ unittest // InetAddress sorting tests { InetAddress[10] expected = [ - // IPv4 sorted first + // ipv4 sorted first InetAddress(IPAddr(10, 0, 0, 1), 80), InetAddress(IPAddr(127, 0, 0, 1), 8080), InetAddress(IPAddr(192, 168, 1, 1), 80), InetAddress(IPAddr(192, 168, 1, 1), 443), - // IPv6 sorted next + // ipv6 sorted next InetAddress(IPv6Addr(1, 0, 0, 0, 0, 0, 0, 0), 1024), InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 80, 0, 0), InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 433, 1, 1), diff --git a/src/urt/socket.d b/src/urt/socket.d index 52bf820..7902c74 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -194,7 +194,7 @@ private: Result create_socket(AddressFamily af, SocketType type, Protocol proto, out Socket socket) { version (HasUnixSocket) {} else - assert(af != AddressFamily.Unix, "Unix sockets not supported on this platform!"); + assert(af != AddressFamily.unix, "Unix sockets not supported on this platform!"); socket.handle = .socket(s_addressFamily[af], s_socketType[type], s_protocol[proto]); if (socket == Socket.invalid) @@ -500,7 +500,7 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval // determine the option 'level' OptLevel level = get_optlevel(option); version (HasIPv6) {} else - assert(level != OptLevel.IPv6 && level != OptLevel.ICMPv6, "Platform does not support IPv6!"); + assert(level != OptLevel.ipv6 && level != OptLevel.ICMPv6, "Platform does not support IPv6!"); // platforms don't all agree on option data formats! const(void)* arg = optval; @@ -1052,7 +1052,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ switch (address.family) { - case AddressFamily.IPv4: + case AddressFamily.ipv4: { addrLen = sockaddr_in.sizeof; if (buffer.length < sockaddr_in.sizeof) @@ -1060,7 +1060,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ sockaddr_in* ain = cast(sockaddr_in*)sock_addr; memzero(ain, sockaddr_in.sizeof); - ain.sin_family = s_addressFamily[AddressFamily.IPv4]; + ain.sin_family = s_addressFamily[AddressFamily.ipv4]; version (Windows) { ain.sin_addr.S_un.S_un_b.s_b1 = address._a.ipv4.addr.b[0]; @@ -1075,7 +1075,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ storeBigEndian(&ain.sin_port, ushort(address._a.ipv4.port)); break; } - case AddressFamily.IPv6: + case AddressFamily.ipv6: { version (HasIPv6) { @@ -1085,9 +1085,9 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ sockaddr_in6* ain6 = cast(sockaddr_in6*)sock_addr; memzero(ain6, sockaddr_in6.sizeof); - ain6.sin6_family = s_addressFamily[AddressFamily.IPv6]; + ain6.sin6_family = s_addressFamily[AddressFamily.ipv6]; storeBigEndian(&ain6.sin6_port, cast(ushort)address._a.ipv6.port); - storeBigEndian(cast(uint*)&ain6.sin6_flowinfo, address._a.ipv6.flowInfo); + storeBigEndian(cast(uint*)&ain6.sin6_flowinfo, address._a.ipv6.flow_info); storeBigEndian(cast(uint*)&ain6.sin6_scope_id, address._a.ipv6.scopeId); for (int a = 0; a < 8; ++a) { @@ -1103,7 +1103,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ assert(false, "Platform does not support IPv6!"); break; } - case AddressFamily.Unix: + case AddressFamily.unix: { // version (HasUnixSocket) // { @@ -1113,7 +1113,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ // // sockaddr_un* aun = cast(sockaddr_un*)sock_addr; // memzero(aun, sockaddr_un.sizeof); -// aun.sun_family = s_addressFamily[AddressFamily.Unix]; +// aun.sun_family = s_addressFamily[AddressFamily.unix]; // // memcpy(aun.sun_path, address.un.path, UNIX_PATH_LEN); // break; @@ -1140,7 +1140,7 @@ InetAddress make_InetAddress(const(sockaddr)* sock_address) addr.family = map_address_family(sock_address.sa_family); switch (addr.family) { - case AddressFamily.IPv4: + case AddressFamily.ipv4: { const sockaddr_in* ain = cast(const(sockaddr_in)*)sock_address; @@ -1148,14 +1148,14 @@ InetAddress make_InetAddress(const(sockaddr)* sock_address) addr._a.ipv4.addr = make_IPAddr(ain.sin_addr); break; } - case AddressFamily.IPv6: + case AddressFamily.ipv6: { version (HasIPv6) { const sockaddr_in6* ain6 = cast(const(sockaddr_in6)*)sock_address; addr._a.ipv6.port = loadBigEndian(&ain6.sin6_port); - addr._a.ipv6.flowInfo = loadBigEndian(cast(const(uint)*)&ain6.sin6_flowinfo); + addr._a.ipv6.flow_info = loadBigEndian(cast(const(uint)*)&ain6.sin6_flowinfo); addr._a.ipv6.scopeId = loadBigEndian(cast(const(uint)*)&ain6.sin6_scope_id); addr._a.ipv6.addr = make_IPv6Addr(ain6.sin6_addr); } @@ -1163,7 +1163,7 @@ InetAddress make_InetAddress(const(sockaddr)* sock_address) assert(false, "Platform does not support IPv6!"); break; } - case AddressFamily.Unix: + case AddressFamily.unix: { // version (HasUnixSocket) // { @@ -1267,15 +1267,15 @@ __gshared immutable ushort[AddressFamily.max+1] s_addressFamily = [ AddressFamily map_address_family(int addressFamily) { if (addressFamily == AF_INET) - return AddressFamily.IPv4; + return AddressFamily.ipv4; else if (addressFamily == AF_INET6) - return AddressFamily.IPv6; + return AddressFamily.ipv6; else if (addressFamily == AF_UNIX) - return AddressFamily.Unix; + return AddressFamily.unix; else if (addressFamily == AF_UNSPEC) - return AddressFamily.Unspecified; + return AddressFamily.unspecified; assert(false, "Unsupported address family"); - return AddressFamily.Unknown; + return AddressFamily.unknown; } __gshared immutable int[SocketType.max+1] s_socketType = [ @@ -1475,7 +1475,7 @@ version (Windows) Socket dummy; uint bytes = 0; - if (!create_socket(AddressFamily.IPv4, SocketType.datagram, Protocol.udp, dummy)) + if (!create_socket(AddressFamily.ipv4, SocketType.datagram, Protocol.udp, dummy)) goto FAIL; if (WSAIoctl(dummy.handle, SIO_GET_EXTENSION_FUNCTION_POINTER, cast(void*)&WSAID_WSARECVMSG, cast(uint)GUID.sizeof, &WSARecvMsg, cast(uint)WSARecvMsgFn.sizeof, &bytes, null, null) != 0) From e345a97b992e6335c27c212c3b8193b2869b8b72 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 30 Oct 2025 11:09:39 +1000 Subject: [PATCH 024/138] Fix a really dumb bug. --- src/urt/system.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/urt/system.d b/src/urt/system.d index 2bb58ec..903ce2a 100644 --- a/src/urt/system.d +++ b/src/urt/system.d @@ -99,7 +99,7 @@ void set_system_idle_params(IdleParams params) enum EXECUTION_STATE ES_DISPLAY_REQUIRED = 0x00000002; enum EXECUTION_STATE ES_CONTINUOUS = 0x80000000; - SetThreadExecutionState(ES_CONTINUOUS | (params.SystemRequired ? ES_SYSTEM_REQUIRED : 0) | (params.DisplayRequired ? ES_DISPLAY_REQUIRED : 0)); + SetThreadExecutionState(ES_CONTINUOUS | ((params & IdleParams.SystemRequired) ? ES_SYSTEM_REQUIRED : 0) | ((params & IdleParams.DisplayRequired) ? ES_DISPLAY_REQUIRED : 0)); } else version (Posix) { From 970bcaaef41c559d9b54a1f6ee459db76dc5c62a Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 3 Nov 2025 21:52:37 +1000 Subject: [PATCH 025/138] Added ascii strcmp/icmp --- src/urt/string/ascii.d | 44 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/urt/string/ascii.d b/src/urt/string/ascii.d index 9edab53..36f814f 100644 --- a/src/urt/string/ascii.d +++ b/src/urt/string/ascii.d @@ -67,16 +67,20 @@ char to_upper(char c) pure char[] to_lower(const(char)[] str, char[] buffer) pure { + if (buffer.length < str.length) + return null; foreach (i; 0 .. str.length) - buffer[i] = to_lower(str[i]); - return buffer; + buffer[i] = str[i].to_lower; + return buffer[0..str.length]; } char[] to_upper(const(char)[] str, char[] buffer) pure { + if (buffer.length < str.length) + return null; foreach (i; 0 .. str.length) - buffer[i] = to_upper(str[i]); - return buffer; + buffer[i] = str[i].to_upper; + return buffer[0..str.length]; } char[] to_lower(char[] str) pure @@ -88,3 +92,35 @@ char[] to_upper(char[] str) pure { return to_upper(str, str); } + +ptrdiff_t cmp(bool case_insensitive = false)(const(char)[] a, const(char)[] b) pure +{ + if (a.length != b.length) + return a.length - b.length; + static if (case_insensitive) + { + for (size_t i = 0; i < a.length; ++i) + { + char ca = a[i].to_lower; + char cb = b[i].to_lower; + if (ca != cb) + return ca - cb; + } + } + else + { + for (size_t i = 0; i < a.length; ++i) + if (a[i] != b[i]) + return a[i] - b[i]; + } + return 0; +} + +ptrdiff_t icmp(const(char)[] a, const(char)[] b) pure + => cmp!true(a, b); + +bool eq(const(char)[] a, const(char)[] b) pure + => cmp(a, b) == 0; + +bool ieq(const(char)[] a, const(char)[] b) pure + => cmp!true(a, b) == 0; From 5c0fb18a01229ffcc261205548d3a093d15bfb15 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 3 Nov 2025 22:00:13 +1000 Subject: [PATCH 026/138] Added toString/fromString for common time types. Improved precision for stringification down to nanoseconds. Added unit tests. --- src/urt/time.d | 377 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 344 insertions(+), 33 deletions(-) diff --git a/src/urt/time.d b/src/urt/time.d index d1d0e00..b8dd4be 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -107,17 +107,33 @@ pure nothrow @nogc: } else { - long ms = (ticks != 0 ? appTime(this) : Duration()).as!"msecs"; + long ns = (ticks != 0 ? appTime(this) : Duration()).as!"nsecs"; if (!buffer.ptr) - return 2 + timeToString(ms, null); + return 2 + timeToString(ns, null); if (buffer.length < 2) return -1; buffer[0..2] = "T+"; - ptrdiff_t len = timeToString(ms, buffer[2..$]); + ptrdiff_t len = timeToString(ns, buffer[2..$]); return len < 0 ? len : 2 + len; } } + ptrdiff_t fromString(const(char)[] s) + { + static if (clock == Clock.SystemTime) + { + DateTime dt; + ptrdiff_t len = dt.fromString(s); + if (len >= 0) + this = getSysTime(dt); + return len; + } + else + { + assert(false, "TODO: ???"); // what is the format we parse? + } + } + auto __debugOverview() const { debug @@ -191,7 +207,98 @@ pure nothrow @nogc: import urt.string.format : FormatArg; ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const { - return timeToString(as!"msecs", buffer); + return timeToString(as!"nsecs", buffer); + } + + ptrdiff_t fromString(const(char)[] s) + { + import urt.conv : parse_int; + import urt.string.ascii : is_alpha, ieq; + + if (s.length == 0) + return -1; + + size_t offset = 0; + long total_nsecs = 0; + ubyte last_unit = 8; + + while (offset < s.length) + { + // Parse number + size_t len; + long value = s[offset..$].parse_int(&len); + if (len == 0) + return last_unit != 8 ? offset : -1; + offset += len; + + if (offset >= s.length) + return -1; + + // Parse unit + size_t unit_start = offset; + while (offset < s.length && s[offset].is_alpha) + offset++; + + if (offset == unit_start) + return -1; + + const(char)[] unit = s[unit_start..offset]; + + // Convert unit to nanoseconds and check for duplicates + ubyte this_unit; + if (unit.ieq("w")) + { + value *= 604_800_000_000_000; + this_unit = 7; + } + else if (unit.ieq("d")) + { + value *= 86_400_000_000_000; + this_unit = 6; + } + else if (unit.ieq("h")) + { + value *= 3_600_000_000_000; + this_unit = 5; + } + else if (unit.ieq("m")) + { + value *= 60_000_000_000; + this_unit = 4; + } + else if (unit.ieq("s")) + { + value *= 1_000_000_000; + this_unit = 3; + } + else if (unit.ieq("ms")) + { + value *= 1_000_000; + this_unit = 2; + } + else if (unit.ieq("us")) + { + value *= 1_000; + this_unit = 1; + } + else if (unit.ieq("ns")) + this_unit = 0; + else + return -1; + + // Check for ordering, duplicates, and only one of ms/us/ns may be present + if (this_unit >= last_unit || (this_unit < 2 && last_unit < 3)) + return -1; + last_unit = this_unit; + + total_nsecs += value; + } + + if (last_unit == 8) + return -1; + + ticks = total_nsecs / nsecMultiplier; + return offset; } auto __debugOverview() const @@ -290,15 +397,6 @@ pure nothrow @nogc: import urt.conv : format_int; size_t offset = 0; - uint y = year; - if (year <= 0) - { - if (buffer.length < 3) - return -1; - y = -year + 1; - buffer[0 .. 3] = "BC "; - offset += 3; - } ptrdiff_t len = year.format_int(buffer[offset..$]); if (len < 0 || len == buffer.length) return -1; @@ -313,7 +411,7 @@ pure nothrow @nogc: if (len < 0 || len == buffer.length) return -1; offset += len; - buffer[offset++] = ' '; + buffer[offset++] = 'T'; len = hour.format_int(buffer[offset..$], 10, 2, '0'); if (len < 0 || len == buffer.length) return -1; @@ -325,15 +423,149 @@ pure nothrow @nogc: offset += len; buffer[offset++] = ':'; len = second.format_int(buffer[offset..$], 10, 2, '0'); - if (len < 0 || len == buffer.length) + if (len < 0) return -1; offset += len; - buffer[offset++] = '.'; - len = (ns / 1_000_000).format_int(buffer[offset..$], 10, 3, '0'); - if (len < 0) - return len; + if (ns) + { + if (len == buffer.length) + return -1; + buffer[offset++] = '.'; + uint nsecs = ns; + uint m = 0; + while (nsecs) + { + if (len == buffer.length) + return -1; + int digit = nsecs / digit_multipliers[m]; + buffer[offset++] = cast(char)('0' + digit); + nsecs -= digit * digit_multipliers[m++]; + } + } return offset + len; } + + ptrdiff_t fromString(const(char)[] s) + { + import urt.conv : parse_int; + import urt.string.ascii : ieq, is_numeric, is_whitespace, to_lower; + + if (s.length < 14) + return -1; + size_t offset = 0; + + // parse year + if (s[0..2].ieq("bc")) + { + offset = 2; + if (s[2] == ' ') + ++offset; + } + if (s[offset] == '+') + return -1; + size_t len; + long value = s[offset..$].parse_int(&len); + if (len == 0) + return -1; + if (offset > 0) + { + if (value <= 0) + return -1; // no year 0, or negative years BC! + value = -value + 1; + } + if (value < short.min || value > short.max) + return -1; + year = cast(short)value; + offset += len; + + if (s[offset] != '-' && s[offset] != '/') + return -1; + + // parse month + value = s[++offset..$].parse_int(&len); + if (len == 0) + { + foreach (i; 0..12) + { + if (s[offset..offset+3].ieq(g_month_names[i])) + { + value = i + 1; + len = 3; + goto got_month; + } + } + return -1; + got_month: + } + else if (value < 1 || value > 12) + return -1; + month = cast(Month)value; + offset += len; + + if (s[offset] != '-' && s[offset] != '/') + return -1; + + // parse day + value = s[++offset..$].parse_int(&len); + if (len == 0 || value < 1 || value > 31) + return -1; + day = cast(ubyte)value; + offset += len; + + if (offset >= s.length || (s[offset] != 'T' && s[offset] != ' ')) + return -1; + + // parse hour + value = s[++offset..$].parse_int(&len); + if (len != 2 || value < 0 || value > 23) + return -1; + hour = cast(ubyte)value; + offset += len; + + if (offset >= s.length || s[offset++] != ':') + return -1; + + // parse minute + value = s[offset..$].parse_int(&len); + if (len != 2 || value < 0 || value > 59) + return -1; + minute = cast(ubyte)value; + offset += len; + + if (offset >= s.length || s[offset++] != ':') + return -1; + + // parse second + value = s[offset..$].parse_int(&len); + if (len != 2 || value < 0 || value > 59) + return -1; + second = cast(ubyte)value; + offset += len; + + ns = 0; + if (offset < s.length && s[offset] == '.') + { + // parse fractions + ++offset; + uint multiplier = 100_000_000; + while (offset < s.length && multiplier > 0 && s[offset].is_numeric) + { + ns += (s[offset++] - '0') * multiplier; + multiplier /= 10; + } + if (multiplier == 100_000_000) + return -1; // no number after the dot + } + + if (offset < s.length && s[offset].to_lower == 'z') + { + ++offset; + // TODO: UTC timezone designator... + assert(false, "TODO: we need to know our local timezone..."); + } + + return offset; + } } Duration dur(string base)(long value) pure @@ -403,6 +635,10 @@ SysTime getSysTime() } } +SysTime getSysTime(DateTime time) pure +{ + assert(false, "TODO: convert to SysTime..."); +} DateTime getDateTime() { @@ -459,6 +695,9 @@ private: immutable MonoTime startTime; +__gshared immutable string[12] g_month_names = [ "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" ]; +__gshared immutable uint[9] digit_multipliers = [ 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1 ]; + version (Windows) { immutable uint ticksPerSecond; @@ -517,22 +756,38 @@ package(urt) void initClock() static assert(false, "TODO"); } -ptrdiff_t timeToString(long ms, char[] buffer) pure +ptrdiff_t timeToString(long ns, char[] buffer) pure { import urt.conv : format_int; - long hr = ms / 3_600_000; + long hr = ns / 3_600_000_000_000; if (!buffer.ptr) - return hr.format_int(null, 10, 2, '0') + 10; + { + size_t tail = 6; + ns %= 1_000_000_000; + if (ns) + { + ++tail; + uint m = 0; + do + { + ++tail; + uint digit = cast(uint)(ns / digit_multipliers[m]); + ns -= digit * digit_multipliers[m++]; + } + while (ns); + } + return hr.format_int(null, 10, 2, '0') + tail; + } ptrdiff_t len = hr.format_int(buffer, 10, 2, '0'); - if (len < 0 || buffer.length < len + 10) + if (len < 0 || buffer.length < len + 6) return -1; - ubyte min = cast(ubyte)(ms / 60_000 % 60); - ubyte sec = cast(ubyte)(ms / 1000 % 60); - ms %= 1000; + ubyte min = cast(ubyte)(ns / 60_000_000_000 % 60); + ubyte sec = cast(ubyte)(ns / 1_000_000_000 % 60); + ns %= 1_000_000_000; buffer.ptr[len++] = ':'; buffer.ptr[len++] = cast(char)('0' + (min / 10)); @@ -540,10 +795,21 @@ ptrdiff_t timeToString(long ms, char[] buffer) pure buffer.ptr[len++] = ':'; buffer.ptr[len++] = cast(char)('0' + (sec / 10)); buffer.ptr[len++] = cast(char)('0' + (sec % 10)); - buffer.ptr[len++] = '.'; - buffer.ptr[len++] = cast(char)('0' + (ms / 100)); - buffer.ptr[len++] = cast(char)('0' + ((ms/10) % 10)); - buffer.ptr[len++] = cast(char)('0' + (ms % 10)); + if (ns) + { + if (buffer.length < len + 2) + return -1; + buffer.ptr[len++] = '.'; + uint m = 0; + while (ns) + { + if (buffer.length < len + 1) + return -1; + uint digit = cast(uint)(ns / digit_multipliers[m]); + buffer.ptr[len++] = cast(char)('0' + digit); + ns -= digit * digit_multipliers[m++]; + } + } return len; } @@ -552,10 +818,55 @@ unittest import urt.mem.temp; assert(tconcat(msecs(3_600_000*3 + 60_000*47 + 1000*34 + 123))[] == "03:47:34.123"); - assert(tconcat(msecs(3_600_000*-123))[] == "-123:00:00.000"); - - assert(getTime().toString(null, null, null) == 14); + assert(tconcat(msecs(3_600_000*-123))[] == "-123:00:00"); + assert(MonoTime().toString(null, null, null) == 10); assert(tconcat(getTime())[0..2] == "T+"); + + // Test Duration.fromString with compound formats + Duration d; + + // Simple single units + assert(d.fromString("5s") == 2 && d.as!"seconds" == 5); + assert(d.fromString("10m") == 3 && d.as!"minutes" == 10); + + // Compound durations + assert(d.fromString("1h30m") == 5 && d.as!"minutes" == 90); + assert(d.fromString("5m30s") == 5 && d.as!"seconds" == 330); + + // Duplicate units should fail + assert(d.fromString("30m30m") == -1); + assert(d.fromString("1h2h") == -1); + assert(d.fromString("5s10s") == -1); + + // Out-of-order units should fail + assert(d.fromString("30s5m") == -1); // s before m + assert(d.fromString("1m2h") == -1); // m before h + assert(d.fromString("5s10ms5m") == -1); // m after s + assert(d.fromString("5s10ms10ns") == -1); // ms and ns (only one sub-second unit allowed) + + // Improper units should fail + assert(d.fromString("30z") == -1); // not a time denomination + + // Correctly ordered units should work + assert(d.fromString("1d2h30m15s") == 10 && d.as!"seconds" == 86_400 + 7_200 + 1_800 + 15); + + // Test DateTime.fromString + DateTime dt; + assert(dt.fromString("2024-06-15T12:34:56") == 19); + assert(dt.fromString("-100/06/15 12:34:56") == 19); + assert(dt.fromString("BC100-AUG-15 12:34:56.789") == 25); + assert(dt.fromString("BC 10000-AUG-15 12:34:56.789123456") == 34); + assert(dt.fromString("1-1-1 01:01:01") == 14); + assert(dt.fromString("1-1-1 01:01:01.") == -1); + assert(dt.fromString("2025-01-01") == -1); + assert(dt.fromString("2024-0-15 12:34:56") == -1); + assert(dt.fromString("2024-13-15 12:34:56") == -1); + assert(dt.fromString("2024-1-0 12:34:56") == -1); + assert(dt.fromString("2024-1-32 12:34:56") == -1); + assert(dt.fromString("2024-1-1 24:34:56") == -1); + assert(dt.fromString("2024-1-1 01:60:56") == -1); + assert(dt.fromString("2024-1-1 01:01:60") == -1); + assert(dt.fromString("10000-1-1 1:01:01") == -1); } From d5527350f783423107967fae2d3c6d8df375b07b Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 3 Nov 2025 22:08:21 +1000 Subject: [PATCH 027/138] Added support for `Duration` to `Variant`, which is a synonym for `Quantity!Nanoseconds`. Since both types are functionally equivalent, they can be interchanged. --- src/urt/si/unit.d | 1 + src/urt/variant.d | 48 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index 1db2668..480ab98 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -66,6 +66,7 @@ enum CubicMetre = Metre^^3; enum Litre = ScaledUnit(CubicMetre, -3); enum Gram = ScaledUnit(Kilogram, -3); enum Milligram = ScaledUnit(Kilogram, -6); +enum Nanosecond = ScaledUnit(Second, -9); enum Hertz = Cycle / Second; //enum Kilohertz = TODO: ONOES! Our system can't encode kilohertz! This is disaster!! enum Newton = Kilogram * Metre / Second^^2; diff --git a/src/urt/variant.d b/src/urt/variant.d index 52c004c..a086c33 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -8,7 +8,8 @@ import urt.lifetime; import urt.map; import urt.mem.allocator; import urt.si.quantity; -import urt.si.unit : ScaledUnit; +import urt.si.unit : ScaledUnit, Second, Nanosecond; +import urt.time : Duration, dur; import urt.traits; nothrow @nogc: @@ -20,7 +21,8 @@ enum ValidUserType(T) = (is(T == struct) || is(T == class)) && !is(T == VariantKVP) && !is(T == Array!U, U) && !is(T : const(char)[]) && - !is(T == Quantity!(T, U), T, alias U); + !is(T == Quantity!(T, U), T, alias U) && + !is(T == Duration); alias VariantKVP = KVP!(const(char)[], Variant); @@ -170,6 +172,13 @@ nothrow @nogc: } } + this(Duration dur) + { + this(dur.as!"nsecs"); + flags |= Flags.IsQuantity; + count = Nanosecond.pack; + } + this(const(char)[] s) // TODO: (S)(S s) // if (is(S : const(char)[])) { @@ -567,6 +576,8 @@ nothrow @nogc: => (flags & Flags.DoubleFlag) != 0; bool isQuantity() const pure => (flags & Flags.IsQuantity) != 0; + bool isDuration() const pure + => isQuantity && (count & 0xFFFFFF) == Second.pack; bool isString() const pure => (flags & Flags.IsString) != 0; bool isArray() const pure @@ -707,6 +718,35 @@ nothrow @nogc: return r; } + Duration asDuration() const pure @property + { + assert(isDuration); + alias Nanoseconds = Quantity!(long, Nanosecond); + Nanoseconds ns; + if (size_t.sizeof < 8 && isFloat) // TODO: better way to detect if double is NOT supported in hardware? + { + Quantity!float q; + q.value = asFloat; + q.unit.pack = count; + ns = cast(Nanoseconds)q; + } + else if (isDouble) + { + Quantity!double q; + q.value = asDouble; + q.unit.pack = count; + ns = cast(Nanoseconds)q; + } + else + { + Quantity!long q; + q.value = asLong; + q.unit.pack = count; + ns = q; + } + return ns.value.dur!"nsecs"; + } + const(char)[] asString() const pure { if (isNull) @@ -804,6 +844,10 @@ nothrow @nogc: { return asQuantity!U(); } + else static if (is(T == Duration)) + { + return asDuration; + } else static if (is(T : const(char)[])) { static if (is(T == struct)) // for String/MutableString/etc From 7b57ce8ca3a14a2d5f9dd70d761064b370bffde6 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 5 Nov 2025 22:16:42 +1000 Subject: [PATCH 028/138] Fixed format for Quantity and related types. --- src/urt/si/quantity.d | 5 +++-- src/urt/si/unit.d | 14 ++++++++------ src/urt/variant.d | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index c8da35e..880166a 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -291,7 +291,8 @@ nothrow @nogc: return r; } - ptrdiff_t toString(char[] buffer) const + import urt.string.format : FormatArg; + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const { import urt.conv : format_float; @@ -338,7 +339,7 @@ nothrow @nogc: if (u.pack) { - ptrdiff_t l2 = u.toString(buffer[l .. $]); + ptrdiff_t l2 = u.toString(buffer[l .. $], null, null); if (l2 < 0) return l2; l += l2; diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index 480ab98..1b98a6a 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -108,8 +108,8 @@ nothrow: // debug/ctfe helper string toString() pure { - char[32] t; - ptrdiff_t l = toString(t); + char[32] t = void; + ptrdiff_t l = toString(t, null, null); if (l < 0) return "Invalid unit"; // OR JUST COULDN'T STRINGIFY! return t[0..l].idup; @@ -244,7 +244,8 @@ nothrow: this = this.opBinary!op(rh); } - ptrdiff_t toString(char[] buffer) const pure + import urt.string.format : FormatArg; + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const pure { assert(false, "TODO"); } @@ -361,8 +362,8 @@ nothrow: // debug/ctfe helper string toString() pure { - char[32] t; - ptrdiff_t l = toString(t); + char[32] t = void; + ptrdiff_t l = toString(t, null, null); if (l < 0) return "Invalid unit"; // OR JUST COULDN'T STRINGIFY! return t[0..l].idup; @@ -706,7 +707,8 @@ nothrow: return len; } - ptrdiff_t toString(char[] buffer) const pure + import urt.string.format : FormatArg; + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const pure { if (!unit.pack) { diff --git a/src/urt/variant.d b/src/urt/variant.d index a086c33..febad39 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -937,7 +937,7 @@ nothrow @nogc: case Variant.Type.Number: if (isQuantity()) - return asQuantity().toString(buffer);//, format, formatArgs); + return asQuantity().toString(buffer, format, formatArgs); if (isDouble()) return asDouble().format_float(buffer); From 42f5ac166a610175778ccc9e1f52c2d1dfbab8db Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 5 Nov 2025 22:21:03 +1000 Subject: [PATCH 029/138] Add a way to register simple log sinks. TODO: more to come on this... --- src/urt/log.d | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/urt/log.d b/src/urt/log.d index 71af7fd..029718c 100644 --- a/src/urt/log.d +++ b/src/urt/log.d @@ -1,8 +1,11 @@ module urt.log; -import urt.io; +import urt.mem.temp; -enum Level +nothrow @nogc: + + +enum Level : ubyte { Error = 0, Warning, @@ -12,8 +15,19 @@ enum Level immutable string[] levelNames = [ "Error", "Warning", "Info", "Debug" ]; +alias LogSink = void function(Level level, const(char)[] message) nothrow @nogc; + __gshared Level logLevel = Level.Info; +void register_log_sink(LogSink sink) nothrow @nogc +{ + if (g_log_sink_count < g_log_sinks.length) + { + g_log_sinks[g_log_sink_count] = sink; + g_log_sink_count++; + } +} + void writeDebug(T...)(ref T things) { writeLog(Level.Debug, things); } void writeInfo(T...)(ref T things) { writeLog(Level.Info, things); } void writeWarning(T...)(ref T things) { writeLog(Level.Warning, things); } @@ -28,12 +42,26 @@ void writeLog(T...)(Level level, ref T things) { if (level > logLevel) return; - writeln(levelNames[level], ": ", things); + + const(char)[] message = tconcat(levelNames[level], ": ", things); + for (size_t i = 0; i < g_log_sink_count; i++) + g_log_sinks[i](level, message); } void writeLogf(T...)(Level level, const(char)[] format, ref T things) { if (level > logLevel) return; - writelnf("{-2}: {@-1}", things, levelNames[level], format); + + const(char)[] message = tformat("{0}: {@-2}", levelNames[level], things, format); + + for (size_t i = 0; i < g_log_sink_count; i++) + g_log_sinks[i](level, message); } + + +private: + +// HACK: temp until we have a proper registration process... +__gshared LogSink[8] g_log_sinks; +__gshared size_t g_log_sink_count = 0; From 0cf0f6b23f5d6a15f58e2face397f545c17c8eac Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 6 Nov 2025 03:24:50 +1000 Subject: [PATCH 030/138] Fix the DateTime stringify function. It didn't previously report the needed length when supplied a null buffer. Also, the ISO 8601 timestamp format requires padded month-day. Also, fixed a bug! --- src/urt/time.d | 60 +++++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/src/urt/time.d b/src/urt/time.d index b8dd4be..80accd5 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -394,55 +394,65 @@ pure nothrow @nogc: import urt.string.format : FormatArg; ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const { - import urt.conv : format_int; + import urt.conv : format_int, format_uint; + + ptrdiff_t len; + if (!buffer.ptr) + { + len = 15; // all the fixed chars + len += year.format_int(null); + if (ns) + { + ++len; // the dot + uint nsecs = ns; + uint m = 0; + while (nsecs) + { + ++len; + uint digit = nsecs / digit_multipliers[m]; + nsecs -= digit * digit_multipliers[m++]; + } + } + return len; + } size_t offset = 0; - ptrdiff_t len = year.format_int(buffer[offset..$]); + len = year.format_int(buffer[offset..$]); if (len < 0 || len == buffer.length) return -1; offset += len; buffer[offset++] = '-'; - len = month.format_int(buffer[offset..$]); - if (len < 0 || len == buffer.length) - return -1; - offset += len; + buffer[offset++] = '0' + (month / 10); + buffer[offset++] = '0' + (month % 10); buffer[offset++] = '-'; - len = day.format_int(buffer[offset..$]); - if (len < 0 || len == buffer.length) - return -1; - offset += len; + buffer[offset++] = '0' + (day / 10); + buffer[offset++] = '0' + (day % 10); buffer[offset++] = 'T'; - len = hour.format_int(buffer[offset..$], 10, 2, '0'); - if (len < 0 || len == buffer.length) - return -1; - offset += len; + buffer[offset++] = '0' + (hour / 10); + buffer[offset++] = '0' + (hour % 10); buffer[offset++] = ':'; - len = minute.format_int(buffer[offset..$], 10, 2, '0'); - if (len < 0 || len == buffer.length) - return -1; - offset += len; + buffer[offset++] = '0' + (minute / 10); + buffer[offset++] = '0' + (minute % 10); buffer[offset++] = ':'; - len = second.format_int(buffer[offset..$], 10, 2, '0'); - if (len < 0) - return -1; - offset += len; + buffer[offset++] = '0' + (second / 10); + buffer[offset++] = '0' + (second % 10); if (ns) { - if (len == buffer.length) + if (offset == buffer.length) return -1; buffer[offset++] = '.'; uint nsecs = ns; uint m = 0; while (nsecs) { - if (len == buffer.length) + if (offset == buffer.length) return -1; int digit = nsecs / digit_multipliers[m]; buffer[offset++] = cast(char)('0' + digit); nsecs -= digit * digit_multipliers[m++]; } } - return offset + len; + return offset; } ptrdiff_t fromString(const(char)[] s) From 294a273913b0fe8cfb54da3b9d9e5660e2d40209 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 10 Nov 2025 23:22:10 +1000 Subject: [PATCH 031/138] Improve StringLit interaction with immutable. --- src/urt/string/string.d | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/urt/string/string.d b/src/urt/string/string.d index b2f7abf..4fa8cdb 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -57,12 +57,11 @@ template StringLit(const(char)[] lit, bool zeroTerminate = true) return buffer; }(); pragma(aligned, 2) - private __gshared literal = LiteralData; + private __gshared immutable literal = LiteralData; - enum String StringLit = String(literal.ptr + 2, false); + enum StringLit = immutable(String)(literal.ptr + 2, false); } - String makeString(const(char)[] s) nothrow { if (s.length == 0) @@ -187,7 +186,17 @@ nothrow @nogc: // TODO: I made this return ushort, but normally length() returns size_t ushort length() const pure - => ptr ? ((cast(ushort*)ptr)[-1] & 0x7FFF) : 0; + { + if (__ctfe) + { + version (LittleEndian) + return ptr ? cast(ushort)(ptr[-2] | (ptr[-1] << 8)) & 0x7FFF : 0; + else + return ptr ? cast(ushort)((ptr[-1] | (ptr[-2] << 8)) & 0x7FFF) : 0; + } + else + return ptr ? ((cast(ushort*)ptr)[-1] & 0x7FFF) : 0; + } bool opCast(T : bool)() const pure => ptr != null && ((cast(ushort*)ptr)[-1] & 0x7FFF) != 0; From 9b6bebcdfb6f9be352f0a0515f87e354b943fb12 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 10 Nov 2025 23:33:13 +1000 Subject: [PATCH 032/138] Added EnumInfo for efficient runtime enum lookup. --- src/urt/algorithm.d | 64 ++++++++--- src/urt/meta/package.d | 235 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 278 insertions(+), 21 deletions(-) diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index 1e603fa..0484777 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -1,6 +1,7 @@ module urt.algorithm; -import urt.traits : lvalue_of; +import urt.meta : AliasSeq; +import urt.traits : is_some_function, lvalue_of, Parameters, ReturnType, Unqual; import urt.util : swap; version = SmallSize; @@ -56,8 +57,36 @@ auto compare(T, U)(auto ref T a, auto ref U b) return a < b ? -1 : (a > b ? 1 : 0); } +size_t binary_search(Pred)(const void[] arr, size_t stride, const void* value, auto ref Pred pred) + if (is_some_function!Pred && is(ReturnType!Pred == int) && is(Parameters!Pred == AliasSeq!(const void*, const void*))) +{ + const void* p = arr.ptr; + size_t low = 0; + size_t high = arr.length; + while (low < high) + { + size_t mid = low + (high - low) / 2; + + // should we chase the first in a sequence of same values? + int cmp = pred(p + mid*stride, value); + if (cmp < 0) + low = mid + 1; + else + high = mid; + } + if (low == arr.length) + return arr.length; + if (pred(p + low*stride, value) == 0) + return low; + return arr.length; +} + size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmp_args) + if (!is(Unqual!T == void)) { + static if (is(pred == void)) + static assert (cmp_args.length == 1, "binary_search without a predicate requires exactly one comparison argument"); + T* p = arr.ptr; size_t low = 0; size_t high = arr.length; @@ -82,6 +111,8 @@ size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmp_arg high = mid; } } + if (low == arr.length) + return arr.length; static if (is(pred == void)) { if (p[low] == cmp_args[0]) @@ -96,22 +127,27 @@ size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmp_arg } -void qsort(alias pred = void, T)(T[] arr) +void qsort(alias pred = void, T)(T[] arr) pure { version (SmallSize) + enum use_small_size_impl = true; + else + enum use_small_size_impl = false; + + if (!__ctfe && use_small_size_impl) { static if (is(pred == void)) static if (__traits(compiles, lvalue_of!T.opCmp(lvalue_of!T))) - static int compare(const void* a, const void* b) nothrow @nogc + static int compare(const void* a, const void* b) pure nothrow @nogc => (*cast(const T*)a).opCmp(*cast(const T*)b); else - static int compare(const void* a, const void* b) nothrow @nogc + static int compare(const void* a, const void* b) pure nothrow @nogc => *cast(const T*)a < *cast(const T*)b ? -1 : *cast(const T*)a > *cast(const T*)b ? 1 : 0; else - static int compare(const void* a, const void* b) nothrow @nogc + static int compare(const void* a, const void* b) pure nothrow @nogc => pred(*cast(T*)a, *cast(T*)b); - qsort(arr[], T.sizeof, &compare, (void* a, void* b) nothrow @nogc { + qsort(arr[], T.sizeof, &compare, (void* a, void* b) pure nothrow @nogc { swap(*cast(T*)a, *cast(T*)b); }); } @@ -121,7 +157,7 @@ void qsort(alias pred = void, T)(T[] arr) if (arr.length > 1) { size_t pivotIndex = arr.length / 2; - T* pivot = &p[pivotIndex]; + T* pivot = p + pivotIndex; size_t i = 0; size_t j = arr.length - 1; @@ -135,8 +171,8 @@ void qsort(alias pred = void, T)(T[] arr) } else { - while (pred(*cast(T*)&p[i], *cast(T*)pivot) < 0) i++; - while (pred(*cast(T*)&p[j], *cast(T*)pivot) > 0) j--; + while (pred(p[i], *pivot) < 0) i++; + while (pred(p[j], *pivot) > 0) j--; } if (i <= j) { @@ -147,9 +183,9 @@ void qsort(alias pred = void, T)(T[] arr) } if (j > 0) - qsort(p[0 .. j + 1]); + qsort!pred(p[0 .. j + 1]); if (i < arr.length) - qsort(p[i .. arr.length]); + qsort!pred(p[i .. arr.length]); } } } @@ -159,7 +195,7 @@ unittest struct S { int x; - int opCmp(ref const S rh) const nothrow @nogc + int opCmp(ref const S rh) pure const nothrow @nogc => x < rh.x ? -1 : x > rh.x ? 1 : 0; } @@ -167,7 +203,7 @@ unittest qsort(arr); assert(arr == [-1, 3, 17, 30, 100]); - S[5] arr2 = [ S(3), S(100), S(-1), S(17), S(30)]; + S[5] arr2 = [ S(3), S(100), S(-1), S(17), S(30) ]; qsort(arr2); foreach (i, ref s; arr2) assert(s.x == arr[i]); @@ -189,7 +225,7 @@ version (SmallSize) // just one generic implementation to minimise the code... // kinda slow though... look at all those multiplies! // maybe there's some way to make this faster :/ - void qsort(void[] arr, size_t element_size, int function(const void* a, const void* b) nothrow @nogc compare, void function(void* a, void* b) nothrow @nogc swap) + void qsort(void[] arr, size_t element_size, int function(const void* a, const void* b) pure nothrow @nogc compare, void function(void* a, void* b) pure nothrow @nogc swap) pure { void* p = arr.ptr; size_t length = arr.length / element_size; diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index 599d6c9..b7b1449 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -1,5 +1,7 @@ module urt.meta; +import urt.traits : is_callable, is_enum, EnumType, Unqual; + pure nothrow @nogc: alias Alias(alias a) = a; @@ -7,6 +9,52 @@ alias Alias(T) = T; alias AliasSeq(TList...) = TList; +template Iota(ptrdiff_t start, ptrdiff_t end) +{ + static assert(start <= end, "start must be less than or equal to end"); + alias Iota = AliasSeq!(); + static foreach (i; start .. end) + Iota = AliasSeq!(Iota, i); +} +alias Iota(size_t end) = Iota!(0, end); + +auto make_delegate(Fun)(Fun fun) + if (is_callable!Fun) +{ + import urt.traits : is_function_pointer, ReturnType, Parameters; + + static if (is_function_pointer!fun) + { + struct Hack + { + static if (is(Fun : R function(Args) pure nothrow @nogc, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) pure nothrow @nogc => (cast(ReturnType!Fun function(Parameters!Fun args) pure nothrow @nogc)&this)(args); + else static if (is(Fun : R function(Args) nothrow @nogc, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) nothrow @nogc => (cast(ReturnType!Fun function(Parameters!Fun args) nothrow @nogc)&this)(args); + else static if (is(Fun : R function(Args) pure @nogc, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) pure @nogc => (cast(ReturnType!Fun function(Parameters!Fun args) pure @nogc)&this)(args); + else static if (is(Fun : R function(Args) pure nothrow, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) pure nothrow => (cast(ReturnType!Fun function(Parameters!Fun args) pure nothrow)&this)(args); + else static if (is(Fun : R function(Args) pure, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) pure => (cast(ReturnType!Fun function(Parameters!Fun args) pure)&this)(args); + else static if (is(Fun : R function(Args) nothrow, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) nothrow => (cast(ReturnType!Fun function(Parameters!Fun args) nothrow)&this)(args); + else static if (is(Fun : R function(Args) @nogc, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) @nogc => (cast(ReturnType!Fun function(Parameters!Fun args) @nogc)&this)(args); + else static if (is(Fun : R function(Args), R, Args...)) + ReturnType!Fun call(Parameters!Fun args) => (cast(ReturnType!Fun function(Parameters!Fun args))&this)(args); + } + Hack hack; + auto dg = &hack.call; + dg.ptr = fun; + return dg; + } + else static if (is(Fun == delegate)) + return fun; + else + static assert(false, "Unsupported type for make_delegate"); +} + ulong bit_mask(size_t bits) { return (1UL << bits) - 1; @@ -84,18 +132,191 @@ template INTERLEAVE_SEPARATOR(alias sep, Args...) template enum_keys(E) { - static assert(is(E == enum), "enum_keys only works with enums!"); + static assert(is_enum!E, "enum_keys only works with enums!"); __gshared immutable string[enum_strings.length] enum_keys = [ enum_strings ]; private alias enum_strings = __traits(allMembers, E); } -E enum_from_string(E)(const(char)[] key) -if (is(E == enum)) +const(E)* enum_from_key(E)(const(char)[] key) + if (is_enum!E) + => MakeEnumInfo!E.value_for(key); + +const(char)[] enum_key_from_value(E)(EnumType!E value) + if (is_enum!E) + => MakeEnumInfo!E.key_for(value); + +struct VoidEnumInfo { - foreach (i, k; enum_keys!E) - if (key[] == k[]) - return cast(E)i; - return cast(E)-1; + import urt.algorithm : binary_search; + import urt.string; + + // keys and values are sorted for binary search + const String[] keys; + const void[] values; + uint stride; + uint type_hash; + + const(char)[] key_for(const void* value, int function(const void* a, const void* b) pure nothrow @nogc pred) const pure + { + size_t i = binary_search(values, stride, value, pred); + if (i < values.length) + return keys[_v2k_lookup[i]][]; + return null; + } + + const(char)[] key_for(const void* value, int delegate(const void* a, const void* b) pure nothrow @nogc pred) const pure + { + size_t i = binary_search(values, stride, value, pred); + if (i < values.length) + return keys[_v2k_lookup[i]][]; + return null; + } + + const(void)* value_for(const(char)[] key) const pure + { + size_t i = binary_search(keys, key); + if (i == keys.length) + return null; + i = _k2v_lookup[i]; + return &values[i*stride]; + } + + bool contains(const(char)[] key) const pure + { + size_t i = binary_search(keys, key); + return i < keys.length; + } + +private: + // these tables map between indices of keys and values + const ubyte[] _k2v_lookup; + const ubyte[] _v2k_lookup; +} + +template EnumInfo(E) +{ + alias UE = Unqual!E; + + static if (is(UE == void)) + alias EnumInfo = VoidEnumInfo; + else + { + struct EnumInfo + { + import urt.algorithm : binary_search; + import urt.string; + + static assert (EnumInfo.sizeof == EnumInfo.sizeof, "Template EnumInfo must not add any members!"); + + static if (is(UE T == enum)) + alias V = T; + else + static assert(false, E.string ~ " is not an enum type!"); + + // keys and values are sorted for binary search + const String[] keys; + const UE[] values; + uint stride = E.sizeof; + uint type_hash; + + ref inout(VoidEnumInfo) make_void() inout + => *cast(inout VoidEnumInfo*)&this; + + const(char)[] key_for(V value) const pure + { + size_t i = binary_search(values, value); + if (i < values.length) + return keys[_v2k_lookup[i]][]; + return null; + } + + const(UE)* value_for(const(char)[] key) const pure + { + size_t i = binary_search(keys, key); + if (i == keys.length) + return null; + return &values[_k2v_lookup[i]]; + } + + bool contains(const(char)[] key) const pure + { + size_t i = binary_search(keys, key); + return i < keys.length; + } + + private: + // these tables map between indices of keys and values + const ubyte[] _k2v_lookup; + const ubyte[] _v2k_lookup; + } + + // sanity check the typed one matches the untyped one + static assert(EnumInfo.sizeof == VoidEnumInfo.sizeof); + static assert(EnumInfo.keys.offsetof == VoidEnumInfo.keys.offsetof); + static assert(EnumInfo.values.offsetof == VoidEnumInfo.values.offsetof); + static assert(EnumInfo.stride.offsetof == VoidEnumInfo.stride.offsetof); + static assert(EnumInfo.type_hash.offsetof == VoidEnumInfo.type_hash.offsetof); + static assert(EnumInfo._k2v_lookup.offsetof == VoidEnumInfo._k2v_lookup.offsetof); + static assert(EnumInfo._v2k_lookup.offsetof == VoidEnumInfo._v2k_lookup.offsetof); + } +} + + +template MakeEnumInfo(E) + if (is(Unqual!E == enum)) +{ + alias UE = Unqual!E; + + __gshared immutable MakeEnumInfo = EnumInfo!UE( + _keys[], + _values[], + E.sizeof, + 0, + _k2v_lookup[], + _v2k_lookup[], + ); + +private: + import urt.algorithm : binary_search, compare, qsort; + import urt.string; + import urt.string.uni : uni_compare; + + enum NumItems = __traits(allMembers, E).length; + static assert(NumItems <= ubyte.max, "Too many enum items!"); + + // keys and values are sorted for binary search + __gshared immutable String[NumItems] _keys = [ STATIC_MAP!(GetKey, iota) ]; + __gshared immutable UE[NumItems] _values = [ STATIC_MAP!(GetValue, iota) ]; + + // these tables map between indices of keys and values + __gshared immutable ubyte[NumItems] _k2v_lookup = [ STATIC_MAP!(GetKeyRedirect, iota) ]; + __gshared immutable ubyte[NumItems] _v2k_lookup = [ STATIC_MAP!(GetValRedirect, iota) ]; + + // a whole bunch of nonsense to build the tables... + struct KI + { + string k; + ubyte i; + } + struct VI + { + UE v; + ubyte i; + } + + alias iota = Iota!(enum_members.length); + enum enum_members = __traits(allMembers, E); + enum by_key = (){ KI[NumItems] r = [ STATIC_MAP!(MakeKI, iota) ]; r.qsort!((ref a, ref b) => uni_compare(a.k, b.k)); return r; }(); + enum by_value = (){ VI[NumItems] r = [ STATIC_MAP!(MakeVI, iota) ]; r.qsort!((ref a, ref b) => compare(a.v, b.v)); return r; }(); + enum inv_key = (){ KI[NumItems] bk = by_key; ubyte[NumItems] r; foreach (ubyte i, ref ki; bk) r[ki.i] = i; return r; }(); + enum inv_val = (){ VI[NumItems] bv = by_value; ubyte[NumItems] r; foreach (ubyte i, ref vi; bv) r[vi.i] = i; return r; }(); + + enum MakeKI(ushort i) = KI(enum_members[i], i); + enum MakeVI(ushort i) = VI(__traits(getMember, E, enum_members[i]), i); + enum GetKey(size_t i) = StringLit!(by_key[i].k); + enum GetValue(size_t i) = by_value[i].v; + enum GetKeyRedirect(size_t i) = inv_val[by_key[i].i]; + enum GetValRedirect(size_t i) = inv_key[by_value[i].i]; } From 848e77e0acfe6f4fe45827c86fae369af36ba7e7 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 11 Nov 2025 09:23:58 +1000 Subject: [PATCH 033/138] Remove enum_keys helper in favour of the new enum tools. Also eliminated all the redundant length fields, and just store a single length. --- src/urt/algorithm.d | 11 +++-- src/urt/meta/package.d | 105 +++++++++++++++++++++++------------------ 2 files changed, 67 insertions(+), 49 deletions(-) diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index 0484777..c070380 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -60,9 +60,12 @@ auto compare(T, U)(auto ref T a, auto ref U b) size_t binary_search(Pred)(const void[] arr, size_t stride, const void* value, auto ref Pred pred) if (is_some_function!Pred && is(ReturnType!Pred == int) && is(Parameters!Pred == AliasSeq!(const void*, const void*))) { + debug assert(arr.length % stride == 0, "array length must be a multiple of stride"); + const count = arr.length / stride; + const void* p = arr.ptr; size_t low = 0; - size_t high = arr.length; + size_t high = count; while (low < high) { size_t mid = low + (high - low) / 2; @@ -74,11 +77,11 @@ size_t binary_search(Pred)(const void[] arr, size_t stride, const void* value, a else high = mid; } - if (low == arr.length) - return arr.length; + if (low == count) + return count; if (pred(p + low*stride, value) == 0) return low; - return arr.length; + return count; } size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmp_args) diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index b7b1449..91ca9e6 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -130,13 +130,6 @@ template INTERLEAVE_SEPARATOR(alias sep, Args...) } -template enum_keys(E) -{ - static assert(is_enum!E, "enum_keys only works with enums!"); - __gshared immutable string[enum_strings.length] enum_keys = [ enum_strings ]; - private alias enum_strings = __traits(allMembers, E); -} - const(E)* enum_from_key(E)(const(char)[] key) if (is_enum!E) => MakeEnumInfo!E.value_for(key); @@ -145,52 +138,65 @@ const(char)[] enum_key_from_value(E)(EnumType!E value) if (is_enum!E) => MakeEnumInfo!E.key_for(value); +const(char)[] enum_key_by_decl_index(E)(size_t value) + if (is_enum!E) + => MakeEnumInfo!E.key_by_decl_index(value); + struct VoidEnumInfo { import urt.algorithm : binary_search; import urt.string; +nothrow @nogc: // keys and values are sorted for binary search - const String[] keys; - const void[] values; - uint stride; + const String* keys; + const void* values; + ubyte count; + ushort stride; uint type_hash; const(char)[] key_for(const void* value, int function(const void* a, const void* b) pure nothrow @nogc pred) const pure { - size_t i = binary_search(values, stride, value, pred); - if (i < values.length) + size_t i = binary_search(values[0 .. count*stride], stride, value, pred); + if (i < count) return keys[_v2k_lookup[i]][]; return null; } const(char)[] key_for(const void* value, int delegate(const void* a, const void* b) pure nothrow @nogc pred) const pure { - size_t i = binary_search(values, stride, value, pred); - if (i < values.length) + size_t i = binary_search(values[0 .. count*stride], stride, value, pred); + if (i < count) return keys[_v2k_lookup[i]][]; return null; } + const(char)[] key_by_decl_index(size_t i) const pure + { + assert(i < count, "Declaration index out of range"); + return keys[_decl_lookup[i]][]; + } + const(void)* value_for(const(char)[] key) const pure { - size_t i = binary_search(keys, key); - if (i == keys.length) + size_t i = binary_search(keys[0 .. count], key); + if (i == count) return null; i = _k2v_lookup[i]; - return &values[i*stride]; + return values + i*stride; } bool contains(const(char)[] key) const pure { - size_t i = binary_search(keys, key); - return i < keys.length; + size_t i = binary_search(keys[0 .. count], key); + return i < count; } private: // these tables map between indices of keys and values - const ubyte[] _k2v_lookup; - const ubyte[] _v2k_lookup; + const ubyte* _k2v_lookup; + const ubyte* _v2k_lookup; + const ubyte* _decl_lookup; } template EnumInfo(E) @@ -205,6 +211,7 @@ template EnumInfo(E) { import urt.algorithm : binary_search; import urt.string; + nothrow @nogc: static assert (EnumInfo.sizeof == EnumInfo.sizeof, "Template EnumInfo must not add any members!"); @@ -214,9 +221,10 @@ template EnumInfo(E) static assert(false, E.string ~ " is not an enum type!"); // keys and values are sorted for binary search - const String[] keys; - const UE[] values; - uint stride = E.sizeof; + const String* keys; + const UE* values; + const ubyte count = __traits(allMembers, E).length; + const ushort stride = E.sizeof; uint type_hash; ref inout(VoidEnumInfo) make_void() inout @@ -224,40 +232,43 @@ template EnumInfo(E) const(char)[] key_for(V value) const pure { - size_t i = binary_search(values, value); - if (i < values.length) + size_t i = binary_search(values[0 .. count], value); + if (i < count) return keys[_v2k_lookup[i]][]; return null; } + const(char)[] key_by_decl_index(size_t i) const pure + => make_void().key_by_decl_index(i); + const(UE)* value_for(const(char)[] key) const pure { - size_t i = binary_search(keys, key); - if (i == keys.length) + size_t i = binary_search(keys[0 .. count], key); + if (i == count) return null; - return &values[_k2v_lookup[i]]; + return values + _k2v_lookup[i]; } bool contains(const(char)[] key) const pure - { - size_t i = binary_search(keys, key); - return i < keys.length; - } + => make_void().contains(key); private: // these tables map between indices of keys and values - const ubyte[] _k2v_lookup; - const ubyte[] _v2k_lookup; + const ubyte* _k2v_lookup; + const ubyte* _v2k_lookup; + const ubyte* _decl_lookup; } // sanity check the typed one matches the untyped one static assert(EnumInfo.sizeof == VoidEnumInfo.sizeof); static assert(EnumInfo.keys.offsetof == VoidEnumInfo.keys.offsetof); static assert(EnumInfo.values.offsetof == VoidEnumInfo.values.offsetof); + static assert(EnumInfo.count.offsetof == VoidEnumInfo.count.offsetof); static assert(EnumInfo.stride.offsetof == VoidEnumInfo.stride.offsetof); static assert(EnumInfo.type_hash.offsetof == VoidEnumInfo.type_hash.offsetof); static assert(EnumInfo._k2v_lookup.offsetof == VoidEnumInfo._k2v_lookup.offsetof); static assert(EnumInfo._v2k_lookup.offsetof == VoidEnumInfo._v2k_lookup.offsetof); + static assert(EnumInfo._decl_lookup.offsetof == VoidEnumInfo._decl_lookup.offsetof); } } @@ -267,13 +278,18 @@ template MakeEnumInfo(E) { alias UE = Unqual!E; - __gshared immutable MakeEnumInfo = EnumInfo!UE( - _keys[], - _values[], + enum ubyte NumItems = __traits(allMembers, E).length; + static assert(NumItems <= ubyte.max, "Too many enum items!"); + + __gshared immutable MakeEnumInfo = immutable(EnumInfo!UE)( + _keys.ptr, + _values.ptr, + NumItems, E.sizeof, 0, - _k2v_lookup[], - _v2k_lookup[], + _lookup.ptr, + _lookup.ptr + NumItems, + _lookup.ptr + NumItems*2 ); private: @@ -281,16 +297,14 @@ private: import urt.string; import urt.string.uni : uni_compare; - enum NumItems = __traits(allMembers, E).length; - static assert(NumItems <= ubyte.max, "Too many enum items!"); - // keys and values are sorted for binary search __gshared immutable String[NumItems] _keys = [ STATIC_MAP!(GetKey, iota) ]; __gshared immutable UE[NumItems] _values = [ STATIC_MAP!(GetValue, iota) ]; // these tables map between indices of keys and values - __gshared immutable ubyte[NumItems] _k2v_lookup = [ STATIC_MAP!(GetKeyRedirect, iota) ]; - __gshared immutable ubyte[NumItems] _v2k_lookup = [ STATIC_MAP!(GetValRedirect, iota) ]; + __gshared immutable ubyte[NumItems * 3] _lookup = [ STATIC_MAP!(GetKeyRedirect, iota), + STATIC_MAP!(GetValRedirect, iota), + STATIC_MAP!(GetKeyOrig, iota) ]; // a whole bunch of nonsense to build the tables... struct KI @@ -317,6 +331,7 @@ private: enum GetValue(size_t i) = by_value[i].v; enum GetKeyRedirect(size_t i) = inv_val[by_key[i].i]; enum GetValRedirect(size_t i) = inv_key[by_value[i].i]; + enum GetKeyOrig(size_t i) = inv_key[i]; } From fe37a7e8e5b28df2761672d769407a71941e17c9 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 17 Nov 2025 01:18:28 +1000 Subject: [PATCH 034/138] Fix several logging bugs (actually mostly string formatting bugs!) --- src/urt/log.d | 2 +- src/urt/string/format.d | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/urt/log.d b/src/urt/log.d index 029718c..9a08f0a 100644 --- a/src/urt/log.d +++ b/src/urt/log.d @@ -53,7 +53,7 @@ void writeLogf(T...)(Level level, const(char)[] format, ref T things) if (level > logLevel) return; - const(char)[] message = tformat("{0}: {@-2}", levelNames[level], things, format); + const(char)[] message = tformat("{-2}: {@-1}", things, levelNames[level], format); for (size_t i = 0; i < g_log_sink_count; i++) g_log_sinks[i](level, message); diff --git a/src/urt/string/format.d b/src/urt/string/format.d index 410432e..074a32d 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -344,9 +344,9 @@ struct DefFormat(T) } static if (is(T == long)) - size_t len = format_int(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', show_sign); + ptrdiff_t len = format_int(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', show_sign); else - size_t len = format_uint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); + ptrdiff_t len = format_uint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); if (to_lower && len > 0) { @@ -473,6 +473,8 @@ struct DefFormat(T) } char[] hex = toHexString(cast(ubyte[])value, buffer, grp1, grp2); + if (!hex.ptr) + return -1; return hex.length; } else static if (is(T : const U[], U)) @@ -861,6 +863,8 @@ ptrdiff_t parseFormat(ref const(char)[] format, ref char[] buffer, const(FormatA if (bytes < 0) return -2; char[] t = formatImpl(buffer, indirectFormat.ptr[0 .. bytes], args); + if (!t.ptr) + return -1; len = t.length; } else From d329520278f4876427cc9b0f64a80086154b476d Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 1 Dec 2025 08:58:29 +1000 Subject: [PATCH 035/138] Add support for Variant to store binary data. Made variant strings a flag on binary data. --- src/urt/format/json.d | 25 +++++++++------ src/urt/traits.d | 2 +- src/urt/variant.d | 72 +++++++++++++++++++++++++++++++++---------- 3 files changed, 72 insertions(+), 27 deletions(-) diff --git a/src/urt/format/json.d b/src/urt/format/json.d index ca2cd26..01e977e 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -100,17 +100,24 @@ ptrdiff_t write_json(ref const Variant val, char[] buffer, bool dense = false, u return -1; return written; - case Variant.Type.String: - const char[] s = val.asString(); - if (buffer.ptr) + case Variant.Type.Buffer: + if (val.isString) { - if (buffer.length < s.length + 2) - return -1; - buffer[0] = '"'; - buffer[1 .. 1 + s.length] = s[]; - buffer[1 + s.length] = '"'; + const char[] s = val.asString(); + if (buffer.ptr) + { + if (buffer.length < s.length + 2) + return -1; + buffer[0] = '"'; + buffer[1 .. 1 + s.length] = s[]; + buffer[1 + s.length] = '"'; + } + return s.length + 2; + } + else + { + assert(false, "TODO: how are binary buffers represented in json?"); } - return s.length + 2; case Variant.Type.Number: import urt.conv; diff --git a/src/urt/traits.d b/src/urt/traits.d index bfa8073..fb124d9 100644 --- a/src/urt/traits.d +++ b/src/urt/traits.d @@ -10,7 +10,7 @@ enum bool is_boolean(T) = __traits(isUnsigned, T) && is(T : bool); enum bool is_unsigned_int(T) = is(Unqual!T == ubyte) || is(Unqual!T == ushort) || is(Unqual!T == uint) || is(Unqual!T == ulong); enum bool is_signed_int(T) = is(Unqual!T == byte) || is(Unqual!T == short) || is(Unqual!T == int) || is(Unqual!T == long); enum bool is_some_int(T) = is_unsigned_int!T || is_signed_int!T; -enum bool is_unsigned_integral(T) = is(Unqual!T == bool) || is_unsigned_int!T || is_some_char!T; +enum bool is_unsigned_integral(T) = is_boolean!T || is_unsigned_int!T || is_some_char!T; enum bool is_signed_integral(T) = is_signed_int!T; enum bool is_integral(T) = is_unsigned_integral!T || is_signed_integral!T; enum bool is_some_float(T) = is(Unqual!T == float) || is(Unqual!T == double) || is(Unqual!T == real); diff --git a/src/urt/variant.d b/src/urt/variant.d index febad39..81af559 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -194,13 +194,29 @@ nothrow @nogc: count = cast(uint)s.length; } + this(T)(T[] buffer) + if (is(Unqual!T == void)) + { + if (buffer.length < embed.length) + { + flags = Flags.ShortBuffer; + embed[0 .. buffer.length] = cast(char[])buffer[]; + embed[$-1] = cast(ubyte)buffer.length; + return; + } + flags = Flags.Buffer; + value.s = cast(char*)buffer.ptr; + count = cast(uint)buffer.length; + } + this(Variant[] a) { flags = Flags.Array; nodeArray = a[]; } + this(T)(T[] a) - if (!is(T == Variant) && !is(T == VariantKVP) && !is(T : dchar)) + if (!is(T == Variant) && !is(T == VariantKVP) && !is(T : dchar) && !is(Unqual!T == void)) { flags = Flags.Array; nodeArray.reserve(a.length); @@ -387,7 +403,7 @@ nothrow @nogc: case Type.Null: if (b.type == Type.Null) return 0; - else if ((b.type == Type.String || b.type == Type.Array || b.type == Type.Map) && b.empty()) + else if ((b.type == Type.Buffer || b.type == Type.Array || b.type == Type.Map) && b.empty()) return 0; r = -1; // sort null before other things... break; @@ -469,13 +485,13 @@ nothrow @nogc: r = -1; // sort numbers before other things... break; - case Type.String: - if (b.type != Type.String) + case Type.Buffer: + if (b.type != Type.Buffer) { r = -1; break; } - r = compare(a.asString(), b.asString()); + r = compare(cast(const(char)[])a.asBuffer(), cast(const(char)[])b.asBuffer()); break; case Type.Array: @@ -578,6 +594,8 @@ nothrow @nogc: => (flags & Flags.IsQuantity) != 0; bool isDuration() const pure => isQuantity && (count & 0xFFFFFF) == Second.pack; + bool isBuffer() const pure + => type == Type.Buffer; bool isString() const pure => (flags & Flags.IsString) != 0; bool isArray() const pure @@ -747,6 +765,16 @@ nothrow @nogc: return ns.value.dur!"nsecs"; } + const(void)[] asBuffer() const pure + { + if (isNull) + return null; + assert(isBuffer); + if (flags & Flags.Embedded) + return embed[0 .. embed[$-1]]; + return value.s[0 .. count]; + } + const(char)[] asString() const pure { if (isNull) @@ -949,17 +977,25 @@ nothrow @nogc: return asUlong().format_uint(buffer); return asLong().format_int(buffer); - case Variant.Type.String: - const char[] s = asString(); - if (buffer.ptr) + case Variant.Type.Buffer: + if (isString) + { + const char[] s = asString(); + if (buffer.ptr) + { + // TODO: should we write out quotes? + // a string of a number won't be distinguishable from a number without quotes... + if (buffer.length < s.length) + return -1; + buffer[0 .. s.length] = s[]; + } + return s.length; + } + else { - // TODO: should we write out quotes? - // a string of a number won't be distinguishable from a number without quotes... - if (buffer.length < s.length) - return -1; - buffer[0 .. s.length] = s[]; + import urt.string.format : formatValue; + return formatValue(asBuffer(), buffer, format, formatArgs); } - return s.length; case Variant.Type.Map: case Variant.Type.Array: @@ -1165,7 +1201,7 @@ package: True = 1, False = 2, Number = 3, - String = 4, + Buffer = 4, Array = 5, Map = 6, User = 7 @@ -1182,8 +1218,10 @@ package: NumberUint64 = cast(Flags)Type.Number | Flags.IsNumber | Flags.Uint64Flag, NumberFloat = cast(Flags)Type.Number | Flags.IsNumber | Flags.FloatFlag | Flags.DoubleFlag, NumberDouble = cast(Flags)Type.Number | Flags.IsNumber | Flags.DoubleFlag, - String = cast(Flags)Type.String | Flags.IsString, - ShortString = cast(Flags)Type.String | Flags.IsString | Flags.Embedded, + Buffer = cast(Flags)Type.Buffer, + ShortBuffer = cast(Flags)Type.Buffer | Flags.Embedded, + String = cast(Flags)Type.Buffer | Flags.IsString, + ShortString = cast(Flags)Type.Buffer | Flags.IsString | Flags.Embedded, Array = cast(Flags)Type.Array | Flags.NeedDestruction, Map = cast(Flags)Type.Map | Flags.NeedDestruction, User = cast(Flags)Type.User, From 60ddd5e778c77146d4931e692fb860c6ebed3be4 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 1 Dec 2025 09:00:19 +1000 Subject: [PATCH 036/138] Add 2 little helpers: - Bounded strlen_s - SysTime from unit time --- src/urt/string/package.d | 8 ++++++++ src/urt/time.d | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/urt/string/package.d b/src/urt/string/package.d index 3210e03..81cbf75 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -15,6 +15,14 @@ public import urt.mem.temp : tstringz, twstringz; nothrow @nogc: +size_t strlen_s(const(char)[] s) pure +{ + size_t len = 0; + while (len < s.length && s[len] != '\0') + ++len; + return len; +} + ptrdiff_t cmp(bool case_insensitive = false, T, U)(const(T)[] a, const(U)[] b) pure { static if (case_insensitive) diff --git a/src/urt/time.d b/src/urt/time.d index 80accd5..7972a1e 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -697,6 +697,16 @@ ulong unixTimeNs(SysTime t) pure static assert(false, "TODO"); } +SysTime from_unix_time_ns(ulong ns) pure +{ + version (Windows) + return SysTime(ns / 100UL + 116444736000000000UL); + else version (Posix) + return SysTime(ns); + else + static assert(false, "TODO"); +} + Duration abs(Duration d) pure => Duration(d.ticks < 0 ? -d.ticks : d.ticks); From 708247852e742132163f933c9ca8b19e0bacb1bc Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 2 Dec 2025 09:55:38 +1000 Subject: [PATCH 037/138] More druntime scrubbing... --- src/urt/internal/lifetime.d | 121 ++++++++++++++++++++++++++++++++++++ src/urt/lifetime.d | 34 +--------- src/urt/math.d | 1 - src/urt/mem/package.d | 20 +++--- 4 files changed, 133 insertions(+), 43 deletions(-) create mode 100644 src/urt/internal/lifetime.d diff --git a/src/urt/internal/lifetime.d b/src/urt/internal/lifetime.d new file mode 100644 index 0000000..314164f --- /dev/null +++ b/src/urt/internal/lifetime.d @@ -0,0 +1,121 @@ +module urt.internal.lifetime; + +import urt.lifetime : forward; + +/+ +emplaceRef is a package function for druntime internal use. It works like +emplace, but takes its argument by ref (as opposed to "by pointer"). +This makes it easier to use, easier to be safe, and faster in a non-inline +build. +Furthermore, emplaceRef optionally takes a type parameter, which specifies +the type we want to build. This helps to build qualified objects on mutable +buffer, without breaking the type system with unsafe casts. ++/ +void emplaceRef(T, UT, Args...)(ref UT chunk, auto ref Args args) +{ + static if (args.length == 0) + { + static assert(is(typeof({static T i;})), + "Cannot emplace a " ~ T.stringof ~ " because " ~ T.stringof ~ + ".this() is annotated with @disable."); + static if (is(T == class)) static assert(!__traits(isAbstractClass, T), + T.stringof ~ " is abstract and it can't be emplaced"); + emplaceInitializer(chunk); + } + else static if ( + !is(T == struct) && Args.length == 1 /* primitives, enums, arrays */ + || + Args.length == 1 && is(typeof({T t = forward!(args[0]);})) /* conversions */ + || + is(typeof(T(forward!args))) /* general constructors */) + { + static struct S + { + T payload; + this()(auto ref Args args) + { + static if (__traits(compiles, payload = forward!args)) + payload = forward!args; + else + payload = T(forward!args); + } + } + if (__ctfe) + { + static if (__traits(compiles, chunk = T(forward!args))) + chunk = T(forward!args); + else static if (args.length == 1 && __traits(compiles, chunk = forward!(args[0]))) + chunk = forward!(args[0]); + else assert(0, "CTFE emplace doesn't support " + ~ T.stringof ~ " from " ~ Args.stringof); + } + else + { + S* p = () @trusted { return cast(S*) &chunk; }(); + static if (UT.sizeof > 0) + emplaceInitializer(*p); + p.__ctor(forward!args); + } + } + else static if (is(typeof(chunk.__ctor(forward!args)))) + { + // This catches the rare case of local types that keep a frame pointer + emplaceInitializer(chunk); + chunk.__ctor(forward!args); + } + else + { + //We can't emplace. Try to diagnose a disabled postblit. + static assert(!(Args.length == 1 && is(Args[0] : T)), + "Cannot emplace a " ~ T.stringof ~ " because " ~ T.stringof ~ + ".this(this) is annotated with @disable."); + + //We can't emplace. + static assert(false, + T.stringof ~ " cannot be emplaced from " ~ Args[].stringof ~ "."); + } +} + +// ditto +static import urt.traits; +void emplaceRef(UT, Args...)(ref UT chunk, auto ref Args args) + if (is(UT == urt.traits.Unqual!UT)) +{ + emplaceRef!(UT, UT)(chunk, forward!args); +} + +/+ +Emplaces T.init. +In contrast to `emplaceRef(chunk)`, there are no checks for disabled default +constructors etc. ++/ +void emplaceInitializer(T)(scope ref T chunk) nothrow pure @trusted + if (!is(T == const) && !is(T == immutable) && !is(T == inout)) +{ + import core.internal.traits : hasElaborateAssign; + + static if (__traits(isZeroInit, T)) + { + import urt.mem : memset; + memset(cast(void*) &chunk, 0, T.sizeof); + } + else static if (__traits(isScalar, T) || + T.sizeof <= 16 && !hasElaborateAssign!T && __traits(compiles, (){ T chunk; chunk = T.init; })) + { + chunk = T.init; + } + else static if (__traits(isStaticArray, T)) + { + // For static arrays there is no initializer symbol created. Instead, we emplace elements one-by-one. + foreach (i; 0 .. T.length) + { + emplaceInitializer(chunk[i]); + } + } + else + { + import urt.mem : memcpy; + const initializer = __traits(initSymbol, T); + memcpy(cast(void*)&chunk, initializer.ptr, initializer.length); + } +} diff --git a/src/urt/lifetime.d b/src/urt/lifetime.d index cb6cf40..586d9db 100644 --- a/src/urt/lifetime.d +++ b/src/urt/lifetime.d @@ -1,5 +1,6 @@ module urt.lifetime; +import urt.internal.lifetime : emplaceRef; // TODO: DESTROY THIS! T* emplace(T)(T* chunk) @safe pure { @@ -78,12 +79,6 @@ T* emplace(T, Args...)(void[] chunk, auto ref Args args) return cast(T*) chunk.ptr; } - -// HACK: we should port this to our lib... -static import core.internal.lifetime; -alias emplaceRef = core.internal.lifetime.emplaceRef; - - /+ void copyEmplace(S, T)(ref S source, ref T target) @system if (is(immutable S == immutable T)) @@ -269,7 +264,7 @@ private void moveEmplaceImpl(T)(scope ref T target, return scope ref T source) @ static if (hasElaborateAssign!T || !isAssignable!T) { - import core.stdc.string : memcpy; + import urt.mem : memcpy; () @trusted { memcpy(&target, &source, T.sizeof); }(); } else @@ -327,20 +322,11 @@ void moveEmplace(T)(ref T source, ref T target) @system @nogc -//debug = PRINTF; - -debug(PRINTF) -{ - import core.stdc.stdio; -} - /// Implementation of `_d_delstruct` and `_d_delstructTrace` template _d_delstructImpl(T) { private void _d_delstructImpure(ref T p) { - debug(PRINTF) printf("_d_delstruct(%p)\n", p); - destroy(*p); p = null; } @@ -478,27 +464,11 @@ T _d_newclassT(T)() @trusted attr |= BlkAttr.NO_SCAN; p = GC.malloc(init.length, attr, typeid(T)); - debug(PRINTF) printf(" p = %p\n", p); - } - - debug(PRINTF) - { - printf("p = %p\n", p); - printf("init.ptr = %p, len = %llu\n", init.ptr, cast(ulong)init.length); - printf("vptr = %p\n", *cast(void**) init); - printf("vtbl[0] = %p\n", (*cast(void***) init)[0]); - printf("vtbl[1] = %p\n", (*cast(void***) init)[1]); - printf("init[0] = %x\n", (cast(uint*) init)[0]); - printf("init[1] = %x\n", (cast(uint*) init)[1]); - printf("init[2] = %x\n", (cast(uint*) init)[2]); - printf("init[3] = %x\n", (cast(uint*) init)[3]); - printf("init[4] = %x\n", (cast(uint*) init)[4]); } // initialize it p[0 .. init.length] = init[]; - debug(PRINTF) printf("initialization done\n"); return cast(T) p; } diff --git a/src/urt/math.d b/src/urt/math.d index 3c66be8..e3b06b8 100644 --- a/src/urt/math.d +++ b/src/urt/math.d @@ -1,7 +1,6 @@ module urt.math; import urt.intrinsic; -import core.stdc.stdio; // For writeDebugf // for arch where using FPU for int<->float conversions is preferred //version = PreferFPUIntConv; diff --git a/src/urt/mem/package.d b/src/urt/mem/package.d index 795a4e3..9a903c8 100644 --- a/src/urt/mem/package.d +++ b/src/urt/mem/package.d @@ -11,19 +11,19 @@ extern(C) nothrow @nogc: void* alloca(size_t size); - void* memcpy(void* dest, const void* src, size_t n); - void* memmove(void* dest, const void* src, size_t n); - void* memset(void* s, int c, size_t n); - void* memzero(void* s, size_t n) => memset(s, 0, n); + void* memcpy(void* dest, const void* src, size_t n) pure; + void* memmove(void* dest, const void* src, size_t n) pure; + void* memset(void* s, int c, size_t n) pure; + void* memzero(void* s, size_t n) pure => memset(s, 0, n); - size_t strlen(const char* s); - int strcmp(const char* s1, const char* s2); - char* strcpy(char* dest, const char* src); + size_t strlen(const char* s) pure; + int strcmp(const char* s1, const char* s2) pure; + char* strcpy(char* dest, const char* src) pure; char* strcat(char* dest, const char* src); - size_t wcslen(const wchar_t* s); -// wchar_t* wcscpy(wchar_t* dest, const wchar_t* src); + size_t wcslen(const wchar_t* s) pure; +// wchar_t* wcscpy(wchar_t* dest, const wchar_t* src) pure; // wchar_t* wcscat(wchar_t* dest, const wchar_t* src); -// wchar_t* wcsncpy(wchar_t* dest, const wchar_t* src, size_t n); +// wchar_t* wcsncpy(wchar_t* dest, const wchar_t* src, size_t n) pure; // wchar_t* wcsncat(wchar_t* dest, const wchar_t* src, size_t n); } From e55eafde5225a2ff3769d978725948bdcf907c82 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 2 Dec 2025 23:05:34 +1000 Subject: [PATCH 038/138] Json string writer must escape strings --- src/urt/format/json.d | 74 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/src/urt/format/json.d b/src/urt/format/json.d index 01e977e..4b65529 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -104,15 +104,77 @@ ptrdiff_t write_json(ref const Variant val, char[] buffer, bool dense = false, u if (val.isString) { const char[] s = val.asString(); - if (buffer.ptr) + + if (!buffer.ptr) { - if (buffer.length < s.length + 2) + size_t len = 0; + foreach (c; s) + { + if (c < 0x20) + { + if (c == '\n' || c == '\r' || c == '\t' || c == '\b' || c == '\f') + len += 2; + else + len += 6; + } + else if (c == '"' || c == '\\') + len += 2; + else + len += 1; + } + return len + 2; + } + + if (buffer.length < s.length + 2) + return -1; + + buffer[0] = '"'; + // escape strings + size_t offset = 1; + foreach (c; s) + { + char sub = void; + if (c < 0x20) + { + if (c == '\n') + sub = 'n'; + else if (c == '\r') + sub = 'r'; + else if (c == '\t') + sub = 't'; + else if (c == '\b') + sub = 'b'; + else if (c == '\f') + sub = 'f'; + else + { + if (buffer.length < offset + 7) + return -1; + buffer[offset .. offset + 4] = "\\u00"; + offset += 4; + buffer[offset++] = hex_digits[c >> 4]; + buffer[offset++] = hex_digits[c & 0xF]; + continue; + } + } + else if (c == '"' || c == '\\') + sub = c; + else + { + if (buffer.length < offset + 2) + return -1; + buffer[offset++] = c; + continue; + } + + // write escape sequence + if (buffer.length < offset + 3) return -1; - buffer[0] = '"'; - buffer[1 .. 1 + s.length] = s[]; - buffer[1 + s.length] = '"'; + buffer[offset++] = '\\'; + buffer[offset++] = sub; } - return s.length + 2; + buffer[offset++] = '"'; + return offset; } else { From 4a6088087f4bf6dd8c23bf4798950697927e21cc Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 4 Dec 2025 18:14:53 +1000 Subject: [PATCH 039/138] Variant fixes --- src/urt/endian.d | 10 +++++----- src/urt/variant.d | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/urt/endian.d b/src/urt/endian.d index bb806d9..9c0033c 100644 --- a/src/urt/endian.d +++ b/src/urt/endian.d @@ -96,7 +96,7 @@ T endianToNative(T, bool little)(ref const ubyte[T.sizeof] bytes) static assert(!is(U == class) && !is(U == interface) && !is(U == V*, V), T.stringof ~ " is not POD"); static if (U.sizeof == 1) - r = *cast(T*)&bytes; + return *cast(T*)&bytes; else { T r; @@ -261,7 +261,7 @@ ubyte[T.sizeof] nativeToLittleEndian(T)(auto ref const T data) // load/store from/to memory void storeBigEndian(T)(T* target, const T val) - if (is_some_int!T || is(T == float)) + if (is_some_int!T || is(T == float) || is(T == double)) { version (BigEndian) *target = val; @@ -269,7 +269,7 @@ void storeBigEndian(T)(T* target, const T val) *target = byte_reverse(val); } void storeLittleEndian(T)(T* target, const T val) - if (is_some_int!T || is(T == float)) + if (is_some_int!T || is(T == float) || is(T == double)) { version (LittleEndian) *target = val; @@ -277,7 +277,7 @@ void storeLittleEndian(T)(T* target, const T val) *target = byte_reverse(val); } T loadBigEndian(T)(const(T)* src) - if (is_some_int!T || is(T == float)) + if (is_some_int!T || is(T == float) || is(T == double)) { version (BigEndian) return *src; @@ -285,7 +285,7 @@ T loadBigEndian(T)(const(T)* src) return byte_reverse(*src); } T loadLittleEndian(T)(const(T)* src) - if (is_some_int!T || is(T == float)) + if (is_some_int!T || is(T == float) || is(T == double)) { version (LittleEndian) return *src; diff --git a/src/urt/variant.d b/src/urt/variant.d index 81af559..6fbf197 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -344,11 +344,12 @@ nothrow @nogc: return nodeArray.pushBack(); } - bool opEquals(T)(ref const Variant rhs) const pure + bool opEquals(ref const Variant rhs) const pure { return opCmp(rhs) == 0; } bool opEquals(T)(auto ref const T rhs) const + if (!is(T == Variant)) { // TODO: handle short-cut array/map comparisons? static if (is(T == typeof(null))) @@ -536,6 +537,7 @@ nothrow @nogc: return invert ? -r : r; } int opCmp(T)(auto ref const T rhs) const + if (!is(T == Variant)) { // TODO: handle short-cut string, array, map comparisons static if (is(T == typeof(null))) @@ -854,7 +856,7 @@ nothrow @nogc: return asUlong(); else { - uint u = asInt(); + uint u = asUint(); static if (!is(T == uint)) assert(u <= T.max, "Value out of range for " ~ T.stringof); return cast(T)u; @@ -918,6 +920,16 @@ nothrow @nogc: return null; } + void set_unit(ScaledUnit unit) + { + assert(isNumber()); + count = unit.pack; + if (count != 0) + flags |= Flags.IsQuantity; + else + flags &= ~Flags.IsQuantity; + } + // TODO: this seems to interfere with UFCS a lot... // ref inout(Variant) opDispatch(string member)() inout pure // { From c978668e72b8889812de72d95866300d0c518f07 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 7 Dec 2025 12:01:31 +1000 Subject: [PATCH 040/138] Added a function to build a runtime EnumInfo. Substantially compressed the data by: - aggregating buffers - storing keys as a 16-bit index into a single string buffer - removed a bunch of redundant length fields The runtime EnumInfo is a single allocation. --- src/urt/meta/package.d | 309 +++++++++++++++++++++++++++++++---------- 1 file changed, 232 insertions(+), 77 deletions(-) diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index 91ca9e6..0a17ea3 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -2,7 +2,7 @@ module urt.meta; import urt.traits : is_callable, is_enum, EnumType, Unqual; -pure nothrow @nogc: +nothrow @nogc: alias Alias(alias a) = a; alias Alias(T) = T; @@ -18,7 +18,7 @@ template Iota(ptrdiff_t start, ptrdiff_t end) } alias Iota(size_t end) = Iota!(0, end); -auto make_delegate(Fun)(Fun fun) +auto make_delegate(Fun)(Fun fun) pure if (is_callable!Fun) { import urt.traits : is_function_pointer, ReturnType, Parameters; @@ -55,7 +55,7 @@ auto make_delegate(Fun)(Fun fun) static assert(false, "Unsupported type for make_delegate"); } -ulong bit_mask(size_t bits) +ulong bit_mask(size_t bits) pure { return (1UL << bits) - 1; } @@ -130,17 +130,17 @@ template INTERLEAVE_SEPARATOR(alias sep, Args...) } -const(E)* enum_from_key(E)(const(char)[] key) +const(E)* enum_from_key(E)(const(char)[] key) pure if (is_enum!E) - => MakeEnumInfo!E.value_for(key); + => enum_info!E.value_for(key); -const(char)[] enum_key_from_value(E)(EnumType!E value) +const(char)[] enum_key_from_value(E)(EnumType!E value) pure if (is_enum!E) - => MakeEnumInfo!E.key_for(value); + => enum_info!E.key_for(value); -const(char)[] enum_key_by_decl_index(E)(size_t value) +const(char)[] enum_key_by_decl_index(E)(size_t value) pure if (is_enum!E) - => MakeEnumInfo!E.key_by_decl_index(value); + => enum_info!E.key_by_decl_index(value); struct VoidEnumInfo { @@ -149,54 +149,71 @@ struct VoidEnumInfo nothrow @nogc: // keys and values are sorted for binary search - const String* keys; - const void* values; - ubyte count; + ushort count; ushort stride; uint type_hash; const(char)[] key_for(const void* value, int function(const void* a, const void* b) pure nothrow @nogc pred) const pure { - size_t i = binary_search(values[0 .. count*stride], stride, value, pred); + size_t i = binary_search(_values[0 .. count*stride], stride, value, pred); if (i < count) - return keys[_v2k_lookup[i]][]; + return get_key(_lookup_tables[count + i]); return null; } const(char)[] key_for(const void* value, int delegate(const void* a, const void* b) pure nothrow @nogc pred) const pure { - size_t i = binary_search(values[0 .. count*stride], stride, value, pred); + size_t i = binary_search(_values[0 .. count*stride], stride, value, pred); if (i < count) - return keys[_v2k_lookup[i]][]; + return get_key(_lookup_tables[count + i]); return null; } const(char)[] key_by_decl_index(size_t i) const pure { assert(i < count, "Declaration index out of range"); - return keys[_decl_lookup[i]][]; + return get_key(_lookup_tables[count*2 + i]); } const(void)* value_for(const(char)[] key) const pure { - size_t i = binary_search(keys[0 .. count], key); + size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); if (i == count) return null; - i = _k2v_lookup[i]; - return values + i*stride; + i = _lookup_tables[i]; + return _values + i*stride; } bool contains(const(char)[] key) const pure { - size_t i = binary_search(keys[0 .. count], key); + size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); return i < count; } private: + const void* _values; + const ushort* _keys; + const char* _string_buffer; + // these tables map between indices of keys and values - const ubyte* _k2v_lookup; - const ubyte* _v2k_lookup; - const ubyte* _decl_lookup; + const ubyte* _lookup_tables; + + this(ubyte count, ushort stride, uint type_hash, inout void* values, inout ushort* keys, inout char* strings, inout ubyte* lookup) inout pure + { + this.count = count; + this.stride = stride; + this.type_hash = type_hash; + this._keys = keys; + this._values = values; + this._string_buffer = strings; + this._lookup_tables = lookup; + } + + const(char)[] get_key(size_t i) const pure + { + const(char)* s = _string_buffer + _keys[i]; + return s[0 .. s.key_length]; + } } template EnumInfo(E) @@ -210,7 +227,6 @@ template EnumInfo(E) struct EnumInfo { import urt.algorithm : binary_search; - import urt.string; nothrow @nogc: static assert (EnumInfo.sizeof == EnumInfo.sizeof, "Template EnumInfo must not add any members!"); @@ -221,90 +237,117 @@ template EnumInfo(E) static assert(false, E.string ~ " is not an enum type!"); // keys and values are sorted for binary search - const String* keys; - const UE* values; - const ubyte count = __traits(allMembers, E).length; - const ushort stride = E.sizeof; - uint type_hash; + union { + VoidEnumInfo _base; + const UE* _values; // shadows the _values in _base with a typed version + } + alias _base this; + + inout(VoidEnumInfo*) make_void() inout pure + => &_base; - ref inout(VoidEnumInfo) make_void() inout - => *cast(inout VoidEnumInfo*)&this; + this(ubyte count, uint type_hash, inout UE* values, inout ushort* keys, inout char* strings, inout ubyte* lookup) inout pure + { + _base = inout(VoidEnumInfo)(count, UE.sizeof, type_hash, values, keys, strings, lookup); + } + + const(UE)[] values() const pure + => _values[0 .. count]; const(char)[] key_for(V value) const pure { size_t i = binary_search(values[0 .. count], value); if (i < count) - return keys[_v2k_lookup[i]][]; + return get_key(_lookup_tables[count + i]); return null; } const(char)[] key_by_decl_index(size_t i) const pure - => make_void().key_by_decl_index(i); + => _base.key_by_decl_index(i); const(UE)* value_for(const(char)[] key) const pure { - size_t i = binary_search(keys[0 .. count], key); + size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); if (i == count) return null; - return values + _k2v_lookup[i]; + return _values + _lookup_tables[i]; } bool contains(const(char)[] key) const pure - => make_void().contains(key); - - private: - // these tables map between indices of keys and values - const ubyte* _k2v_lookup; - const ubyte* _v2k_lookup; - const ubyte* _decl_lookup; + => _base.contains(key); } - - // sanity check the typed one matches the untyped one - static assert(EnumInfo.sizeof == VoidEnumInfo.sizeof); - static assert(EnumInfo.keys.offsetof == VoidEnumInfo.keys.offsetof); - static assert(EnumInfo.values.offsetof == VoidEnumInfo.values.offsetof); - static assert(EnumInfo.count.offsetof == VoidEnumInfo.count.offsetof); - static assert(EnumInfo.stride.offsetof == VoidEnumInfo.stride.offsetof); - static assert(EnumInfo.type_hash.offsetof == VoidEnumInfo.type_hash.offsetof); - static assert(EnumInfo._k2v_lookup.offsetof == VoidEnumInfo._k2v_lookup.offsetof); - static assert(EnumInfo._v2k_lookup.offsetof == VoidEnumInfo._v2k_lookup.offsetof); - static assert(EnumInfo._decl_lookup.offsetof == VoidEnumInfo._decl_lookup.offsetof); } } - -template MakeEnumInfo(E) +template enum_info(E) if (is(Unqual!E == enum)) { alias UE = Unqual!E; - enum ubyte NumItems = __traits(allMembers, E).length; - static assert(NumItems <= ubyte.max, "Too many enum items!"); + enum ubyte num_items = enum_members.length; + static assert(num_items <= ubyte.max, "Too many enum items!"); - __gshared immutable MakeEnumInfo = immutable(EnumInfo!UE)( - _keys.ptr, + __gshared immutable enum_info = immutable(EnumInfo!UE)( + num_items, + fnv1a(cast(ubyte[])UE.stringof), _values.ptr, - NumItems, - E.sizeof, - 0, - _lookup.ptr, - _lookup.ptr + NumItems, - _lookup.ptr + NumItems*2 + _keys.ptr, + _strings.ptr, + _lookup.ptr ); private: import urt.algorithm : binary_search, compare, qsort; - import urt.string; + import urt.hash : fnv1a; import urt.string.uni : uni_compare; // keys and values are sorted for binary search - __gshared immutable String[NumItems] _keys = [ STATIC_MAP!(GetKey, iota) ]; - __gshared immutable UE[NumItems] _values = [ STATIC_MAP!(GetValue, iota) ]; + __gshared immutable UE[num_items] _values = [ STATIC_MAP!(GetValue, iota) ]; + + // keys are stored as offsets info the string buffer + __gshared immutable ushort[num_items] _keys = () { + ushort[num_items] key_offsets; + size_t offset = 2; + foreach (i; 0 .. num_items) + { + const(char)[] key = by_key[i].k; + key_offsets[i] = cast(ushort)offset; + offset += 2 + key.length; + if (key.length & 1) + offset += 1; // align to 2 bytes + } + return key_offsets; + }(); + + // build the string buffer + __gshared immutable char[total_strings] _strings = () { + char[total_strings] str_data; + char* ptr = str_data.ptr; + foreach (i; 0 .. num_items) + { + const(char)[] key = by_key[i].k; + version (LittleEndian) + { + *ptr++ = key.length & 0xFF; + *ptr++ = (key.length >> 8) & 0xFF; + } + else + { + *ptr++ = (key.length >> 8) & 0xFF; + *ptr++ = key.length & 0xFF; + } + ptr[0 .. key.length] = key[]; + ptr += key.length; + if (key.length & 1) + *ptr++ = 0; // align to 2 bytes + } + return str_data; + }(); // these tables map between indices of keys and values - __gshared immutable ubyte[NumItems * 3] _lookup = [ STATIC_MAP!(GetKeyRedirect, iota), - STATIC_MAP!(GetValRedirect, iota), - STATIC_MAP!(GetKeyOrig, iota) ]; + __gshared immutable ubyte[num_items * 3] _lookup = [ STATIC_MAP!(GetKeyRedirect, iota), + STATIC_MAP!(GetValRedirect, iota), + STATIC_MAP!(GetKeyOrig, iota) ]; // a whole bunch of nonsense to build the tables... struct KI @@ -320,20 +363,112 @@ private: alias iota = Iota!(enum_members.length); enum enum_members = __traits(allMembers, E); - enum by_key = (){ KI[NumItems] r = [ STATIC_MAP!(MakeKI, iota) ]; r.qsort!((ref a, ref b) => uni_compare(a.k, b.k)); return r; }(); - enum by_value = (){ VI[NumItems] r = [ STATIC_MAP!(MakeVI, iota) ]; r.qsort!((ref a, ref b) => compare(a.v, b.v)); return r; }(); - enum inv_key = (){ KI[NumItems] bk = by_key; ubyte[NumItems] r; foreach (ubyte i, ref ki; bk) r[ki.i] = i; return r; }(); - enum inv_val = (){ VI[NumItems] bv = by_value; ubyte[NumItems] r; foreach (ubyte i, ref vi; bv) r[vi.i] = i; return r; }(); + enum by_key = (){ KI[num_items] r = [ STATIC_MAP!(MakeKI, iota) ]; r.qsort!((ref a, ref b) => uni_compare(a.k, b.k)); return r; }(); + enum by_value = (){ VI[num_items] r = [ STATIC_MAP!(MakeVI, iota) ]; r.qsort!((ref a, ref b) => compare(a.v, b.v)); return r; }(); + enum inv_key = (){ KI[num_items] bk = by_key; ubyte[num_items] r; foreach (ubyte i, ref ki; bk) r[ki.i] = i; return r; }(); + enum inv_val = (){ VI[num_items] bv = by_value; ubyte[num_items] r; foreach (ubyte i, ref vi; bv) r[vi.i] = i; return r; }(); + + // calculate the total size of the string buffer + enum total_strings = () { + size_t total = 0; + static foreach (k; enum_members) + total += 2 + k.length + (k.length & 1); + return total; + }(); enum MakeKI(ushort i) = KI(enum_members[i], i); enum MakeVI(ushort i) = VI(__traits(getMember, E, enum_members[i]), i); - enum GetKey(size_t i) = StringLit!(by_key[i].k); enum GetValue(size_t i) = by_value[i].v; enum GetKeyRedirect(size_t i) = inv_val[by_key[i].i]; enum GetValRedirect(size_t i) = inv_key[by_value[i].i]; enum GetKeyOrig(size_t i) = inv_key[i]; } +VoidEnumInfo* make_enum_info(T)(const(char)[] name, const(char)[][] keys, T[] values) +{ + import urt.algorithm; + import urt.hash : fnv1a; + import urt.mem.allocator; + import urt.string; + import urt.string.uni; + import urt.util; + + assert(keys.length == values.length, "keys and values must have the same length"); + assert(keys.length <= ubyte.max, "Too many enum items!"); + + size_t count = keys.length; + + struct VI(T) + { + T v; + ubyte i; + } + + // first we'll sort the keys and values for binary searching + // we need to associate their original indices for the lookup tables + auto ksort = tempAllocator().allocArray!(VI!(const(char)[]))(count); + auto vsort = tempAllocator().allocArray!(VI!T)(count); + foreach (i; 0 .. count) + { + ksort[i] = VI!(const(char)[])(keys[i], cast(ubyte)i); + vsort[i] = VI!T(values[i], cast(ubyte)i); + } + ksort.qsort!((ref a, ref b) => uni_compare(a.v, b.v)); + vsort.qsort!((ref a, ref b) => compare(a.v, b.v)); + + // build the reverse lookup tables + ubyte[] inv_k = tempAllocator().allocArray!ubyte(count); + ubyte[] inv_v = tempAllocator().allocArray!ubyte(count); + foreach (i, ref ki; ksort) + inv_k[ki.i] = cast(ubyte)i; + foreach (i, ref vi; vsort) + inv_v[vi.i] = cast(ubyte)i; + + // count the string memory + size_t total_string; + foreach (i; 0 .. count) + total_string += 2 + keys[i].length + (keys[i].length & 1); + + // calculate the total size + size_t total_size = VoidEnumInfo.sizeof + T.sizeof*count; + total_size += (total_size & 1) + ushort.sizeof*count + count*3; + total_size += (total_size & 1) + total_string; + + // allocate a buffer and assign all the sub-buffers + void[] info = defaultAllocator().alloc(total_size); + VoidEnumInfo* result = cast(VoidEnumInfo*)info.ptr; + T* value_ptr = cast(T*)&result[1]; + char* str_data = cast(char*)&value_ptr[count]; + if (cast(size_t)str_data & 1) + *str_data++ = 0; // align to 2 bytes + ushort* key_ptr = cast(ushort*)str_data; + ubyte* lookup = cast(ubyte*)&key_ptr[count]; + str_data = cast(char*)&lookup[count*3]; + if (cast(size_t)str_data & 1) + *str_data++ = 0; // align to 2 bytes + char* str_ptr = str_data + 2; + + // populate the enum info data + foreach (i; 0 .. count) + { + value_ptr[i] = vsort[i].v; + + // write the string data and store the key offset + const(char)[] key = ksort[i].v; + key_ptr[i] = cast(ushort)(str_ptr - str_data); + writeString(str_ptr, key); + if (key.length & 1) + (str_ptr++)[key.length] = 0; // align to 2 bytes + str_ptr += 2 + key.length; + + lookup[i] = inv_v[ksort[i].i]; + lookup[count + i] = inv_k[vsort[i].i]; + lookup[count*2 + i] = inv_k[i]; + } + + // build and return the object + return new(*result) VoidEnumInfo(cast(ubyte)keys.length, cast(ushort)T.sizeof, fnv1a(cast(ubyte[])name), value_ptr, key_ptr, str_data, lookup); +} private: @@ -350,3 +485,23 @@ template is_same(A, B) { enum is_same = is(A == B); } + +ushort key_length(const(char)* key) pure +{ + if (__ctfe) + { + version (LittleEndian) + return key[-2] | cast(ushort)(key[-1] << 8); + else + return key[-1] | cast(ushort)(key[-2] << 8); + } + else + return *cast(ushort*)(key - 2); +} + +int key_compare(ushort a, const(char)[] b, const(char)* strings) pure +{ + import urt.string.uni : uni_compare; + const(char)* s = strings + a; + return uni_compare(s[0 .. s.key_length], b); +} From d3a470dd8905b00d5a341afbbc90422dc0b0db14 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 7 Dec 2025 12:03:28 +1000 Subject: [PATCH 041/138] Added a helper for building string caches. - make sure it's CTFE-able. --- src/urt/mem/string.d | 5 --- src/urt/string/string.d | 89 +++++++++++++++++++++++++++++++++---- src/urt/string/tailstring.d | 6 +-- 3 files changed, 84 insertions(+), 16 deletions(-) diff --git a/src/urt/mem/string.d b/src/urt/mem/string.d index 1cefa4e..a3adc22 100644 --- a/src/urt/mem/string.d +++ b/src/urt/mem/string.d @@ -11,11 +11,6 @@ shared static this() } -struct StringCache -{ -} - - struct CacheString { nothrow @nogc: diff --git a/src/urt/string/string.d b/src/urt/string/string.d index 4fa8cdb..843c7fd 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -32,6 +32,47 @@ struct StringAllocator void delegate(char* s) nothrow @nogc free; } +struct StringCacheBuilder +{ +nothrow @nogc: + this(char[] buffer) pure + { + assert(buffer.length <= ushort.max, "Buffer too long"); + this._buffer = buffer; + this._offset = 0; + } + + ushort add_string(const(char)[] s) pure + { + assert(s.length <= MaxStringLen, "String too long"); + if (__ctfe) + { + version (LittleEndian) + { + _buffer[_offset + 0] = cast(char)(s.length & 0xFF); + _buffer[_offset + 1] = cast(char)(s.length >> 8); + } + else + { + _buffer[_offset + 0] = cast(char)(s.length >> 8); + _buffer[_offset + 1] = cast(char)(s.length & 0xFF); + } + } + else + *cast(ushort*)(_buffer.ptr + _offset) = cast(ushort)s.length; + + ushort result = cast(ushort)(_offset + 2); + _buffer[result .. result + s.length] = s[]; + _offset = cast(ushort)(result + s.length); + if (_offset & 1) + _buffer[_offset++] = '\0'; + return result; + } + +private: + char[] _buffer; + ushort _offset; +} //enum String StringLit(string s) = s.makeString; template StringLit(const(char)[] lit, bool zeroTerminate = true) @@ -116,6 +157,46 @@ String makeString(const(char)[] s, char[] buffer) nothrow @nogc return String(writeString(buffer.ptr + 2, s), false); } +char* writeString(char* buffer, const(char)[] str) pure nothrow @nogc +{ + // TODO: assume the calling code has confirmed the length is within spec + if (__ctfe) + { + version (LittleEndian) + { + buffer[-2] = cast(char)(str.length & 0xFF); + buffer[-1] = cast(char)(str.length >> 8); + } + else + { + buffer[-2] = cast(char)(str.length >> 8); + buffer[-1] = cast(char)(str.length & 0xFF); + } + } + else + (cast(ushort*)buffer)[-1] = cast(ushort)str.length; + buffer[0 .. str.length] = str[]; + return buffer; +} + +String as_string(const(char)* s) nothrow @nogc + => String(s, false); + +inout(char)[] as_dstring(inout(char)* s) pure nothrow @nogc +{ + debug assert(s !is null); + + if (__ctfe) + { + version (LittleEndian) + ushort len = cast(ushort)(s[-2] | (s[-1] << 8)); + else + ushort len = cast(ushort)(s[-1] | (s[-2] << 8)); + return s[0 .. len]; + } + else + return s[0 .. (cast(ushort*)s)[-1]]; +} struct String { @@ -941,14 +1022,6 @@ private: __gshared StringAllocator[4] stringAllocators; static assert(stringAllocators.length <= 4, "Only 2 bits reserved to store allocator index"); -char* writeString(char* buffer, const(char)[] str) pure nothrow @nogc -{ - // TODO: assume the calling code has confirmed the length is within spec - (cast(ushort*)buffer)[-1] = cast(ushort)str.length; - buffer[0 .. str.length] = str[]; - return buffer; -} - package(urt) void initStringAllocators() { stringAllocators[StringAlloc.Default].alloc = (ushort bytes, void* userData) { diff --git a/src/urt/string/tailstring.d b/src/urt/string/tailstring.d index c9d8751..0c7b77d 100644 --- a/src/urt/string/tailstring.d +++ b/src/urt/string/tailstring.d @@ -23,7 +23,7 @@ struct TailString(T) else { const(char)* thisptr = cast(const(char)*)&this; - const(char)* sptr = s.ptr - (s.ptr[-1] < 128 ? 1 : 2); + const(char)* sptr = s.ptr - 2; assert(sptr > thisptr && sptr - thisptr <= offset.max, "!!"); offset = cast(ubyte)(sptr - thisptr); } @@ -39,7 +39,7 @@ struct TailString(T) if (offset == 0) return null; const(char)* ptr = (cast(const(char)*)&this) + offset; - return ptr[0] < 128 ? ptr + 1 : ptr + 2; + return ptr + 2; } size_t length() const nothrow @nogc @@ -64,7 +64,7 @@ struct TailString(T) else { const(char)* thisptr = cast(const(char)*)&this; - const(char)* sptr = s.ptr - (s.ptr[-1] < 128 ? 1 : 2); + const(char)* sptr = s.ptr - 2; assert(sptr > thisptr && sptr - thisptr <= offset.max, "!!"); offset = cast(ubyte)(sptr - thisptr); } From 7025ea75bf963b0989484ef7cafe95d9e056437c Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 7 Dec 2025 12:03:58 +1000 Subject: [PATCH 042/138] Fix qsort... apparently I buggered it up! --- src/urt/algorithm.d | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index c070380..9995cfb 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -232,30 +232,32 @@ version (SmallSize) { void* p = arr.ptr; size_t length = arr.length / element_size; - if (length > 1) - { - size_t pivotIndex = length / 2; - void* pivot = p + pivotIndex*element_size; + if (length <= 1) + return; - size_t i = 0; - size_t j = length - 1; + size_t pivot_index = length / 2; + size_t last = length - 1; + swap(p + pivot_index*element_size, p + last*element_size); - while (i <= j) + void* pivot = p + last*element_size; + + size_t partition = 0; + for (size_t k = 0; k < last; ++k) + { + void* elem = p + k*element_size; + if (compare(elem, pivot) < 0) { - while (compare(p + i*element_size, pivot) < 0) i++; - while (compare(p + j*element_size, pivot) > 0) j--; - if (i <= j) - { - swap(p + i*element_size, p + j*element_size); - i++; - j--; - } + if (k != partition) + swap(elem, p + partition*element_size); + ++partition; } - - if (j > 0) - qsort(p[0 .. (j + 1)*element_size], element_size, compare, swap); - if (i < length) - qsort(p[i*element_size .. length*element_size], element_size, compare, swap); } + + swap(p + partition*element_size, p + last*element_size); + + if (partition > 1) + qsort(p[0 .. partition*element_size], element_size, compare, swap); + if (partition + 1 < length) + qsort(p[(partition + 1)*element_size .. length*element_size], element_size, compare, swap); } } From 0b3c1109ab6948f25e86a8328e49e689f02d8557 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 7 Dec 2025 12:54:51 +1000 Subject: [PATCH 043/138] Added a signed parse_int_with_base --- src/urt/conv.d | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/urt/conv.d b/src/urt/conv.d index dbbd3f9..8889082 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -132,11 +132,23 @@ done: return value; } +long parse_int_with_base(const(char)[] str, size_t* bytes_taken = null) pure +{ + const(char)* p = str.ptr; + int base = str.parse_base_prefix(); + if (base == 10) + return str.parse_int(bytes_taken); + ulong i = str.parse_uint(bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += str.ptr - p; + return i; +} + ulong parse_uint_with_base(const(char)[] str, size_t* bytes_taken = null) pure { const(char)* p = str.ptr; - int base = parse_base_prefix(str); - ulong i = parse_uint(str, bytes_taken, base); + int base = str.parse_base_prefix(); + ulong i = str.parse_uint(bytes_taken, base); if (bytes_taken && *bytes_taken != 0) *bytes_taken += str.ptr - p; return i; From 44060913a7449a4f288985830423255a89413f60 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 7 Dec 2025 13:08:17 +1000 Subject: [PATCH 044/138] Added a template to make a ScaledUnit from a string. --- src/urt/si/unit.d | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index 1b98a6a..1ebf7a0 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -32,6 +32,9 @@ nothrow @nogc: // +enum ScaledUnit unit(const(char)[] desc) = () { ScaledUnit r; float f; ptrdiff_t e = r.parseUnit(desc, f); assert(e > 0, "Invalid unit"); assert(f == 1, "Unit requires pre-scale"); return r; }(); + + // base units enum Metre = Unit(UnitType.Length); enum Kilogram = Unit(UnitType.Mass); From 38f75e3d4380d93cd81d51487ef266df55b6d9e4 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 7 Dec 2025 12:55:10 +1000 Subject: [PATCH 045/138] Unroll array --- src/urt/meta/package.d | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index 91ca9e6..a28f17a 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -99,6 +99,18 @@ template STATIC_MAP(alias fun, args...) STATIC_MAP = AliasSeq!(STATIC_MAP, fun!arg); } +template STATIC_UNROLL(alias array) +{ + static if (is(typeof(array) : T[], T)) + { + alias STATIC_UNROLL = AliasSeq!(); + static foreach (i; 0 .. array.length) + STATIC_UNROLL = AliasSeq!(STATIC_UNROLL, array[i]); + } + else + static assert(false, "STATIC_UNROLL requires an array"); +} + template STATIC_FILTER(alias filter, args...) { alias STATIC_FILTER = AliasSeq!(); From 74fc4f11e6e85345c42e019803095f8214cc6b93 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 8 Dec 2025 12:57:56 +1000 Subject: [PATCH 046/138] Fixed an assortment of small bugs. --- src/urt/endian.d | 4 ++-- src/urt/si/quantity.d | 2 +- src/urt/time.d | 24 ++++++++++++++++++++---- src/urt/traits.d | 2 ++ src/urt/variant.d | 20 ++++++++++---------- 5 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/urt/endian.d b/src/urt/endian.d index 9c0033c..a48f872 100644 --- a/src/urt/endian.d +++ b/src/urt/endian.d @@ -100,8 +100,8 @@ T endianToNative(T, bool little)(ref const ubyte[T.sizeof] bytes) else { T r; - for (size_t i = 0, j = 0; i < N; ++i, j += T.sizeof) - r[i] = endianToNative!(U, little)(bytes.ptr[j .. j + T.sizeof][0 .. T.sizeof]); + for (size_t i = 0, j = 0; i < N; ++i, j += U.sizeof) + r[i] = endianToNative!(U, little)(bytes.ptr[j .. j + U.sizeof][0 .. U.sizeof]); return r; } } diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index 880166a..df9775f 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -72,7 +72,7 @@ nothrow @nogc: value = adjustScale(b); } } - + void opAssign()(T value) pure { static if (Dynamic) diff --git a/src/urt/time.d b/src/urt/time.d index 7972a1e..2b3036d 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -391,6 +391,23 @@ pure nothrow @nogc: this = mixin("this " ~ op ~ " rhs;"); } + int opCmp(DateTime dt) const + { + int r = year - dt.year; + if (r != 0) return r; + r = month - dt.month; + if (r != 0) return r; + r = day - dt.day; + if (r != 0) return r; + r = hour - dt.hour; + if (r != 0) return r; + r = minute - dt.minute; + if (r != 0) return r; + r = second - dt.second; + if (r != 0) return r; + return ns - dt.ns; + } + import urt.string.format : FormatArg; ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const { @@ -416,11 +433,10 @@ pure nothrow @nogc: return len; } - size_t offset = 0; - len = year.format_int(buffer[offset..$]); - if (len < 0 || len == buffer.length) + len = year.format_int(buffer[]); + if (len < 0 || len + 15 > buffer.length) return -1; - offset += len; + size_t offset = len; buffer[offset++] = '-'; buffer[offset++] = '0' + (month / 10); buffer[offset++] = '0' + (month % 10); diff --git a/src/urt/traits.d b/src/urt/traits.d index fb124d9..6f8a761 100644 --- a/src/urt/traits.d +++ b/src/urt/traits.d @@ -222,6 +222,8 @@ enum is_primitive(T) = is_integral!T || is_some_float!T || (is_enum!T && is_prim is(T == P*, P) || is(T == S[], S) || (is(T == A[N], A, size_t N) && is_primitive!A) || is(T == R function(Args), R, Args...) || is(T == R delegate(Args), R, Args...)); +enum is_trivial(T) = __traits(isPOD, T); + enum is_default_constructible(T) = is_primitive!T || (is(T == struct) && __traits(compiles, { T t; })); enum is_constructible(T, Args...) = (is_primitive!T && (Args.length == 0 || (Args.length == 1 && is(Args[0] : T)))) || diff --git a/src/urt/variant.d b/src/urt/variant.d index 6fbf197..d6aebdd 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -1018,9 +1018,9 @@ nothrow @nogc: case Variant.Type.User: if (flags & Flags.Embedded) - return find_type_details(alloc).stringify(cast(void*)embed.ptr, buffer, true); + return find_type_details(alloc).stringify(cast(void*)embed.ptr, buffer, true, format, formatArgs); else - return g_type_details[alloc].stringify(cast(void*)ptr, buffer, true); + return g_type_details[alloc].stringify(cast(void*)ptr, buffer, true, format, formatArgs); } } @@ -1091,7 +1091,7 @@ nothrow @nogc: { ref immutable TypeDetails td = get_type_details(i); debug assert(td.alignment <= 64 && td.size <= buffer.sizeof, "Buffer is too small for user type!"); - ptrdiff_t taken = td.stringify(td.embedded ? embed.ptr : buffer.ptr, cast(char[])s, false); + ptrdiff_t taken = td.stringify(td.embedded ? embed.ptr : buffer.ptr, cast(char[])s, false, null, null); if (taken > 0) { flags = Flags.User; @@ -1283,6 +1283,7 @@ unittest private: import urt.hash : fnv1a; +import urt.string.format : formatValue, FormatArg; static assert(Variant.sizeof == 16); static assert(Variant.Type.max <= Variant.Flags.TypeMask); @@ -1342,7 +1343,7 @@ struct TypeDetails bool embedded; void function(void* src, void* dst, bool move) nothrow @nogc copy_emplace; void function(void* val) nothrow @nogc destroy; - ptrdiff_t function(void* val, char[] buffer, bool format) nothrow @nogc stringify; + ptrdiff_t function(void* val, char[] buffer, bool do_format, const(char)[] format_spec, const(FormatArg)[] format_args) nothrow @nogc stringify; int function(const void* a, const void* b, int type) pure nothrow @nogc cmp; } __gshared TypeDetails[8] g_type_details; @@ -1405,7 +1406,7 @@ public template TypeDetailsFor(T) else enum move_emplace = null; - static if (!is(T == class) && is(typeof(destroy!(false, T)))) + static if (!is_trivial!T && is(typeof(destroy!(false, T)))) { static void destroy_impl(void* val) nothrow @nogc { @@ -1416,13 +1417,12 @@ public template TypeDetailsFor(T) else enum destroy_fun = null; - static ptrdiff_t stringify(void* val, char[] buffer, bool format) nothrow @nogc + static ptrdiff_t stringify(void* val, char[] buffer, bool do_format, const(char)[] format_spec, const(FormatArg)[] format_args) nothrow @nogc { - import urt.string.format : toString; - if (format) + if (do_format) { - static if (is(typeof(toString!T))) - return toString(*cast(const T*)val, buffer); + static if (__traits(compiles, { formatValue(*cast(const T*)val, buffer, format_spec, format_args); })) + return formatValue(*cast(const T*)val, buffer, format_spec, format_args); else return -1; } From bb72172fc6ed3c1cfed2403fb912aec9a81d18a2 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 10 Dec 2025 15:19:14 +1000 Subject: [PATCH 047/138] Fix handling of negative times. --- src/urt/time.d | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/urt/time.d b/src/urt/time.d index 2b3036d..20857dc 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -796,23 +796,24 @@ ptrdiff_t timeToString(long ns, char[] buffer) pure { import urt.conv : format_int; - long hr = ns / 3_600_000_000_000; + int hr = cast(int)(ns / 3_600_000_000_000); + ns = ns < 0 ? -ns % 3_600_000_000_000 : ns % 3_600_000_000_000; + uint remainder = cast(uint)(ns % 1_000_000_000); if (!buffer.ptr) { size_t tail = 6; - ns %= 1_000_000_000; - if (ns) + if (remainder) { ++tail; uint m = 0; do { ++tail; - uint digit = cast(uint)(ns / digit_multipliers[m]); - ns -= digit * digit_multipliers[m++]; + uint digit = cast(uint)(remainder / digit_multipliers[m]); + remainder -= digit * digit_multipliers[m++]; } - while (ns); + while (remainder); } return hr.format_int(null, 10, 2, '0') + tail; } @@ -821,9 +822,9 @@ ptrdiff_t timeToString(long ns, char[] buffer) pure if (len < 0 || buffer.length < len + 6) return -1; - ubyte min = cast(ubyte)(ns / 60_000_000_000 % 60); - ubyte sec = cast(ubyte)(ns / 1_000_000_000 % 60); - ns %= 1_000_000_000; + uint min_sec = cast(uint)(ns / 1_000_000_000); + uint min = min_sec / 60; + uint sec = min_sec % 60; buffer.ptr[len++] = ':'; buffer.ptr[len++] = cast(char)('0' + (min / 10)); @@ -831,19 +832,20 @@ ptrdiff_t timeToString(long ns, char[] buffer) pure buffer.ptr[len++] = ':'; buffer.ptr[len++] = cast(char)('0' + (sec / 10)); buffer.ptr[len++] = cast(char)('0' + (sec % 10)); - if (ns) + if (remainder) { if (buffer.length < len + 2) return -1; buffer.ptr[len++] = '.'; - uint m = 0; - while (ns) + uint i = 0; + while (remainder) { - if (buffer.length < len + 1) + if (buffer.length <= len) return -1; - uint digit = cast(uint)(ns / digit_multipliers[m]); + uint m = digit_multipliers[i++]; + uint digit = cast(uint)(remainder / m); buffer.ptr[len++] = cast(char)('0' + digit); - ns -= digit * digit_multipliers[m++]; + remainder -= digit * m; } } return len; @@ -855,6 +857,7 @@ unittest assert(tconcat(msecs(3_600_000*3 + 60_000*47 + 1000*34 + 123))[] == "03:47:34.123"); assert(tconcat(msecs(3_600_000*-123))[] == "-123:00:00"); + assert(tconcat(usecs(3_600_000_000*-123 + 1))[] == "-122:59:59.999999"); assert(MonoTime().toString(null, null, null) == 10); assert(tconcat(getTime())[0..2] == "T+"); From 5b90efcd9d6babfee8f2f772a82d72fc443bcc13 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 12 Dec 2025 16:31:47 +1000 Subject: [PATCH 048/138] Fix EnumInfo typed values array offset. --- src/urt/meta/package.d | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index 0d25eea..bb6e8d9 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -251,7 +251,10 @@ template EnumInfo(E) // keys and values are sorted for binary search union { VoidEnumInfo _base; - const UE* _values; // shadows the _values in _base with a typed version + struct { + ubyte[VoidEnumInfo._values.offsetof] _pad; + const UE* _values; // shadows the _values in _base with a typed version + } } alias _base this; From b7ca9d6c9670a04c4a6f08d6ab581a9023c623f9 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 12 Dec 2025 19:03:33 +1000 Subject: [PATCH 049/138] Fixed a bug resuming from a fibre multiple times. --- src/urt/async.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/urt/async.d b/src/urt/async.d index 81e0a19..557f24a 100644 --- a/src/urt/async.d +++ b/src/urt/async.d @@ -79,7 +79,7 @@ void asyncUpdate() if (!t.event.ready()) continue; } - t.resume(); + t.call.fibre.resume(); } } From a7cc818c5b20148dda194ec97d69ddcc27c1ed65 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 14 Dec 2025 14:02:44 +1000 Subject: [PATCH 050/138] Add toString for Arrays and Maps. - Made it a template; so the code is never generated unless it's called (this should probably be standard practise!) --- src/urt/array.d | 14 +++++++++ src/urt/map.d | 63 +++++++++++++++++++++++++++++++++++++++++ src/urt/string/format.d | 34 ++++++++++++++-------- 3 files changed, 99 insertions(+), 12 deletions(-) diff --git a/src/urt/array.d b/src/urt/array.d index 702c861..6674151 100644 --- a/src/urt/array.d +++ b/src/urt/array.d @@ -685,6 +685,20 @@ nothrow @nogc: _length = 0; } + import urt.string.format : FormatArg, formatValue; + ptrdiff_t toString()(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const + { + static if (is(T == class) || is(T == interface)) + assert(false, "TODO: class toString is not @nogc!"); + else + return formatValue(ptr[0 .. _length], buffer, format, formatArgs); + } + + ptrdiff_t fromString()(const(char)[] s) + { + assert(false, "TODO"); + } + private: T* ptr; uint _length; diff --git a/src/urt/map.d b/src/urt/map.d index 97a6f99..3a624ac 100644 --- a/src/urt/map.d +++ b/src/urt/map.d @@ -300,6 +300,69 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) auto opIndex() const nothrow => Range!(IterateBy.KVP, true)(pRoot); + import urt.string.format : FormatArg, formatValue; + ptrdiff_t toString()(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const + { + if (buffer.ptr is null) + { + // count the buffer size + size_t size = 2, comma = 0; + foreach (kvp; this) + { + size += comma; + comma = 1; + ptrdiff_t len = formatValue(kvp.key, buffer, format, formatArgs); + if (len < 0) + return len; + size += len + 1; + len = formatValue(kvp.value, buffer, format, formatArgs); + if (len < 0) + return len; + size += len; + } + return size; + } + + if (buffer.length < 2) + return -1; + buffer[0] = '{'; + + size_t offset = 1; + bool add_comma = false; + foreach (kvp; this) + { + if (add_comma) + { + if (offset >= buffer.length) + return -1; + buffer[offset++] = ','; + } + else + add_comma = true; + ptrdiff_t len = formatValue(kvp.key, buffer[offset .. $], format, formatArgs); + if (len < 0) + return len; + offset += len; + if (offset >= buffer.length) + return -1; + buffer[offset++] = ':'; + len = formatValue(kvp.value, buffer[offset .. $], format, formatArgs); + if (len < 0) + return len; + offset += len; + } + + if (offset >= buffer.length) + return -1; + buffer[offset++] = '}'; + return offset; + } + + ptrdiff_t fromString()(const(char)[] s) + { + assert(false, "TODO"); + } + private: nothrow: alias Node = AVLTreeNode!(K, V); diff --git a/src/urt/string/format.d b/src/urt/string/format.d index 074a32d..7784113 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -120,6 +120,8 @@ struct FormatArg { static if (IsAggregate!T && is(typeof(&value.toString) : StringifyFunc)) toString = &value.toString; + else static if (IsAggregate!T && is(typeof(&value.toString!()) : StringifyFunc)) + toString = &value.toString!(); else static if (IsAggregate!T && __traits(compiles, value.toString(buffer, "format", cast(FormatArg[])null) == 0)) { // wrap in a delegate that adjusts for format + args... @@ -151,22 +153,16 @@ struct FormatArg } ptrdiff_t getString(char[] buffer, const(char)[] format, const(FormatArg)[] args) const nothrow @nogc - { - return toString(buffer, format, args); - } + => toString(buffer, format, args); + ptrdiff_t getLength(const(char)[] format, const(FormatArg)[] args) const nothrow @nogc - { - return toString(null, format, args); - } + => getString(null, format, args); bool canInt() const nothrow @nogc - { - return toInt != null; - } + => toInt != null; + ptrdiff_t getInt() const nothrow @nogc - { - return toInt(); - } + => toInt(); private: // TODO: we could assert that the delegate pointers match, and only store it once... @@ -569,6 +565,9 @@ struct DefFormat(T) } else static if (is(T == class)) { + // HACK: class toString is not @nogc, so we'll just stringify the pointer for right now... + return defToString(cast(void*)value, buffer, format, formatArgs); +/+ try { const(char)[] t = (cast()value).toString(); @@ -581,6 +580,7 @@ struct DefFormat(T) } catch (Exception) return -1; ++/ } else static if (is(T == const)) { @@ -632,6 +632,16 @@ struct DefFormat(T) } return ++len; } + else static if (is(T == function)) + { + assert(false, "TODO"); + return 0; + } + else static if (is(T == delegate)) + { + assert(false, "TODO"); + return 0; + } else static assert(false, "Not implemented for type: ", T.stringof); } From 89eba280556af7dbf6624e8b17b249cdf37e276f Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 14 Dec 2025 14:03:25 +1000 Subject: [PATCH 051/138] Write a log message before calling abort() when a fibre encounters an unrecoverable error. --- src/urt/fibre.d | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/urt/fibre.d b/src/urt/fibre.d index 2e736d2..38a5579 100644 --- a/src/urt/fibre.d +++ b/src/urt/fibre.d @@ -84,6 +84,8 @@ struct Fibre } catch (Throwable e) { + import urt.log; + writeDebugf("fibre abort: {0}:{1} - {2}", e.file, e.line, e.msg); abort(); } From f0867935dcbb8e6449b0621dcc9b24c9edfa4cc4 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 30 Dec 2025 21:12:39 +1000 Subject: [PATCH 052/138] Fixed a swap-endian bug in a function that had never been called. --- src/urt/endian.d | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/urt/endian.d b/src/urt/endian.d index a48f872..2ee3da5 100644 --- a/src/urt/endian.d +++ b/src/urt/endian.d @@ -208,8 +208,7 @@ pragma(inline, true) auto nativeToEndian(bool little, T)(T val) { import urt.meta : IntForWidth; alias U = IntForWidth!(T.sizeof*8); - U r = nativeToEndian!little(*cast(U*)&val); - return *cast(T*)&r; + return nativeToEndian!little(*cast(U*)&val); } ubyte[T.sizeof] nativeToEndian(bool little, T)(auto ref const T data) From aa0d02b34d2adf6bd744739f763cbb76591aa28a Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 14 Dec 2025 20:07:39 +1000 Subject: [PATCH 053/138] Explicit format arg. --- src/urt/string/string.d | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/urt/string/string.d b/src/urt/string/string.d index 843c7fd..1cf61e2 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -559,9 +559,9 @@ nothrow @nogc: append(forward!things); } - this(Args...)(Format_T, auto ref Args args) + this(Args...)(Format_T, const(char)[] format, auto ref Args args) { - format(forward!args); + this.format(format, forward!args); } ~this() @@ -690,9 +690,9 @@ nothrow @nogc: return this; } - ref MutableString!Embed appendFormat(Things...)(auto ref Things things) + ref MutableString!Embed appendFormat(Things...)(const(char)[] format, auto ref Things args) { - insertFormat(length(), forward!things); + insertFormat(length(), format, forward!args); return this; } @@ -704,11 +704,11 @@ nothrow @nogc: return this; } - ref MutableString!Embed format(Args...)(auto ref Args args) + ref MutableString!Embed format(Args...)(const(char)[] format, auto ref Args args) { if (ptr) writeLength(0); - insertFormat(0, forward!args); + insertFormat(0, format, forward!args); return this; } @@ -740,7 +740,7 @@ nothrow @nogc: return this; } - ref MutableString!Embed insertFormat(Things...)(size_t offset, auto ref Things things) + ref MutableString!Embed insertFormat(Things...)(size_t offset, const(char)[] format, auto ref Things args) { import urt.string.format : _format = format; import urt.util : max, next_power_of_2; @@ -748,7 +748,7 @@ nothrow @nogc: char* oldPtr = ptr; size_t oldLen = length(); - size_t insertLen = _format(null, things).length; + size_t insertLen = _format(null, format, args).length; size_t newLen = oldLen + insertLen; if (newLen == oldLen) return this; @@ -757,7 +757,7 @@ nothrow @nogc: size_t oldAlloc = allocated(); ptr = newLen <= oldAlloc ? oldPtr : allocStringBuffer(max(16, cast(ushort)newLen + 4).next_power_of_2 - 4); memmove(ptr + offset + insertLen, oldPtr + offset, oldLen - offset); - _format(ptr[offset .. offset + insertLen], forward!things); + _format(ptr[offset .. offset + insertLen], format, forward!args); writeLength(newLen); if (oldPtr && ptr != oldPtr) From 22e16a0722f5a0bfea591eb53484999b995bb4e8 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 31 Dec 2025 20:59:08 +1000 Subject: [PATCH 054/138] Added a function to parse a decimal uint, reporting the exponent where the resulting value is the smallest integral significand. --- src/urt/conv.d | 87 +++++++++++++++++++++++++++++++++++++++++++++++ src/urt/si/unit.d | 37 +++----------------- 2 files changed, 92 insertions(+), 32 deletions(-) diff --git a/src/urt/conv.d b/src/urt/conv.d index 8889082..8bd0b79 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -81,6 +81,91 @@ ulong parse_uint(const(char)[] str, size_t* bytes_taken = null, int base = 10) p return value; } +ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* bytes_taken = null, uint base = 10) pure +{ + import urt.util : ctz, is_power_of_2; + + assert(base > 1 && base <= 36, "Invalid base"); + + const(char)* s = str.ptr; + const(char)* e = s + str.length; + + ulong value = 0; + int exp = 0; + uint digits = 0; + uint zero_seq = 0; + + for (; s < e; ++s) + { + char c = *s; + + if (c == '.') + { + if (s == str.ptr) + goto done; + ++s; + if (digits) + digits += zero_seq; + zero_seq = 0; + goto parse_decimal; + } + else if (c == '0') + { + ++zero_seq; + continue; + } + + uint digit = get_digit(c); + if (digit >= base) + break; + + for (uint i = 0; i <= zero_seq; ++i) + value = value * base; + value += digit; + + if (digits) + digits += zero_seq; + digits += 1; + zero_seq = 0; + } + + // number has no decimal point, tail zeroes are positive exp + exp = digits ? zero_seq : 0; + goto done; + +parse_decimal: + for (; s < e; ++s) + { + char c = *s; + + if (c == '0') + { + ++zero_seq; + continue; + } + + uint digit = get_digit(c); + if (digit >= base) + break; + + for (uint i = 0; i <= zero_seq; ++i) + value = value * base; + value += digit; + + if (digits) + digits += zero_seq; + digits += 1; + exp -= 1 + zero_seq; + zero_seq = 0; + } + +done: + exponent = exp; + if (bytes_taken) + *bytes_taken = s - str.ptr; + return value; +} + ulong parse_uint_with_decimal(const(char)[] str, out ulong fixed_point_divisor, size_t* bytes_taken = null, int base = 10) pure { assert(base > 1 && base <= 36, "Invalid base"); @@ -99,6 +184,8 @@ ulong parse_uint_with_decimal(const(char)[] str, out ulong fixed_point_divisor, if (c == '.') { + if (s == str.ptr) + goto done; ++s; goto parse_decimal; } diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index 1ebf7a0..1e2c70e 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -567,6 +567,8 @@ nothrow: ptrdiff_t parseUnit(const(char)[] s, out float preScale) pure { + import urt.conv : parse_uint_with_exponent; + preScale = 1; if (s.length == 0) @@ -599,41 +601,12 @@ nothrow: { size_t offset = 0; - // parse the exponent + // parse the scale factor int e = 0; if (term[0].is_numeric) { - if (term[0] == '0') - { - if (term.length < 2 || term[1] != '.') - return -1; - e = 1; - offset = 2; - while (offset < term.length) - { - if (term[offset] == '1') - break; - if (term[offset] != '0') - return -1; - ++e; - ++offset; - } - ++offset; - e = -e; - } - else if (term[0] == '1') - { - offset = 1; - while (offset < term.length) - { - if (term[offset] != '0') - break; - ++e; - ++offset; - } - } - else - return -1; + ulong sf = term.parse_uint_with_exponent(e, &offset); + preScale *= sf; } if (offset == term.length) From 50d5ba50da76de7f9716a4b978b14863397dffc1 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 31 Dec 2025 23:54:21 +1000 Subject: [PATCH 055/138] Added unittest for parse_uint_with_exponent... fixed a bug. --- src/urt/conv.d | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/urt/conv.d b/src/urt/conv.d index 8bd0b79..160eb11 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -104,9 +104,7 @@ ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* byte if (s == str.ptr) goto done; ++s; - if (digits) - digits += zero_seq; - zero_seq = 0; + exp = zero_seq; goto parse_decimal; } else if (c == '0') @@ -119,12 +117,13 @@ ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* byte if (digit >= base) break; - for (uint i = 0; i <= zero_seq; ++i) - value = value * base; - value += digit; - if (digits) + { + for (uint i = 0; i <= zero_seq; ++i) + value = value * base; digits += zero_seq; + } + value += digit; digits += 1; zero_seq = 0; } @@ -148,16 +147,19 @@ parse_decimal: if (digit >= base) break; - for (uint i = 0; i <= zero_seq; ++i) - value = value * base; - value += digit; - if (digits) + { + for (uint i = 0; i <= zero_seq; ++i) + value = value * base; digits += zero_seq; + } + value += digit; digits += 1; exp -= 1 + zero_seq; zero_seq = 0; } + if (!digits) + exp = 0; // didn't parse any digits; reset exp to 0 done: exponent = exp; @@ -166,6 +168,19 @@ done: return value; } +unittest +{ + int e; + size_t taken; + assert("0001023000".parse_uint_with_exponent(e, &taken, 10) == 1023 && e == 3 && taken == 10); + assert("0.0012003000".parse_uint_with_exponent(e, &taken, 10) == 12003 && e == -7 && taken == 12); + assert("00010.23000".parse_uint_with_exponent(e, &taken, 10) == 1023 && e == -2 && taken == 11); + assert("00012300.0".parse_uint_with_exponent(e, &taken, 10) == 123 && e == 2 && taken == 10); + assert("00100.00230".parse_uint_with_exponent(e, &taken, 10) == 1000023 && e == -4 && taken == 11); + assert("0.0".parse_uint_with_exponent(e, &taken, 10) == 0 && e == 0 && taken == 3); + assert(".01".parse_uint_with_exponent(e, &taken, 10) == 0 && e == 0 && taken == 0); +} + ulong parse_uint_with_decimal(const(char)[] str, out ulong fixed_point_divisor, size_t* bytes_taken = null, int base = 10) pure { assert(base > 1 && base <= 36, "Invalid base"); From 50f7c18692a72a92e2ff1d3fcd38ae05775ca568 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 7 Jan 2026 15:11:52 +1000 Subject: [PATCH 056/138] Fixed some string formatting bugs and TODOs --- src/urt/encoding.d | 20 ++-- src/urt/format/json.d | 147 ++++++++++++++++------------ src/urt/inet.d | 30 +++--- src/urt/si/unit.d | 69 ++++++++++---- src/urt/string/format.d | 205 +++++++++++++++++++++++++--------------- 5 files changed, 285 insertions(+), 186 deletions(-) diff --git a/src/urt/encoding.d b/src/urt/encoding.d index 43fa85e..9f76aa0 100644 --- a/src/urt/encoding.d +++ b/src/urt/encoding.d @@ -8,10 +8,10 @@ enum HexDecode(string str) = () { ubyte[hex_decode_length(str.length)] r; enum URLDecode(string str) = () { char[url_decode_length(str)] r; size_t len = url_decode(str, r[]); assert(len == r.sizeof, "Not a URL encoded string: " ~ str); return r; }(); -ptrdiff_t base64_encode_length(size_t source_length) pure +size_t base64_encode_length(size_t source_length) pure => (source_length + 2) / 3 * 4; -ptrdiff_t base64_encode_length(const void[] data) pure +size_t base64_encode_length(const void[] data) pure => base64_encode_length(data.length); ptrdiff_t base64_encode(const void[] data, char[] result) pure @@ -58,10 +58,10 @@ ptrdiff_t base64_encode(const void[] data, char[] result) pure return out_len; } -ptrdiff_t base64_decode_length(size_t source_length) pure +size_t base64_decode_length(size_t source_length) pure => source_length / 4 * 3; -ptrdiff_t base64_decode_length(const char[] data) pure +size_t base64_decode_length(const char[] data) pure => base64_decode_length(data.length); ptrdiff_t base64_decode(const char[] data, void[] result) pure @@ -143,10 +143,10 @@ unittest assert(data[0..10] == decoded[0..10]); } -ptrdiff_t hex_encode_length(size_t sourceLength) pure +size_t hex_encode_length(size_t sourceLength) pure => sourceLength * 2; -ptrdiff_t hex_encode_length(const void[] data) pure +size_t hex_encode_length(const void[] data) pure => data.length * 2; ptrdiff_t hex_encode(const void[] data, char[] result) pure @@ -157,10 +157,10 @@ ptrdiff_t hex_encode(const void[] data, char[] result) pure return toHexString(data, result).length; } -ptrdiff_t hex_decode_length(size_t sourceLength) pure +size_t hex_decode_length(size_t sourceLength) pure => sourceLength / 2; -ptrdiff_t hex_decode_length(const char[] data) pure +size_t hex_decode_length(const char[] data) pure => data.length / 2; ptrdiff_t hex_decode(const char[] data, void[] result) pure @@ -212,7 +212,7 @@ unittest } -ptrdiff_t url_encode_length(const char[] data) pure +size_t url_encode_length(const char[] data) pure { import urt.string.ascii : is_url; @@ -255,7 +255,7 @@ ptrdiff_t url_encode(const char[] data, char[] result) pure return j; } -ptrdiff_t url_decode_length(const char[] data) pure +size_t url_decode_length(const char[] data) pure { size_t len = 0; for (size_t i = 0; i < data.length;) diff --git a/src/urt/format/json.d b/src/urt/format/json.d index 4b65529..9a4e7dd 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -101,85 +101,98 @@ ptrdiff_t write_json(ref const Variant val, char[] buffer, bool dense = false, u return written; case Variant.Type.Buffer: - if (val.isString) + if (!val.isString) { - const char[] s = val.asString(); + import urt.encoding; - if (!buffer.ptr) + // emit raw buffer as base64 + const data = val.asBuffer(); + size_t enc_len = base64_encode_length(data.length); + if (buffer.ptr) { - size_t len = 0; - foreach (c; s) - { - if (c < 0x20) - { - if (c == '\n' || c == '\r' || c == '\t' || c == '\b' || c == '\f') - len += 2; - else - len += 6; - } - else if (c == '"' || c == '\\') - len += 2; - else - len += 1; - } - return len + 2; + if (buffer.length < 2 + enc_len) + return -1; + buffer[0] = '"'; + ptrdiff_t r = data.base64_encode(buffer[1 .. 1 + enc_len]); + if (r != enc_len) + return -2; + buffer[1 + enc_len] = '"'; } + return 2 + enc_len; + } - if (buffer.length < s.length + 2) - return -1; + const char[] s = val.asString(); - buffer[0] = '"'; - // escape strings - size_t offset = 1; + if (!buffer.ptr) + { + size_t len = 0; foreach (c; s) { - char sub = void; if (c < 0x20) { - if (c == '\n') - sub = 'n'; - else if (c == '\r') - sub = 'r'; - else if (c == '\t') - sub = 't'; - else if (c == '\b') - sub = 'b'; - else if (c == '\f') - sub = 'f'; + if (c == '\n' || c == '\r' || c == '\t' || c == '\b' || c == '\f') + len += 2; else - { - if (buffer.length < offset + 7) - return -1; - buffer[offset .. offset + 4] = "\\u00"; - offset += 4; - buffer[offset++] = hex_digits[c >> 4]; - buffer[offset++] = hex_digits[c & 0xF]; - continue; - } + len += 6; } else if (c == '"' || c == '\\') - sub = c; + len += 2; + else + len += 1; + } + return len + 2; + } + + if (buffer.length < s.length + 2) + return -1; + + buffer[0] = '"'; + // escape strings + size_t offset = 1; + foreach (c; s) + { + char sub = void; + if (c < 0x20) + { + if (c == '\n') + sub = 'n'; + else if (c == '\r') + sub = 'r'; + else if (c == '\t') + sub = 't'; + else if (c == '\b') + sub = 'b'; + else if (c == '\f') + sub = 'f'; else { - if (buffer.length < offset + 2) + if (buffer.length < offset + 7) return -1; - buffer[offset++] = c; + buffer[offset .. offset + 4] = "\\u00"; + offset += 4; + buffer[offset++] = hex_digits[c >> 4]; + buffer[offset++] = hex_digits[c & 0xF]; continue; } - - // write escape sequence - if (buffer.length < offset + 3) + } + else if (c == '"' || c == '\\') + sub = c; + else + { + if (buffer.length < offset + 2) return -1; - buffer[offset++] = '\\'; - buffer[offset++] = sub; + buffer[offset++] = c; + continue; } - buffer[offset++] = '"'; - return offset; - } - else - { - assert(false, "TODO: how are binary buffers represented in json?"); + + // write escape sequence + if (buffer.length < offset + 3) + return -1; + buffer[offset++] = '\\'; + buffer[offset++] = sub; } + buffer[offset++] = '"'; + return offset; case Variant.Type.Number: import urt.conv; @@ -198,11 +211,19 @@ ptrdiff_t write_json(ref const Variant val, char[] buffer, bool dense = false, u return val.asLong().format_int(buffer); case Variant.Type.User: - // in order to text-ify a user type, we probably need a hash table of text-ify functions, which - // we can lookup by the typeId... - // the tricky bit is, we need to init the table based on instantiations of constructors for each T... - // maybe an object with static constructor for each instantiation, which will hook it into the table? - assert(false, "TODO..."); + // for custom types, we'll use the type's regular string format into a json string + if (!buffer.ptr) + return val.toString(null, null, null) + 2; + if (buffer.length < 1) + return -1; + buffer[0] = '\"'; + ptrdiff_t len = val.toString(buffer[1 .. $], null, null); + if (len < 0) + return len; + if (buffer.length < len + 2) + return -1; + buffer[1 + len] = '\"'; + return len + 2; } } diff --git a/src/urt/inet.d b/src/urt/inet.d index b2d37bb..be1bddc 100644 --- a/src/urt/inet.d +++ b/src/urt/inet.d @@ -114,7 +114,7 @@ nothrow @nogc: return fnv1a(b[]); } - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] format_args) const pure + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const pure { char[15] stack_buffer = void; char[] tmp = buffer.length < stack_buffer.sizeof ? stack_buffer : buffer; @@ -123,7 +123,7 @@ nothrow @nogc: { if (i > 0) tmp[offset++] = '.'; - offset += b[i].format_int(tmp[offset..$]); + offset += b[i].format_uint(tmp[offset..$]); } if (buffer.ptr && tmp.ptr == stack_buffer.ptr) @@ -139,22 +139,22 @@ nothrow @nogc: { ubyte[4] t; size_t offset = 0, len; - ulong i = s[offset..$].parse_int(&len); + ulong i = s[offset..$].parse_uint(&len); offset += len; if (len == 0 || i > 255 || s.length < offset + 1 || s[offset++] != '.') return -1; t[0] = cast(ubyte)i; - i = s[offset..$].parse_int(&len); + i = s[offset..$].parse_uint(&len); offset += len; if (len == 0 || i > 255 || s.length < offset + 1 || s[offset++] != '.') return -1; t[1] = cast(ubyte)i; - i = s[offset..$].parse_int(&len); + i = s[offset..$].parse_uint(&len); offset += len; if (len == 0 || i > 255 || s.length < offset + 1 || s[offset++] != '.') return -1; t[2] = cast(ubyte)i; - i = s[offset..$].parse_int(&len); + i = s[offset..$].parse_uint(&len); offset += len; if (len == 0 || i > 255) return -1; @@ -297,7 +297,7 @@ nothrow @nogc: tmp[offset++] = ':'; continue; } - offset += s[i].format_int(tmp[offset..$], 16); + offset += s[i].format_uint(tmp[offset..$], 16); ++i; } @@ -338,7 +338,7 @@ nothrow @nogc: } if (str[offset] == ':') return -1; - ulong i = str[offset..$].parse_int(&len, 16); + ulong i = str[offset..$].parse_uint(&len, 16); if (len == 0) break; if (i > ushort.max || count[0] + count[1] == 8) @@ -418,7 +418,7 @@ nothrow @nogc: size_t offset = addr.toString(tmp, null, null); tmp[offset++] = '/'; - offset += prefix_len.format_int(tmp[offset..$]); + offset += prefix_len.format_uint(tmp[offset..$]); if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { @@ -436,7 +436,7 @@ nothrow @nogc: if (taken < 0 || s.length <= taken + 1 || s[taken++] != '/') return -1; size_t t; - ulong plen = s[taken..$].parse_int(&t); + ulong plen = s[taken..$].parse_uint(&t); if (t == 0 || plen > 32) return -1; addr = a; @@ -515,7 +515,7 @@ nothrow @nogc: size_t offset = addr.toString(tmp, null, null); tmp[offset++] = '/'; - offset += prefix_len.format_int(tmp[offset..$]); + offset += prefix_len.format_uint(tmp[offset..$]); if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { @@ -533,7 +533,7 @@ nothrow @nogc: if (taken < 0 || s.length <= taken + 1 || s[taken++] != '/') return -1; size_t t; - ulong plen = s[taken..$].parse_int(&t); + ulong plen = s[taken..$].parse_uint(&t); if (t == 0 || plen > 128) return -1; addr = a; @@ -665,7 +665,7 @@ nothrow @nogc: { offset = _a.ipv4.addr.toString(tmp, null, null); tmp[offset++] = ':'; - offset += _a.ipv4.port.format_int(tmp[offset..$]); + offset += _a.ipv4.port.format_uint(tmp[offset..$]); } else { @@ -673,7 +673,7 @@ nothrow @nogc: offset = 1 + _a.ipv6.addr.toString(tmp[1 .. $], null, null); tmp[offset++] = ']'; tmp[offset++] = ':'; - offset += _a.ipv6.port.format_int(tmp[offset..$]); + offset += _a.ipv6.port.format_uint(tmp[offset..$]); } if (buffer.ptr && tmp.ptr == stack_buffer.ptr) @@ -720,7 +720,7 @@ nothrow @nogc: if (s.length > taken && s[taken] == ':') { size_t t; - ulong p = s[++taken..$].parse_int(&t); + ulong p = s[++taken..$].parse_uint(&t); if (t == 0 || p > ushort.max) return -1; taken += t; diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index 1e2c70e..c0c4ae5 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -690,13 +690,27 @@ nothrow: { if (siScale && exp == -2) { - if (buffer.length == 0) - return -1; - buffer[0] = '%'; + if (buffer.ptr) + { + if (buffer.length == 0) + return -1; + buffer[0] = '%'; + } return 1; } + else if (siScale && exp == -3) + { + enum pm_len = "‰".length; + if (buffer.ptr) + { + if (buffer.length < pm_len) + return -1; + buffer[0..pm_len] = "‰"; + } + return pm_len; + } else - assert(false, "TODO!"); + assert(false, "TODO!"); // how (or should?) we encode a scale as a unit type? } size_t len = 0; @@ -711,18 +725,24 @@ nothrow: { if (y == 1) { - if (buffer.length < 2) - return -1; + if (buffer.ptr) + { + if (buffer.length < 2) + return -1; + buffer[0..2] = "10"; + } --x; - buffer[0..2] = "10"; len += 2; } else { - if (buffer.length < 3) - return -1; + if (buffer.ptr) + { + if (buffer.length < 3) + return -1; + buffer[0..3] = "100"; + } x -= 2; - buffer[0..3] = "100"; len += 3; } } @@ -730,17 +750,24 @@ nothrow: if (x != 0) { - if (buffer.length <= len) - return -1; - buffer[len++] = "qryzafpnum kMGTPEZYRQ"[x/3 + 10]; + if (buffer.ptr) + { + if (buffer.length <= len) + return -1; + buffer[len] = "qryzafpnum kMGTPEZYRQ"[x/3 + 10]; + } + ++len; } } if (const string* name = unit in unitNames) { - if (buffer.length < len + name.length) - return -1; - buffer[len .. len + name.length] = *name; + if (buffer.ptr) + { + if (buffer.length < len + name.length) + return -1; + buffer[len .. len + name.length] = *name; + } len += name.length; } else @@ -753,9 +780,12 @@ nothrow: { if (const string* name = this in scaledUnitNames) { - if (buffer.length < len + name.length) - return -1; - buffer[len .. len + name.length] = *name; + if (buffer.ptr) + { + if (buffer.length < len + name.length) + return -1; + buffer[len .. len + name.length] = *name; + } len += name.length; } else @@ -776,7 +806,6 @@ nothrow: return r; } - size_t toHash() const pure => pack; diff --git a/src/urt/string/format.d b/src/urt/string/format.d index 7784113..613a819 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -7,29 +7,27 @@ import urt.util; public import urt.mem.temp : tformat, tconcat, tstring; - nothrow @nogc: debug { + // format functions are not re-entrant! static bool InFormatFunction = false; } alias StringifyFunc = ptrdiff_t delegate(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) nothrow @nogc; -alias IntifyFunc = ptrdiff_t delegate() nothrow @nogc; -ptrdiff_t toString(T)(auto ref T value, char[] buffer) +ptrdiff_t toString(T)(auto ref T value, char[] buffer, const(char)[] format = null, const(FormatArg)[] formatArgs = null) { - import urt.string.format : FormatArg; - debug InFormatFunction = true; - FormatArg a = FormatArg(value); - ptrdiff_t r = a.getString(buffer, null, null); + ptrdiff_t r = get_to_string_func(value)(buffer, null, null); debug InFormatFunction = false; return r; } +alias formatValue = toString; // TODO: remove me? + char[] concat(Args...)(char[] buffer, auto ref Args args) { static if (Args.length == 0) @@ -83,11 +81,10 @@ char[] concat(Args...)(char[] buffer, auto ref Args args) // this implementation handles all the other kinds of things! debug if (!__ctfe) InFormatFunction = true; - FormatArg[Args.length] argFuncs; - // TODO: no need to collect int-ify functions in the arg set... + StringifyFunc[Args.length] arg_funcs = void; static foreach(i, arg; args) - argFuncs[i] = FormatArg(arg); - char[] r = concatImpl(buffer, argFuncs); + arg_funcs[i] = get_to_string_func(arg); + char[] r = concatImpl(buffer, arg_funcs); debug if (!__ctfe) InFormatFunction = false; return r; } @@ -104,86 +101,146 @@ char[] format(Args...)(char[] buffer, const(char)[] fmt, ref Args args) return r; } -ptrdiff_t formatValue(T)(auto ref T value, char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) -{ - static if (is(typeof(&value.toString) : StringifyFunc)) - return value.toString(buffer, format, formatArgs); - else - return value.defFormat.toString(buffer, format, formatArgs); -} - struct FormatArg { - enum IsAggregate(T) = is(T == struct) || is(T == class); - private this(T)(ref T value) pure nothrow @nogc { - static if (IsAggregate!T && is(typeof(&value.toString) : StringifyFunc)) - toString = &value.toString; - else static if (IsAggregate!T && is(typeof(&value.toString!()) : StringifyFunc)) - toString = &value.toString!(); - else static if (IsAggregate!T && __traits(compiles, value.toString(buffer, "format", cast(FormatArg[])null) == 0)) - { - // wrap in a delegate that adjusts for format + args... - static assert(false); - } - else static if (IsAggregate!T && __traits(compiles, value.toString(buffer, "format") == 0)) - { - // wrap in a delegate that adjusts for format... - static assert(false); - } - else static if (IsAggregate!T && __traits(compiles, value.toString(buffer) == 0)) - { - // wrap in a delegate... - static assert(false); - } - else static if (IsAggregate!T && __traits(compiles, value.toString((const(char)[]){}) == 0)) - { - // version with a sink function... - // wrap in a delegate that adjusts for format + args... - static assert(false); - } - else - toString = &defFormat(value).toString; + getString = get_to_string_func(value); - static if (is(typeof(&defFormat(value).toInt))) - toInt = &defFormat(value).toInt; - else - toInt = null; + static if (can_default_int!T) + _to_int_fun = &DefInt!T.to_int; } - ptrdiff_t getString(char[] buffer, const(char)[] format, const(FormatArg)[] args) const nothrow @nogc - => toString(buffer, format, args); + StringifyFunc getString; ptrdiff_t getLength(const(char)[] format, const(FormatArg)[] args) const nothrow @nogc => getString(null, format, args); - bool canInt() const nothrow @nogc - => toInt != null; + bool canInt() const pure nothrow @nogc + => _to_int_fun !is null; - ptrdiff_t getInt() const nothrow @nogc - => toInt(); + ptrdiff_t getInt() const pure nothrow @nogc + { + ptrdiff_t delegate() pure nothrow @nogc to_int; + to_int.ptr = getString.ptr; + to_int.funcptr = _to_int_fun; + return to_int(); + } private: - // TODO: we could assert that the delegate pointers match, and only store it once... - StringifyFunc toString; - IntifyFunc toInt; + // only store the function pointer, since 'this' will be common + ptrdiff_t function() pure nothrow @nogc _to_int_fun; } private: -pragma(inline, true) -DefFormat!T* defFormat(T)(ref const(T) value) pure nothrow @nogc +alias StringifyFuncReduced = ptrdiff_t delegate(char[] buffer, const(char)[] format) nothrow @nogc; +alias StringifyFuncReduced2 = ptrdiff_t delegate(char[] buffer) nothrow @nogc; + +enum can_default_int(T) = is_some_int!T || is(T == bool); + +template to_string_overload_index(T) { - return cast(DefFormat!T*)&value; + static if (!is(T == Unqual!T)) // minimise redundant instantiations + enum to_string_overload_index = to_string_overload_index!(Unqual!T); + else + enum to_string_overload_index = () { + static if (__traits(hasMember, T, "toString")) + { + // multiple passes so that we correctly preference the overload with more arguments... + static foreach (i, overload; __traits(getOverloads, T, "toString", true)) + { + static if (is(typeof(&overload) : typeof(StringifyFunc.funcptr))) + return i; + else static if (is(typeof(&overload!()) : typeof(StringifyFunc.funcptr))) + return i; + } + static foreach (i, overload; __traits(getOverloads, T, "toString", true)) + { + static if (is(typeof(&overload) : typeof(StringifyFuncReduced.funcptr))) + return i; + else static if (is(typeof(&overload!()) : typeof(StringifyFuncReduced.funcptr))) + return i; + } + static foreach (i, overload; __traits(getOverloads, T, "toString", true)) + { + static if (is(typeof(&overload) : typeof(StringifyFuncReduced2.funcptr))) + return i; + else static if (is(typeof(&overload!()) : typeof(StringifyFuncReduced2.funcptr))) + return i; + } + } + return -1; + }(); } -ptrdiff_t defToString(T)(ref const(T) value, char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) nothrow @nogc +StringifyFunc get_to_string_func(T)(ref T value) +{ + enum overload_index = to_string_overload_index!T; + + static if (overload_index >= 0) + { + static if (is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]) : StringifyFunc)) + return &__traits(getOverloads, value, "toString", true)[overload_index]; + + // TODO: if toString is a template, we need to instantiate it and then captuire the function pointer... + + static if (is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]) : StringifyFuncReduced))// || +// is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]!()) : StringifyFuncReduced)) + { + StringifyFunc d; + d.ptr = &value; + d.funcptr = &ToStringShim.shim1!T; + return d; + } + + static if (is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]) : StringifyFuncReduced2))// || +// is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]!()) : StringifyFuncReduced2)) + { + StringifyFunc d; + d.ptr = &value; + d.funcptr = &ToStringShim.shim2!T; + return d; + } + + // TODO: do we want to support toString variants with sink instead of buffer? + + return null; + } + else + return &(cast(DefFormat!T*)&value).toString; +} + +struct ToStringShim +{ + ptrdiff_t shim1(T)(char[] buffer, const(char)[] format, const(FormatArg)[]) + { + ref T _this = *cast(T*)&this; + return _this.toString(buffer, format); + } + ptrdiff_t shim2(T)(char[] buffer, const(char)[], const(FormatArg)[]) + { + ref T _this = *cast(T*)&this; + return _this.toString(buffer); + } +} + +struct DefInt(T) { - return (cast(DefFormat!T*)&value).toString(buffer, format, formatArgs); + T value; + + ptrdiff_t to_int() const pure nothrow @nogc + { + static if (T.max > ptrdiff_t.max) + debug assert(value <= ptrdiff_t.max); + return cast(ptrdiff_t)value; + } } +ptrdiff_t defToString(T)(ref const(T) value, char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) nothrow @nogc + => (cast(DefFormat!T*)&value).toString(buffer, format, formatArgs); + struct DefFormat(T) { T value; @@ -588,6 +645,8 @@ struct DefFormat(T) } else static if (is(T == struct)) { + static assert(!__traits(hasMember, T, "toString"), "Struct with custom toString not properly selected!"); + // general structs if (buffer.ptr) { @@ -645,24 +704,14 @@ struct DefFormat(T) else static assert(false, "Not implemented for type: ", T.stringof); } - - static if (is_some_int!T || is(T == bool)) - { - ptrdiff_t toInt() const pure nothrow @nogc - { - static if (T.max > ptrdiff_t.max) - debug assert(value <= ptrdiff_t.max); - return cast(ptrdiff_t)value; - } - } } -char[] concatImpl(char[] buffer, const(FormatArg)[] args) nothrow @nogc +char[] concatImpl(char[] buffer, const(StringifyFunc)[] args) nothrow @nogc { size_t len = 0; foreach (a; args) { - ptrdiff_t r = a.getString(buffer.ptr ? buffer[len..$] : null, null, null); + ptrdiff_t r = a(buffer.ptr ? buffer[len..$] : null, null, null); if (r < 0) return null; len += r; From 73cd497905665b60816c162002d33ee3ed01eb68 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 8 Jan 2026 12:48:48 +1000 Subject: [PATCH 057/138] Properly support template toString functions, and also improved/simplified the implementation. --- src/urt/string/format.d | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/urt/string/format.d b/src/urt/string/format.d index 613a819..ae564c6 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -181,26 +181,27 @@ StringifyFunc get_to_string_func(T)(ref T value) static if (overload_index >= 0) { - static if (is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]) : StringifyFunc)) - return &__traits(getOverloads, value, "toString", true)[overload_index]; + alias overload = __traits(getOverloads, T, "toString", true)[overload_index]; + static if (__traits(isTemplate, overload)) + alias to_string = overload!(); + else + alias to_string = overload; - // TODO: if toString is a template, we need to instantiate it and then captuire the function pointer... + // TODO: we can't alias the __traits(child) expression, so we need to repeat it everywhere! - static if (is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]) : StringifyFuncReduced))// || -// is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]!()) : StringifyFuncReduced)) - { - StringifyFunc d; - d.ptr = &value; - d.funcptr = &ToStringShim.shim1!T; - return d; - } + alias method_type = typeof(&__traits(child, value, to_string)); - static if (is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]) : StringifyFuncReduced2))// || -// is(typeof(&__traits(getOverloads, value, "toString", true)[overload_index]!()) : StringifyFuncReduced2)) + static if (is(method_type : StringifyFunc)) + return &__traits(child, value, to_string); + + else static if (is(method_type : StringifyFuncReduced) || is(method_type : StringifyFuncReduced2)) { StringifyFunc d; d.ptr = &value; - d.funcptr = &ToStringShim.shim2!T; + static if (is(method_type : StringifyFuncReduced)) + d.funcptr = &ToStringShim.shim1!T; + else + d.funcptr = &ToStringShim.shim2!T; return d; } From a672ee1ff40628a1b1dabd7ff389380ca1ce8357 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 8 Jan 2026 17:08:40 +1000 Subject: [PATCH 058/138] Fleshed out the wildcard match, and Claude added a shitload of tests! --- src/urt/string/package.d | 165 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 157 insertions(+), 8 deletions(-) diff --git a/src/urt/string/package.d b/src/urt/string/package.d index 81cbf75..ad11e2c 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -459,19 +459,66 @@ unittest } -bool wildcardMatch(const(char)[] wildcard, const(char)[] value) pure +bool wildcard_match(const(char)[] wildcard, const(char)[] value, bool value_wildcard = false) pure { - // TODO: write this function... + const(char)* a = wildcard.ptr, ae = a + wildcard.length, b = value.ptr, be = b + value.length; + const(char)* star_a = null, star_b = null; - // HACK: we just use this for tail wildcards right now... - for (size_t i = 0; i < wildcard.length; ++i) + while (a < ae && b < be) { - if (wildcard[i] == '*') - return true; - if (wildcard[i] != value[i]) + char ca_orig = *a, cb_orig = *b; + char ca = ca_orig, cb = cb_orig; + + // handle escape + if (ca == '\\' && a + 1 < ae) + ca = *++a; + if (value_wildcard && cb == '\\' && b + 1 < be) + cb = *++b; + + // handle wildcards + if (ca_orig == '*') + { + star_a = ++a; + star_b = b; + continue; + } + if (value_wildcard && cb_orig == '*') + { + star_b = ++b; + star_a = a; + continue; + } + + // compare next char + if (ca_orig == '?' || (value_wildcard && cb_orig == '?') || ca == cb) + { + ++a; + ++b; + continue; + } + + // backtrack: expand previous * match + if (!star_a) return false; + a = star_a; + b = ++star_b; } - return wildcard.length == value.length; + + // skip past tail wildcards + while (a < ae && *a == '*') + ++a; + if (value_wildcard) + { + while (b < be && *b == '*') + ++b; + } + + // check for match + if (a == ae && (b == be || star_a !is null)) + return true; + if (value_wildcard && b == be && star_b !is null) + return true; + return false; } @@ -500,4 +547,106 @@ unittest assert("HÉLLO".contains('É')); assert(!"HÉLLO".contains('A')); assert("HÉLLO".contains_i("éll")); + + // test wildcard_match + assert(wildcard_match("hello", "hello")); + assert(!wildcard_match("hello", "world")); + assert(wildcard_match("h*o", "hello")); + assert(wildcard_match("h*", "hello")); + assert(wildcard_match("*o", "hello")); + assert(wildcard_match("*", "hello")); + assert(wildcard_match("h?llo", "hello")); + assert(!wildcard_match("h?llo", "hllo")); + assert(wildcard_match("h??lo", "hello")); + assert(!wildcard_match("a*b", "axxxc")); + + // multiple wildcards + assert(wildcard_match("*l*o", "hello")); + assert(wildcard_match("h*l*o", "hello")); + assert(wildcard_match("h*l*", "hello")); + assert(wildcard_match("*e*l*", "hello")); + assert(wildcard_match("*h*e*l*l*o*", "hello")); + + // wildcards with sequences in between + assert(wildcard_match("h*ll*", "hello")); + assert(wildcard_match("*el*", "hello")); + assert(wildcard_match("h*el*o", "hello")); + assert(!wildcard_match("h*el*x", "hello")); + assert(wildcard_match("*lo", "hello")); + assert(!wildcard_match("*lx", "hello")); + + // mixed wildcards and single matches + assert(wildcard_match("h?*o", "hello")); + assert(wildcard_match("h*?o", "hello")); + assert(wildcard_match("?e*o", "hello")); + assert(wildcard_match("h?ll?", "hello")); + assert(!wildcard_match("h?ll?", "hllo")); + + // overlapping wildcards + assert(wildcard_match("**hello", "hello")); + assert(wildcard_match("hello**", "hello")); + assert(wildcard_match("h**o", "hello")); + assert(wildcard_match("*?*", "hello")); + assert(wildcard_match("?*?", "hello")); + assert(!wildcard_match("?*?", "x")); + assert(wildcard_match("?*?", "xx")); + + // escape sequences + assert(wildcard_match("\\*", "*")); + assert(wildcard_match("\\?", "?")); + assert(wildcard_match("\\\\", "\\")); + assert(!wildcard_match("\\*", "a")); + assert(wildcard_match("h\\*o", "h*o")); + assert(!wildcard_match("h\\*o", "hello")); + assert(wildcard_match("\\*\\?\\\\", "*?\\")); + assert(wildcard_match("a\\*b*c", "a*bxyzc")); + assert(wildcard_match("*\\**", "hello*world")); + + // edge cases + assert(wildcard_match("", "")); + assert(!wildcard_match("", "a")); + assert(wildcard_match("*", "")); + assert(wildcard_match("**", "")); + assert(!wildcard_match("?", "")); + assert(wildcard_match("a*b*c", "abc")); + assert(wildcard_match("a*b*c", "aXbYc")); + assert(wildcard_match("a*b*c", "aXXbYYc")); + + // value_wildcard tests - bidirectional matching + assert(wildcard_match("hello", "h*o", true)); + assert(wildcard_match("h*o", "hello", true)); + assert(wildcard_match("h?llo", "he?lo", true)); + assert(wildcard_match("h\\*o", "h\\*o", true)); + assert(wildcard_match("test*", "*test", true)); + + // both sides have wildcards + assert(wildcard_match("h*o", "h*o", true)); + assert(wildcard_match("*hello*", "*world*", true)); + assert(wildcard_match("a*b*c", "a*b*c", true)); + assert(wildcard_match("*", "*", true)); + assert(wildcard_match("?", "?", true)); + + // complex interplay - wildcards matching wildcards + assert(wildcard_match("a*c", "a?c", true)); + assert(wildcard_match("a?c", "a*c", true)); + assert(wildcard_match("*abc", "?abc", true)); + assert(wildcard_match("abc*", "abc?", true)); + + // multiple wildcards on both sides + assert(wildcard_match("a*b*c", "a?b?c", true)); + assert(wildcard_match("*a*b*", "?a?b?", true)); + assert(wildcard_match("a**b", "a*b", true)); + assert(wildcard_match("a*b", "a**b", true)); + + // wildcards at different positions + assert(wildcard_match("*test", "test*", true)); + assert(wildcard_match("test*end", "*end", true)); + assert(wildcard_match("*middle*", "start*", true)); + + // edge cases with value_wildcard + assert(wildcard_match("", "", true)); + assert(wildcard_match("*", "", true)); + assert(wildcard_match("", "*", true)); + assert(wildcard_match("**", "*", true)); + assert(wildcard_match("*", "**", true)); } From 3c483a258b1c06d04c479186a445ca8e8e5ae862 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 8 Jan 2026 17:47:49 +1000 Subject: [PATCH 059/138] Added case insensitive option, numeric match, and optional match. --- src/urt/string/package.d | 354 +++++++++++++++++++++++++++++++++++---- 1 file changed, 318 insertions(+), 36 deletions(-) diff --git a/src/urt/string/package.d b/src/urt/string/package.d index ad11e2c..0e39201 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -459,68 +459,186 @@ unittest } -bool wildcard_match(const(char)[] wildcard, const(char)[] value, bool value_wildcard = false) pure +// Pattern grammar: +// * - Match zero or more characters +// ? - Match exactly one character +// # - Match exactly one digit (0-9) +// ~T - Token T is optional (matches with or without T) +// \C - Escape character C (literal match) +// Examples: +// "h*o" matches "hello", "ho" +// "h?llo" matches "hello", "hallo" but not "hllo" +// "v#.#" matches "v1.2", "v3.7" but not "va.b" +// "~ab" matches "ab", "b" +// "h\*o" matches "h*o" literally +bool wildcard_match(const(char)[] wildcard, const(char)[] value, bool value_wildcard = false, bool case_insensitive = false) pure { const(char)* a = wildcard.ptr, ae = a + wildcard.length, b = value.ptr, be = b + value.length; - const(char)* star_a = null, star_b = null; - while (a < ae && b < be) + static inout(char)* skip_wilds(inout(char)* p, const(char)* pe) + { + while (p < pe) + { + if (*p == '*') + ++p; + else if (*p == '~') + { + one_more: + if (++p < pe) + { + if (*p == '~') + goto one_more; + if (*p++ == '\\' && p < pe) + ++p; + } + } + else break; + } + // HACK: skip trailing slash (handle a degenerate case) + if (p == pe - 1 && *p == '\\') + ++p; + return p; + } + + struct BacktrackState { - char ca_orig = *a, cb_orig = *b; - char ca = ca_orig, cb = cb_orig; + const(char)* a, b; + } + BacktrackState[64] backtrack_stack = void; + size_t backtrack_depth = 0; - // handle escape - if (ca == '\\' && a + 1 < ae) - ca = *++a; - if (value_wildcard && cb == '\\' && b + 1 < be) - cb = *++b; + char ca_orig = void, cb_orig = void, ca = void, cb = void; + + while (a < ae && b < be) + { + ca_orig = *a, cb_orig = *b; // handle wildcards if (ca_orig == '*') { - star_a = ++a; - star_b = b; - continue; + a = skip_wilds(++a, ae); + if (a == ae) // tail star matches everything + return true; + + const(char)[] a_remain = a[0 .. ae - a]; + for (; b <= be; ++b) + { + if (wildcard_match(a_remain, b[0 .. be - b], value_wildcard, case_insensitive)) + return true; + } + return false; } - if (value_wildcard && cb_orig == '*') + + // handle optionals + if (ca_orig == '~') { - star_b = ++b; - star_a = a; + while (++a < ae && *a == '~') {} + if (a == ae) + break; + + if (backtrack_depth < backtrack_stack.length) + { + backtrack_stack[backtrack_depth].a = a + (*a == '\\' ? 2 : 1); + backtrack_stack[backtrack_depth].b = b; + ++backtrack_depth; + } continue; } + // handle escape + ca = ca_orig, cb = cb_orig; + if (ca == '\\') + { + if (++a < ae) + ca = *a; + else + break; + } + + // handle value wildcards + if (value_wildcard) + { + if (cb_orig == '*') + { + b = skip_wilds(++b, be); + if (b == be) // tail star matches everything + return true; + + const(char)[] b_remain = b[0 .. be - b]; + for (; a <= ae; ++a) + { + if (wildcard_match(a[0 .. ae - a], b_remain, true, case_insensitive)) + return true; + } + return false; + } + if (cb_orig == '~') + { + while (++b < be && *b == '~') {} + if (b == be) + break; + + if (backtrack_depth < backtrack_stack.length) + { + backtrack_stack[backtrack_depth].a = a; + backtrack_stack[backtrack_depth].b = b + (*b == '\\' ? 2 : 1); + ++backtrack_depth; + } + continue; + } + if (cb == '\\') + { + if (++b < be) + cb = *b; + else + break; + } + if (cb_orig == '#') + { + if (ca.is_numeric || ca_orig == '#') + goto advance; + } + } + // compare next char - if (ca_orig == '?' || (value_wildcard && cb_orig == '?') || ca == cb) + if (ca_orig == '#') { - ++a; - ++b; - continue; + if (cb.is_numeric) + goto advance; } + else if ((case_insensitive ? ca.to_lower == cb.to_lower : ca == cb) || (ca_orig == '?') || (value_wildcard && cb_orig == '?')) + goto advance; - // backtrack: expand previous * match - if (!star_a) + try_backtrack: + if (backtrack_depth == 0) return false; - a = star_a; - b = ++star_b; + + --backtrack_depth; + a = backtrack_stack[backtrack_depth].a; + b = backtrack_stack[backtrack_depth].b; + continue; + + advance: + ++a, ++b; } - // skip past tail wildcards - while (a < ae && *a == '*') - ++a; - if (value_wildcard) + // check the strings are equal... + if (a == ae) { - while (b < be && *b == '*') - ++b; + if (value_wildcard) + b = skip_wilds(b, be); + if (b == be) + return true; } - - // check for match - if (a == ae && (b == be || star_a !is null)) - return true; - if (value_wildcard && b == be && star_b !is null) + else if (b == be && skip_wilds(a, ae) == ae) return true; - return false; + + goto try_backtrack; } +bool wildcard_match_i(const(char)[] wildcard, const(char)[] value, bool value_wildcard = false) pure + => wildcard_match(wildcard, value, value_wildcard, true); + unittest { @@ -560,6 +678,149 @@ unittest assert(wildcard_match("h??lo", "hello")); assert(!wildcard_match("a*b", "axxxc")); + // test optional - basic cases + assert(wildcard_match("~ab", "b")); + assert(wildcard_match("~ab", "ab")); + assert(wildcard_match("a~b", "a")); + assert(wildcard_match("a~b", "ab")); + assert(!wildcard_match("~ab", "a")); + assert(!wildcard_match("a~b", "b")); + assert(!wildcard_match("a~b", "ac")); + + // optional - multiple optionals + assert(wildcard_match("~a~b", "")); + assert(wildcard_match("~a~b", "a")); + assert(wildcard_match("~a~b", "b")); + assert(wildcard_match("~a~b", "ab")); + assert(!wildcard_match("~a~b", "ba")); + assert(wildcard_match("~a~b~c", "")); + assert(wildcard_match("~a~b~c", "ac")); + assert(wildcard_match("~a~b~c", "abc")); + + // optional with wildcards + assert(wildcard_match("~a*", "")); + assert(wildcard_match("~a*", "a")); + assert(wildcard_match("~a*", "abc")); + assert(wildcard_match("*~a", "")); + assert(wildcard_match("*~a", "a")); + assert(wildcard_match("*~a", "xya")); + assert(wildcard_match("a~b*c", "ac")); + assert(wildcard_match("a~b*c", "abc")); + assert(wildcard_match("a~b*c", "abxc")); + assert(wildcard_match("a~b*c", "axbc")); + assert(!wildcard_match("a*~bc", "a")); + assert(wildcard_match("a*~bc", "ac")); + assert(wildcard_match("a*~bc", "abc")); + assert(wildcard_match("a*~bc", "axbc")); + + // optional with ? + assert(wildcard_match("~a?", "a")); + assert(wildcard_match("~a?", "x")); + assert(wildcard_match("~a?", "ax")); + assert(wildcard_match("?~a", "x")); + assert(wildcard_match("?~a", "xa")); + assert(wildcard_match("?~a", "a")); + assert(wildcard_match("?~a", "aa")); + + // optional with # + assert(wildcard_match("~a#", "5")); + assert(wildcard_match("~a#", "a5")); + assert(!wildcard_match("~a#", "a")); + assert(!wildcard_match("~a#", "ax")); + assert(wildcard_match("v~#.#", "v.5")); + assert(wildcard_match("v~#.#", "v1.5")); + + // optional with escape + assert(wildcard_match("~\\*", "")); + assert(wildcard_match("~\\*", "*")); + assert(!wildcard_match("~\\*", "x")); + assert(wildcard_match("a~\\*b", "ab")); + assert(wildcard_match("a~\\*b", "a*b")); + assert(!wildcard_match("a~\\*b", "axb")); + + // double optional ~~ + assert(wildcard_match("~~a", "")); + assert(wildcard_match("~~a", "a")); + assert(!wildcard_match("~~a", "aa")); + + // degenerates + assert(wildcard_match("a\\", "a")); + assert(!wildcard_match("a\\", "")); + assert(!wildcard_match("a\\", "x")); + assert(wildcard_match("a~", "a")); + assert(!wildcard_match("a~", "")); + assert(!wildcard_match("a~", "x")); + assert(wildcard_match("a~\\", "a")); + assert(!wildcard_match("a~\\", "")); + assert(!wildcard_match("a~\\", "x")); + + // optional with value_wildcard - basic + assert(wildcard_match("~b", "", true)); + assert(wildcard_match("~b", "b", true)); + assert(wildcard_match("", "~b", true)); + assert(wildcard_match("b", "~b", true)); + assert(wildcard_match("ab", "~ab", true)); + assert(wildcard_match("ab", "~a~b", true)); + assert(wildcard_match("~ab", "ab", true)); + assert(wildcard_match("~ab", "~ab", true)); + assert(wildcard_match("~ab", "~a~b", true)); + assert(wildcard_match("a~b", "~ab", true)); + assert(wildcard_match("a~b", "~a~b", true)); + assert(wildcard_match("~a~b", "~ab", true)); + assert(wildcard_match("~a~b", "~a~b", true)); + + // optional with value_wildcard - with wildcards on rhs + assert(wildcard_match("~a", "*", true)); + assert(wildcard_match("~ab", "*", true)); + assert(wildcard_match("~a~b", "*", true)); + assert(wildcard_match("*", "~a", true)); + assert(wildcard_match("*", "~ab", true)); + assert(wildcard_match("*", "~a~b", true)); + assert(wildcard_match("~a", "?", true)); + assert(wildcard_match("?", "~a", true)); + assert(wildcard_match("~ab", "?", true)); + assert(wildcard_match("~ab", "?b", true)); + assert(wildcard_match("~ab", "a?", true)); + assert(wildcard_match("~ab", "??", true)); + assert(wildcard_match("?", "~ab", true)); + assert(wildcard_match("?b", "~ab", true)); + assert(wildcard_match("a?", "~ab", true)); + assert(wildcard_match("??", "~ab", true)); + assert(wildcard_match("~ab", "?b", true)); + assert(wildcard_match("~abc", "?c", true)); + assert(wildcard_match("~a*", "*", true)); + assert(wildcard_match("*~a", "*", true)); + assert(wildcard_match("*", "~a*", true)); + assert(wildcard_match("*", "*~a", true)); + assert(wildcard_match("ab", "*~c", true)); + assert(wildcard_match("abc", "a?~c", true)); + + // optional on both sides with wildcards + assert(wildcard_match("~a*", "*~b", true)); + assert(wildcard_match("a~b*", "*~cd", true)); + assert(wildcard_match("~ab", "*b", true)); + assert(wildcard_match("~ab", "*ab", true)); + assert(wildcard_match("x~ab", "*ab", true)); + assert(wildcard_match("*b", "~ab", true)); + assert(wildcard_match("*ab", "~ab", true)); + assert(wildcard_match("*ab", "x~ab", true)); + assert(wildcard_match("~a~b", "~c~d", true)); + assert(wildcard_match("~a~b", "~c~da", true)); + assert(wildcard_match("~a~b", "~c~db", true)); + assert(wildcard_match("~a~b", "~c~dab", true)); + assert(wildcard_match("~a~bc", "~c~d", true)); + assert(wildcard_match("~a~bd", "~c~d", true)); + assert(wildcard_match("~a~bcd", "~c~d", true)); + assert(wildcard_match("*a~bc*d", "xxacxxd", true)); + assert(!wildcard_match("*a~bc*de", "xxabcxxd", true)); + assert(!wildcard_match("*a~bc*d", "xxabcxxde", true)); + assert(wildcard_match("*a~bc*d", "~x~x~a~c~x~x~d", true)); + + // case insensitive with optional + assert(wildcard_match_i("~a", "A")); + assert(wildcard_match_i("~aB", "Ab")); + assert(wildcard_match_i("A~b", "aB")); + // multiple wildcards assert(wildcard_match("*l*o", "hello")); assert(wildcard_match("h*l*o", "hello")); @@ -594,8 +855,11 @@ unittest // escape sequences assert(wildcard_match("\\*", "*")); assert(wildcard_match("\\?", "?")); + assert(wildcard_match("\\#", "#")); assert(wildcard_match("\\\\", "\\")); assert(!wildcard_match("\\*", "a")); + assert(!wildcard_match("\\?", "a")); + assert(!wildcard_match("\\#", "5")); assert(wildcard_match("h\\*o", "h*o")); assert(!wildcard_match("h\\*o", "hello")); assert(wildcard_match("\\*\\?\\\\", "*?\\")); @@ -649,4 +913,22 @@ unittest assert(wildcard_match("", "*", true)); assert(wildcard_match("**", "*", true)); assert(wildcard_match("*", "**", true)); + + // digit wildcard tests + assert(wildcard_match("#", "0")); + assert(wildcard_match("#", "5")); + assert(wildcard_match("#", "9")); + assert(!wildcard_match("#", "a")); + assert(!wildcard_match("#", "A")); + assert(!wildcard_match("#", "")); + assert(!wildcard_match("#", "12")); + assert(!wildcard_match("#", "#")); + assert(wildcard_match("#", "#", true)); + assert(!wildcard_match("#", "\\#", true)); + assert(wildcard_match("v#.#", "v1.2")); + assert(!wildcard_match("v#.#", "va.b")); + assert(!wildcard_match("v#.#", "v1.")); + assert(wildcard_match("port-##", "port-42")); + assert(wildcard_match("#*#", "123")); + assert(!wildcard_match("#*#", "abc")); } From 2233eecf97396f04fb31bed1962f2377d64724cb Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 21 Jan 2026 10:08:24 +1000 Subject: [PATCH 060/138] Fixed unit parsing. - Moved the scaling factor outside of the SI unit parsing, since all units can be scaled. - Fixed an issue where some units could incorrectly have SI prefixes added to them. --- src/urt/si/unit.d | 163 ++++++++++++++++++++++++---------------------- 1 file changed, 85 insertions(+), 78 deletions(-) diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index c0c4ae5..8994aef 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -595,85 +595,85 @@ nothrow: if (p == 0) return -1; // invalid exponent - if (const ScaledUnit* su = term in noScaleUnitMap) + size_t offset = 0; + + // parse the scale factor + int e = 0; + if (term[0].is_numeric) + { + ulong sf = term.parse_uint_with_exponent(e, &offset); + preScale *= sf; + } + + if (offset == term.length) + r *= ScaledUnit(Unit(), e); + else if (const ScaledUnit* su = term[offset .. $] in noScaleUnitMap) + { r *= (*su) ^^ (invert ? -p : p); + preScale *= 10.0^^e; + } else { - size_t offset = 0; - - // parse the scale factor - int e = 0; - if (term[0].is_numeric) + // try and parse SI prefix... + switch (term[offset]) { - ulong sf = term.parse_uint_with_exponent(e, &offset); - preScale *= sf; + case 'Y': e += 24; ++offset; break; + case 'Z': e += 21; ++offset; break; + case 'E': e += 18; ++offset; break; + case 'P': e += 15; ++offset; break; + case 'T': e += 12; ++offset; break; + case 'G': e += 9; ++offset; break; + case 'M': e += 6; ++offset; break; + case 'k': e += 3; ++offset; break; + case 'h': e += 2; ++offset; break; + case 'c': e -= 2; ++offset; break; + case 'u': e -= 6; ++offset; break; + case 'n': e -= 9; ++offset; break; + case 'p': e -= 12; ++offset; break; + case 'f': e -= 15; ++offset; break; + case 'a': e -= 18; ++offset; break; + case 'z': e -= 21; ++offset; break; + case 'y': e -= 24; ++offset; break; + case 'm': + // can confuse with metres... so gotta check... + if (offset + 1 < term.length) + e -= 3, ++offset; + break; + case 'd': + if (offset + 1 < term.length && term[offset + 1] == 'a') + { + e += 1, offset += 2; + break; + } + e -= 1, ++offset; + break; + default: + if (offset + "µ".length < term.length && term[offset .. offset + "µ".length] == "µ") + e -= 6, offset += "µ".length; + break; } - if (offset == term.length) - r *= ScaledUnit(Unit(), e); - else + return -1; + + term = term[offset .. $]; + if (const Unit* u = term in unitMap) { - // try and parse SI prefix... - switch (term[offset]) + if (term == "kg") { - case 'Y': e += 24; ++offset; break; - case 'Z': e += 21; ++offset; break; - case 'E': e += 18; ++offset; break; - case 'P': e += 15; ++offset; break; - case 'T': e += 12; ++offset; break; - case 'G': e += 9; ++offset; break; - case 'M': e += 6; ++offset; break; - case 'k': e += 3; ++offset; break; - case 'h': e += 2; ++offset; break; - case 'c': e -= 2; ++offset; break; - case 'u': e -= 6; ++offset; break; - case 'n': e -= 9; ++offset; break; - case 'p': e -= 12; ++offset; break; - case 'f': e -= 15; ++offset; break; - case 'a': e -= 18; ++offset; break; - case 'z': e -= 21; ++offset; break; - case 'y': e -= 24; ++offset; break; - case 'm': - // can confuse with metres... so gotta check... - if (offset + 1 < term.length) - e -= 3, ++offset; - break; - case 'd': - if (offset + 1 < term.length && term[offset + 1] == 'a') - { - e += 1, offset += 2; - break; - } - e -= 1, ++offset; - break; - default: - if (offset + "µ".length < term.length && term[offset .. offset + "µ".length] == "µ") - e -= 6, offset += "µ".length; - break; - } - if (offset == term.length) + // we alrady parsed the 'k', so this string must have been "kkg", which is nonsense return -1; - - term = term[offset .. $]; - if (const Unit* u = term in unitMap) - { - if (term == "kg") - { - // we alrady parsed the 'k' - return -1; - } - r *= ScaledUnit((*u) ^^ (invert ? -p : p), e); - } - else if (const ScaledUnit* su = term in scaledUnitMap) - r *= ScaledUnit(su.unit, su.exp + e) ^^ (invert ? -p : p); - else if (const ScaledUnit* su = term in noScaleUnitMap) - { - r *= (*su) ^^ (invert ? -p : p); - preScale *= 10^^e; } - else - return -1; // string was not taken? + r *= ScaledUnit((*u) ^^ (invert ? -p : p), e); + } + else if (const ScaledUnit* su = term in scaledUnitMap) + r *= ScaledUnit(su.unit, su.exp + e) ^^ (invert ? -p : p); + else if (const ScaledUnit* su = term in noScaleUnitMapSI) + { + r *= (*su) ^^ (invert ? -p : p); + preScale *= 10.0^^e; } + else + return -1; // string was not taken? } if (sep == '/') @@ -986,7 +986,7 @@ immutable Unit[string] unitMap = [ "kg" : Kilogram, "s" : Second, "A" : Ampere, - "°K" : Kelvin, + "K" : Kelvin, "cd" : Candela, "rad" : Radian, @@ -1030,24 +1030,31 @@ immutable ScaledUnit[string] noScaleUnitMap = [ "deg" : Degree, "°C" : Celsius, "°F" : Fahrenheit, - "Ah" : AmpereHour, - "Wh" : WattHour, "cy" : Cycle, - "Hz" : Hertz, "psi" : PSI, - - // questionable... :/ - "VAh" : WattHour, - "varh" : WattHour, + "%" : Percent, + "‰" : Permille, + "‱" : ScaledUnit(Unit(), -4), + "ppm" : ScaledUnit(Unit(), -6), ]; +// these can have SI prefixes immutable ScaledUnit[string] scaledUnitMap = [ - "%" : Percent, - "‰" : Permille, "l" : Litre, "g" : Gram, ]; +// these can have SI prefixes, but scale must be converted to coefficient +immutable ScaledUnit[string] noScaleUnitMapSI = [ + "Ah" : AmpereHour, + "Wh" : WattHour, + "Hz" : Hertz, + + // questionable... :/ + "VAh" : WattHour, + "varh" : WattHour, +]; + int takePower(ref const(char)[] s) pure { size_t e = s.findFirst('^'); From 569e27bd85db0b5ea78729a2c192e4dc1aae1935 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sat, 24 Jan 2026 16:21:41 +1000 Subject: [PATCH 061/138] Trim underscore from enum keys, which often exist to disambiguate from keywords. - Tweak `trim` to accept a predicate (default to `is_whitespace`) - Tweak `uni_seq_len` to remove some redundant logic. - Fix `uni_compare` and simplify the implementation a bit. --- src/urt/meta/package.d | 5 +++- src/urt/string/package.d | 10 +++---- src/urt/string/uni.d | 65 +++++++++++++++++++++------------------- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index bb6e8d9..c703fc5 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -391,7 +391,7 @@ private: return total; }(); - enum MakeKI(ushort i) = KI(enum_members[i], i); + enum MakeKI(ushort i) = KI(trim_key!(enum_members[i]), i); enum MakeVI(ushort i) = VI(__traits(getMember, E, enum_members[i]), i); enum GetValue(size_t i) = by_value[i].v; enum GetKeyRedirect(size_t i) = inv_val[by_key[i].i]; @@ -487,6 +487,9 @@ VoidEnumInfo* make_enum_info(T)(const(char)[] name, const(char)[][] keys, T[] va private: +import urt.string : trim; +enum trim_key(string key) = key.trim!(c => c == '_'); + template is_same(alias a, alias b) { static if (!is(typeof(&a && &b)) // at least one is an rvalue diff --git a/src/urt/string/package.d b/src/urt/string/package.d index 0e39201..08025ea 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -223,25 +223,25 @@ bool endsWith(const(char)[] s, const(char)[] suffix) pure return cmp(s[$ - suffix.length .. $], suffix) == 0; } -inout(char)[] trim(bool Front = true, bool Back = true)(inout(char)[] s) pure +inout(char)[] trim(alias pred = is_whitespace, bool Front = true, bool Back = true)(inout(char)[] s) pure { size_t first = 0, last = s.length; static if (Front) { - while (first < s.length && is_whitespace(s.ptr[first])) + while (first < s.length && pred(s.ptr[first])) ++first; } static if (Back) { - while (last > first && is_whitespace(s.ptr[last - 1])) + while (last > first && pred(s.ptr[last - 1])) --last; } return s.ptr[first .. last]; } -alias trimFront = trim!(true, false); +alias trimFront(alias pred = is_whitespace) = trim!(pred, true, false); -alias trimBack = trim!(false, true); +alias trimBack(alias pred = is_whitespace) = trim!(pred, false, true); inout(char)[] trimComment(char Delimiter)(inout(char)[] s) pure { diff --git a/src/urt/string/uni.d b/src/urt/string/uni.d index ce7f7d1..8447be9 100644 --- a/src/urt/string/uni.d +++ b/src/urt/string/uni.d @@ -6,36 +6,38 @@ import urt.traits : is_some_char; pure nothrow @nogc: -size_t uni_seq_len(const(char)[] s) +size_t uni_seq_len(const(char)[] str) { - if (s.length == 0) - return 0; + debug assert(str.length > 0); + + const(char)* s = str.ptr; if (s[0] < 0x80) // 1-byte sequence: 0xxxxxxx return 1; else if ((s[0] & 0xE0) == 0xC0) // 2-byte sequence: 110xxxxx 10xxxxxx - return (s.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + return (str.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; else if ((s[0] & 0xF0) == 0xE0) // 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx - return (s.length >= 3 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) ? 3 : - (s.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + return (str.length >= 3 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) ? 3 : + (str.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; else if ((s[0] & 0xF8) == 0xF0) // 4-byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - return (s.length >= 4 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80 && (s[3] & 0xC0) == 0x80) ? 4 : - (s.length >= 3 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) ? 3 : - (s.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + return (str.length >= 4 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80 && (s[3] & 0xC0) == 0x80) ? 4 : + (str.length >= 3 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) ? 3 : + (str.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; return 1; // Invalid UTF-8 sequence } -size_t uni_seq_len(const(wchar)[] s) +size_t uni_seq_len(const(wchar)[] str) { - if (s.length == 0) - return 0; - if (s[0] >= 0xD800 && s[0] < 0xDC00 && s.length >= 2 && s[1] >= 0xDC00 && s[1] < 0xE000) + debug assert(str.length > 0); + + const(wchar)* s = str.ptr; + if (s[0] >= 0xD800 && s[0] < 0xDC00 && str.length >= 2 && s[1] >= 0xDC00 && s[1] < 0xE000) return 2; // Surrogate pair: 110110xxxxxxxxxx 110111xxxxxxxxxx return 1; } pragma(inline, true) size_t uni_seq_len(const(dchar)[] s) - => s.length > 0; + => 1; size_t uni_strlen(C)(const(C)[] s) if (is_some_char!C) @@ -552,38 +554,41 @@ int uni_compare(T, U)(const(T)[] s1, const(U)[] s2) // TODO: this is crude and insufficient; doesn't handle compound diacritics, etc (needs a NFKC normalisation step) - while (p1 < p1end && p2 < p2end) + while (true) { - dchar a = *p1; + // return int.min/max in the case that the strings are a sub-string of the other so the caller can detect this case + if (p1 >= p1end) + return p2 < p2end ? int.min : 0; + if (p2 >= p2end) + return int.max; + + dchar a = *p1, b = *p2; + if (a < 0x80) { - dchar b = *p2; if (a != b) - { - if (b >= 0x80) - { - size_t _; - b = next_dchar(p2[0 .. p2end - p2], _); - } - return cast(int)a - cast(int)b; - } + return int(a) - int(b); ++p1; + p2 += b < 0x80 ? 1 : p2[0 .. p2end - p2].uni_seq_len; + } + else if (b < 0x80) + { + if (a != b) + return int(a) - int(b); + p1 += p1[0 .. p1end - p1].uni_seq_len; ++p2; } else { size_t al, bl; a = next_dchar(p1[0 .. p1end - p1], al); - dchar b = next_dchar(p2[0 .. p2end - p2], bl); + b = next_dchar(p2[0 .. p2end - p2], bl); if (a != b) return cast(int)a - cast(int)b; p1 += al; - p2 += al; + p2 += bl; } } - - // return int.min/max in the case that the strings are a sub-string of the other so the caller can detect this case - return (p1 < p1end) ? int.max : (p2 < p2end) ? int.min : 0; } int uni_compare_i(T, U)(const(T)[] s1, const(U)[] s2) From f7de5c1c528a2a141e9a214515498d71a79e7bd4 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sat, 24 Jan 2026 20:58:23 +1000 Subject: [PATCH 062/138] Add a function to adjust a Quantity scale. Minimise error accumulation from floating point precision loss from unnecessary arithmetic. --- src/urt/si/quantity.d | 90 ++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index df9775f..0d5a6b2 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -69,7 +69,7 @@ nothrow @nogc: assert(isCompatible(b), "Incompatible units!"); else static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); - value = adjustScale(b); + value = adjust_scale(b); } } @@ -97,7 +97,7 @@ nothrow @nogc: assert(isCompatible(b), "Incompatible units!"); else static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); - value = adjustScale(b); + value = adjust_scale(b); } } @@ -132,7 +132,7 @@ nothrow @nogc: static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); Quantity!(TypeForOp!(op, T, U), _unit) r; - r.value = mixin("value " ~ op ~ " adjustScale(b)"); + r.value = mixin("value " ~ op ~ " adjust_scale(b)"); static if (Dynamic) r.unit = unit; return r; @@ -211,7 +211,7 @@ nothrow @nogc: assert(isCompatible(r), "Incompatible units!"); else static assert(IsCompatible!_U, "Incompatible units: ", r.unit.toString, " and ", unit.toString); - r.value = cast(U)r.adjustScale(this); + r.value = cast(U)r.adjust_scale(this); static if (T.Dynamic) r.unit = unit; return r; @@ -258,7 +258,7 @@ nothrow @nogc: rhs = rhs*rScale + rTrans; }} else - rhs = adjustScale(rh); + rhs = adjust_scale(rh); compare: static if (epsilon == 0) @@ -287,7 +287,19 @@ nothrow @nogc: } else Quantity!(T, ScaledUnit(unit.unit)) r; - r.value = r.adjustScale(this); + r.value = r.adjust_scale(this); + return r; + } + + Quantity!Ty adjust_scale(Ty = T)(ScaledUnit su) const pure + { + Quantity!Ty r; + r.unit = su; + assert(r.isCompatible(this), "Incompatible units!"); + if (su == unit) + r.value = cast(Ty)this.value; + else + r.value = r.adjust_scale(this); return r; } @@ -354,40 +366,48 @@ nothrow @nogc: } private: - T adjustScale(U, ScaledUnit _U)(Quantity!(U, _U) b) const pure + T adjust_scale(U, ScaledUnit _U)(Quantity!(U, _U) b) const pure { - static if (Dynamic) - { - auto lScale = unit.scale!true(); - auto lTrans = unit.offset!true(); - } + static if (!Dynamic && !b.Dynamic && unit == b.unit) + return cast(T)b.value; else { - enum lScale = unit.scale!true(); - enum lTrans = unit.offset!true(); - } - static if (b.Dynamic) - { - auto rScale = b.unit.scale(); - auto rTrans = b.unit.offset(); - } - else - { - enum rScale = b.unit.scale(); - enum rTrans = b.unit.offset(); - } + if (unit == b.unit) + return cast(T)b.value; - static if (Dynamic || b.Dynamic) - { - auto scale = lScale*rScale; - auto trans = lTrans + lScale*rTrans; - } - else - { - enum scale = lScale*rScale; - enum trans = lTrans + lScale*rTrans; + static if (Dynamic) + { + auto lScale = unit.scale!true(); + auto lTrans = unit.offset!true(); + } + else + { + enum lScale = unit.scale!true(); + enum lTrans = unit.offset!true(); + } + static if (b.Dynamic) + { + auto rScale = b.unit.scale(); + auto rTrans = b.unit.offset(); + } + else + { + enum rScale = b.unit.scale(); + enum rTrans = b.unit.offset(); + } + + static if (Dynamic || b.Dynamic) + { + auto scale = lScale*rScale; + auto trans = lTrans + lScale*rTrans; + } + else + { + enum scale = lScale*rScale; + enum trans = lTrans + lScale*rTrans; + } + return cast(T)(b.value*scale + trans); } - return cast(T)(b.value*scale + trans); } } From 5d854a89cc3d7916a48b7efba527f21c0b98f7fd Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 25 Jan 2026 19:27:04 +1000 Subject: [PATCH 063/138] Add some helpers to StringCacheBuilder --- src/urt/string/string.d | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/urt/string/string.d b/src/urt/string/string.d index 1cf61e2..e3349c1 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -45,6 +45,7 @@ nothrow @nogc: ushort add_string(const(char)[] s) pure { assert(s.length <= MaxStringLen, "String too long"); + assert(_offset + s.length + 2 + (s.length & 1) <= _buffer.length, "Not enough space in buffer"); if (__ctfe) { version (LittleEndian) @@ -69,6 +70,15 @@ nothrow @nogc: return result; } + size_t used() const pure + => _offset; + + size_t remaining() const pure + => _buffer.length - _offset; + + bool full() const pure + => _offset == _buffer.length; + private: char[] _buffer; ushort _offset; From 0efbdde6382783953854b7486f2387a7b580aa8e Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 28 Jan 2026 15:22:17 +1000 Subject: [PATCH 064/138] Array!char.concat/append was extended to support the same set of arguments as string concatenation. --- src/urt/array.d | 136 ++++++++++++++++++++++++++++++++++------ src/urt/string/string.d | 22 +++---- 2 files changed, 127 insertions(+), 31 deletions(-) diff --git a/src/urt/array.d b/src/urt/array.d index 6674151..4f61066 100644 --- a/src/urt/array.d +++ b/src/urt/array.d @@ -1,7 +1,7 @@ module urt.array; import urt.mem; -import urt.traits : is_some_char; +import urt.traits : is_some_char, is_primitive, is_trivial; nothrow @nogc: @@ -314,17 +314,120 @@ nothrow @nogc: } // manipulation - ref Array!(T, EmbedCount) concat(Things...)(auto ref Things things) + static if (!is_some_char!T) { - reserve(_length + things.length); - static foreach (i; 0 .. things.length) + alias concat = append; // TODO: REMOVE THIS ALIAS, we phase out the old name... + + ref Array!(T, EmbedCount) append(Things...)(auto ref Things things) { - static if (is(T == class) || is(T == interface)) - ptr[_length++] = things[i]; - else - emplace!T(&ptr[_length++], forward!(things[i])); + size_t ext_len = 0; + static foreach (i; 0 .. things.length) + { + static if (is(Things[i] == U[], U) && is(U : T)) + ext_len += things[i].length; + else static if (is(Things[i] == U[N], U, size_t N) && is(U : T)) + ext_len += N; + else static if (is(Things[i] : T)) + ext_len += 1; + else + static assert(false, "Invalid type for concat"); + } + reserve(_length + ext_len); + static foreach (i; 0 .. things.length) + { + static if (is(Things[i] == V[], V) && is(V : T)) + { + static if (copy_elements) + { + foreach (ref el; things[i]) + ptr[_length++] = el; + } + else + { + foreach (ref el; things[i]) + emplace!T(&ptr[_length++], el); + } + } + else static if (is(Things[i] == V[M], V, size_t M) && is(V : T)) + { + static if (copy_elements) + { + foreach (ref el; things[i]) + ptr[_length++] = el; + } + else + { + foreach (ref el; things[i]) + emplace!T(&ptr[_length++], el); + } + } + else static if (is(Things[i] : T)) + { + static if (copy_elements) + ptr[_length++] = things[i]; + else + emplace!T(&ptr[_length++], forward!(things[i])); + } + } + return this; } - return this; + } + else + { + // char arrays are really just a string buffer, and so we'll expand the capability of concat to match what `MutableString` accepts... + static assert(is(T == char), "TODO: wchar and dchar"); // needs buffer length counting helpers + + // TODO: string's have this function `concat` which clears the string first, and that's different than Array + // we need to tighten this up! + ref Array!(T, EmbedCount) concat(Things...)(auto ref Things things) + { + clear(); + append(forward!things); + return this; + } + + ref Array!(T, EmbedCount) append(Things...)(auto ref Things things) + { + import urt.string.format : _concat = concat; + + size_t ext_len = _concat(null, things).length; + reserve(_length + ext_len); + _concat(ptr[_length .. _length + ext_len], forward!things); + _length += ext_len; + return this; + } + + ref Array!(T, EmbedCount) append_format(Things...)(const(char)[] format, auto ref Things args) + { + import urt.string.format : _format = format; + + size_t ext_len = _format(null, format, args).length; + reserve(_length + ext_len); + _format(ptr[_length .. _length + ext_len], format, forward!args); + _length += ext_len; + return this; + } + } + + T[] extend(bool do_init = true)(size_t length) + { + assert(_length + length <= uint.max); + + size_t old_len = _length; + reserve(_length + length); + static if (do_init) + { + foreach (i; _length .. _length + length) + { + // TODO: replace with palcement new... + static if (copy_elements) + ptr[i] = T.init; + else + emplace!T(&ptr[i]); + } + } + _length += cast(uint)length; + return ptr[old_len .. _length]; } bool empty() const @@ -592,18 +695,9 @@ nothrow @nogc: return [x, y]; } - void opOpAssign(string op : "~", U)(auto ref U el) - if (is(U : T)) - { - pushBack(forward!el); - } - - void opOpAssign(string op : "~", U)(U[] arr) - if (is(U : T)) + void opOpAssign(string op : "~", U)(auto ref U rh) { - reserve(_length + arr.length); - foreach (ref e; arr) - pushBack(e); + append(forward!rh); } void reserve(size_t count) @@ -700,6 +794,8 @@ nothrow @nogc: } private: + enum copy_elements = is(T == class) || is(T == interface) || is_primitive!T || is_trivial!T; + T* ptr; uint _length; diff --git a/src/urt/string/string.d b/src/urt/string/string.d index e3349c1..be23b03 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -700,9 +700,9 @@ nothrow @nogc: return this; } - ref MutableString!Embed appendFormat(Things...)(const(char)[] format, auto ref Things args) + ref MutableString!Embed append_format(Things...)(const(char)[] format, auto ref Things args) { - insertFormat(length(), format, forward!args); + insert_format(length(), format, forward!args); return this; } @@ -718,7 +718,7 @@ nothrow @nogc: { if (ptr) writeLength(0); - insertFormat(0, format, forward!args); + insert_format(0, format, forward!args); return this; } @@ -750,7 +750,7 @@ nothrow @nogc: return this; } - ref MutableString!Embed insertFormat(Things...)(size_t offset, const(char)[] format, auto ref Things args) + ref MutableString!Embed insert_format(Things...)(size_t offset, const(char)[] format, auto ref Things args) { import urt.string.format : _format = format; import urt.util : max, next_power_of_2; @@ -923,11 +923,11 @@ unittest m.append(" Text"); assert(m == "X! More Text"); - // appendFormat + // append_format m.clear(); - m.appendFormat("Value: {0}", 123); + m.append_format("Value: {0}", 123); assert(m == "Value: 123"); - m.appendFormat(", String: {0}", "abc"); + m.append_format(", String: {0}", "abc"); assert(m == "Value: 123, String: abc"); // concat @@ -948,13 +948,13 @@ unittest m.insert(m.length, "!"); // End (same as append) assert(m == "My Super String!"); - // insertFormat + // insert_format m = "Data"; - m.insertFormat(0, "[{0}] ", 1); + m.insert_format(0, "[{0}] ", 1); assert(m == "[1] Data"); - m.insertFormat(4, "\\{{0}\\}", "fmt"); + m.insert_format(4, "\\{{0}\\}", "fmt"); assert(m == "[1] {fmt}Data"); - m.insertFormat(m.length, " End"); + m.insert_format(m.length, " End"); assert(m == "[1] {fmt}Data End"); // erase From 361a2839f77b8936e8ceebcc45e5186ea06ca55f Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 30 Jan 2026 09:37:07 +1000 Subject: [PATCH 065/138] Fix variant opCmp --- src/urt/variant.d | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/urt/variant.d b/src/urt/variant.d index d6aebdd..8b2a944 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -426,21 +426,19 @@ nothrow @nogc: static double asDoubleWithBool(ref const Variant v) => v.isBool() ? double(v.asBool()) : v.asDouble(); - if (a.isQuantity || b.isQuantity) + uint aunit = a.isQuantity ? a.count : 0; + uint bunit = b.isQuantity ? b.count : 0; + if (aunit || bunit) { // we can't compare different units - uint aunit = a.isQuantity ? (a.count & 0xFFFFFF) : 0; - uint bunit = b.isQuantity ? (b.count & 0xFFFFFF) : 0; - if (aunit != bunit) + if ((aunit & 0xFFFFFF) != (bunit & 0xFFFFFF)) { - r = aunit - bunit; + r = (aunit & 0xFFFFFF) - (bunit & 0xFFFFFF); break; } // matching units, but we'll only do quantity comparison if there is some scaling - ubyte ascale = a.isQuantity ? (a.count >> 24) : 0; - ubyte bscale = b.isQuantity ? (b.count >> 24) : 0; - if (ascale || bscale) + if ((aunit >> 24) != (bunit >> 24)) { Quantity!double aq = a.isQuantity ? a.asQuantity!double() : Quantity!double(asDoubleWithBool(*a)); Quantity!double bq = b.isQuantity ? b.asQuantity!double() : Quantity!double(asDoubleWithBool(*b)); @@ -449,7 +447,7 @@ nothrow @nogc: } } - if (a.flags & Flags.FloatFlag || b.flags & Flags.FloatFlag) + if (a.flags & Flags.DoubleFlag || b.flags & Flags.DoubleFlag) { // float comparison // TODO: determine if float/bool comparison seems right? is: -1 < false < 0.9 < true < 1.1? From 2be4ae3169134740b97d5403fc35be830140a19f Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 1 Feb 2026 14:02:41 +1000 Subject: [PATCH 066/138] Improve the DateTime parsing, and supported DateTime -> SysTime conversion. --- src/urt/time.d | 224 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 173 insertions(+), 51 deletions(-) diff --git a/src/urt/time.d b/src/urt/time.d index 20857dc..7cca87f 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -28,7 +28,8 @@ enum Day : ubyte enum Month : ubyte { - January = 1, + Unspecified = 0, + January, February, March, April, @@ -63,10 +64,15 @@ pure nothrow @nogc: T opCast(T)() const if (is(T == Time!c, Clock c) && c != clock) { - static if (clock == Clock.Monotonic && c == Clock.SystemTime) - return SysTime(ticks + ticksSinceBoot); + static if (is(T == Time!c, Clock c) && c != clock) + { + static if (clock == Clock.Monotonic && c == Clock.SystemTime) + return SysTime(ticks + ticksSinceBoot); + else + return MonoTime(ticks - ticksSinceBoot); + } else - return MonoTime(ticks - ticksSinceBoot); + static assert(false, "constraint out of sync"); } bool opEquals(Time!clock b) const @@ -468,27 +474,31 @@ pure nothrow @nogc: nsecs -= digit * digit_multipliers[m++]; } } + // TODO: timezone suffix? return offset; } ptrdiff_t fromString(const(char)[] s) { - import urt.conv : parse_int; + import urt.conv : parse_int, parse_uint; import urt.string.ascii : ieq, is_numeric, is_whitespace, to_lower; - if (s.length < 14) - return -1; + month = Month.Unspecified; + day = 0; + hour = 0; + minute = 0; + second = 0; + ns = 0; + size_t offset = 0; // parse year - if (s[0..2].ieq("bc")) + if (s.length >= 2 && s[0..2].ieq("bc")) { offset = 2; - if (s[2] == ' ') + if (s.length >= 3 && s[2] == ' ') ++offset; } - if (s[offset] == '+') - return -1; size_t len; long value = s[offset..$].parse_int(&len); if (len == 0) @@ -504,13 +514,15 @@ pure nothrow @nogc: year = cast(short)value; offset += len; - if (s[offset] != '-' && s[offset] != '/') - return -1; + if (offset == s.length || (s[offset] != '-' && s[offset] != '/')) + return offset; // parse month value = s[++offset..$].parse_int(&len); if (len == 0) { + if (s.length < 3) + return -1; foreach (i; 0..12) { if (s[offset..offset+3].ieq(g_month_names[i])) @@ -528,8 +540,8 @@ pure nothrow @nogc: month = cast(Month)value; offset += len; - if (s[offset] != '-' && s[offset] != '/') - return -1; + if (offset == s.length || (s[offset] != '-' && s[offset] != '/')) + return offset; // parse day value = s[++offset..$].parse_int(&len); @@ -538,8 +550,8 @@ pure nothrow @nogc: day = cast(ubyte)value; offset += len; - if (offset >= s.length || (s[offset] != 'T' && s[offset] != ' ')) - return -1; + if (offset == s.length || (s[offset] != 'T' && s[offset] != ' ')) + return offset; // parse hour value = s[++offset..$].parse_int(&len); @@ -548,49 +560,116 @@ pure nothrow @nogc: hour = cast(ubyte)value; offset += len; - if (offset >= s.length || s[offset++] != ':') - return -1; + if (offset == s.length) + return offset; - // parse minute - value = s[offset..$].parse_int(&len); - if (len != 2 || value < 0 || value > 59) - return -1; - minute = cast(ubyte)value; - offset += len; + if (s[offset] == ':') + { + // parse minute + value = s[++offset..$].parse_int(&len); + if (len != 2 || value < 0 || value > 59) + return -1; + minute = cast(ubyte)value; + offset += len; - if (offset >= s.length || s[offset++] != ':') - return -1; + if (offset == s.length) + return offset; - // parse second - value = s[offset..$].parse_int(&len); - if (len != 2 || value < 0 || value > 59) - return -1; - second = cast(ubyte)value; - offset += len; + if (s[offset] == ':') + { + // parse second + value = s[++offset..$].parse_int(&len); + if (len != 2 || value < 0 || value > 59) + return -1; + second = cast(ubyte)value; + offset += len; - ns = 0; - if (offset < s.length && s[offset] == '.') - { - // parse fractions - ++offset; - uint multiplier = 100_000_000; - while (offset < s.length && multiplier > 0 && s[offset].is_numeric) + if (offset < s.length && s[offset] == '.') + { + // parse fractions + ++offset; + uint multiplier = 100_000_000; + while (offset < s.length && multiplier > 0 && s[offset].is_numeric) + { + ns += (s[offset++] - '0') * multiplier; + multiplier /= 10; + } + if (multiplier == 100_000_000) + return offset-1; // no number after the dot + } + } + else if (s[offset] == '.') { - ns += (s[offset++] - '0') * multiplier; - multiplier /= 10; + // fraction of minute + assert(false, "TODO"); } - if (multiplier == 100_000_000) - return -1; // no number after the dot + } + else if (s[offset] == '.') + { + // fraction of hour + assert(false, "TODO"); } - if (offset < s.length && s[offset].to_lower == 'z') + if (offset == s.length) + return offset; + + if (s[offset].to_lower == 'z') { - ++offset; // TODO: UTC timezone designator... - assert(false, "TODO: we need to know our local timezone..."); +// assert(false, "TODO: we need to know our local timezone..."); + + return offset + 1; } - return offset; + if (s[offset] != '-' && s[offset] != '+') + return offset; + + size_t tz_offset = offset + 1; + + // parse timezone (00:00) + int tz_hr, tz_min; + + value = s[tz_offset..$].parse_uint(&len); + if (len == 0) + return offset; + + if (len == 4) + { + if (value > 2359) + return -1; + tz_min = cast(int)(value % 100); + if (tz_min > 59) + return -1; + tz_hr = cast(int)(value / 100); + tz_offset += 4; + } + else + { + if (len != 2 || value > 59) + return -1; + + tz_hr = cast(int)value; + tz_offset += 2; + + if (tz_offset < s.length && s[tz_offset] == ':') + { + value = s[tz_offset+1..$].parse_uint(&len); + if (len != 0) + { + if (len != 2 || value > 59) + return -1; + tz_min = cast(int)value; + tz_offset += 3; + } + } + } + + if (s[offset] == '-') + tz_hr = -tz_hr; + +// assert(false, "TODO: we need to know our local timezone..."); + + return tz_offset; } } @@ -663,7 +742,14 @@ SysTime getSysTime() SysTime getSysTime(DateTime time) pure { - assert(false, "TODO: convert to SysTime..."); + version (Windows) + return dateTimeToFileTime(time); + else version (Posix) + { + assert(false, "TODO"); + } + else + static assert(false, "TODO"); } DateTime getDateTime() @@ -896,8 +982,18 @@ unittest assert(dt.fromString("BC100-AUG-15 12:34:56.789") == 25); assert(dt.fromString("BC 10000-AUG-15 12:34:56.789123456") == 34); assert(dt.fromString("1-1-1 01:01:01") == 14); - assert(dt.fromString("1-1-1 01:01:01.") == -1); - assert(dt.fromString("2025-01-01") == -1); + assert(dt.fromString("1-1-1 01:01:01.") == 14); + assert(dt.fromString("2025") == 4); + assert(dt.fromString("2025-10") == 7); + assert(dt.fromString("2025-01-01") == 10); + assert(dt.fromString("2025-01-01 00") == 13); + assert(dt.fromString("2025-01-01 00:10") == 16); + assert(dt.fromString("2025-01-01 00:10z") == 17); + assert(dt.fromString("2025-01-01 00+00:00") == 19); + assert(dt.fromString("2025-01-01 00-1030") == 18); + assert(dt.fromString("2025-01-01 00+08") == 16); + + assert(dt.fromString("BC -10") == -1); assert(dt.fromString("2024-0-15 12:34:56") == -1); assert(dt.fromString("2024-13-15 12:34:56") == -1); assert(dt.fromString("2024-1-0 12:34:56") == -1); @@ -934,6 +1030,32 @@ version (Windows) return dt; } + + SysTime dateTimeToFileTime(DateTime dt) pure + { + version (BigEndian) + static assert(false, "Only works in little endian!"); + + SYSTEMTIME stime; + stime.wYear = dt.year; + stime.wMonth = cast(ushort)dt.month; + stime.wDayOfWeek = cast(ushort)dt.wday; + stime.wDay = cast(ushort)dt.day; + stime.wHour = cast(ushort)dt.hour; + stime.wMinute = cast(ushort)dt.minute; + stime.wSecond = cast(ushort)dt.second; + stime.wMilliseconds = cast(ushort)(dt.ns / 1_000_000); + + SysTime ftime; + alias PureHACK = extern(Windows) BOOL function(const(SYSTEMTIME)*, FILETIME*) pure nothrow @nogc; + if (!(cast(PureHACK)&SystemTimeToFileTime)(&stime, cast(FILETIME*)&ftime)) + assert(false, "TODO: WHAT TO DO?"); + + debug assert(ftime.ticks % 10_000_000 == (dt.ns / 1_000_000) * 10_000); + ftime.ticks = ftime.ticks - ftime.ticks % 10_000_000 + dt.ns / 100; + + return ftime; + } } else version (Posix) { From 114e944c7a75dffd04cba0cce32413038565cd0d Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 1 Feb 2026 14:03:26 +1000 Subject: [PATCH 067/138] Move function out of urt. --- src/urt/string/package.d | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/urt/string/package.d b/src/urt/string/package.d index 08025ea..4fd0e24 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -243,19 +243,6 @@ alias trimFront(alias pred = is_whitespace) = trim!(pred, true, false); alias trimBack(alias pred = is_whitespace) = trim!(pred, false, true); -inout(char)[] trimComment(char Delimiter)(inout(char)[] s) pure -{ - size_t i = 0; - for (; i < s.length; ++i) - { - if (s[i] == Delimiter) - break; - } - while(i > 0 && (s[i-1] == ' ' || s[i-1] == '\t')) - --i; - return s[0 .. i]; -} - inout(char)[] takeLine(ref inout(char)[] s) pure { for (size_t i = 0; i < s.length; ++i) From fe4b7002043120d391c7f04ecf1383f93fc5a0a6 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 1 Feb 2026 14:00:54 +1000 Subject: [PATCH 068/138] Parse e-notation, delete the `with_decimal` ones, normalise, and reorganise these implementations. --- src/urt/conv.d | 280 +++++++++++++++++++++--------------------- src/urt/format/json.d | 33 +++-- src/urt/math.d | 1 + src/urt/variant.d | 8 +- 4 files changed, 158 insertions(+), 164 deletions(-) diff --git a/src/urt/conv.d b/src/urt/conv.d index 160eb11..dfdc9f0 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -8,47 +8,52 @@ nothrow @nogc: // on error or not-a-number cases, bytes_taken will contain 0 -long parse_int(const(char)[] str, size_t* bytes_taken = null, int base = 10) pure -{ - size_t i = 0; - bool neg = false; - if (str.length > 0) - { - char c = str.ptr[0]; - neg = c == '-'; - if (neg || c == '+') - i++; - } - - ulong value = str.ptr[i .. str.length].parse_uint(bytes_taken, base); +long parse_int(const(char)[] str, size_t* bytes_taken = null, uint base = 10) pure +{ + const(char)* s = str.ptr, e = s + str.length, p = s; + uint neg = parse_sign(p, e); + ulong value = p[0 .. e - p].parse_uint(bytes_taken, base); if (bytes_taken && *bytes_taken != 0) - *bytes_taken += i; - return neg ? -cast(long)value : cast(long)value; + *bytes_taken += p - s; + return neg ? -long(value) : long(value); } -long parse_int_with_decimal(const(char)[] str, out ulong fixed_point_divisor, size_t* bytes_taken = null, int base = 10) pure +long parse_int_with_base(const(char)[] str, size_t* bytes_taken = null) pure { - size_t i = 0; - bool neg = false; + const(char)* s = str.ptr, e = s + str.length, p = s; + uint neg = parse_sign(p, e); + uint base = parse_base_prefix(p, e); + ulong i = p[0 .. e - p].parse_uint(bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += p - s; + return neg ? -long(i) : long(i); +} - if (str.length > 0) - { - char c = str.ptr[0]; - neg = c == '-'; - if (neg || c == '+') - i++; - } +long parse_int_with_exponent(const(char)[] str, out int exponent, size_t* bytes_taken = null, uint base = 10) pure +{ + const(char)* s = str.ptr, e = s + str.length, p = s; + uint neg = parse_sign(p, e); + ulong value = p[0 .. e - p].parse_uint_with_exponent(exponent, bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += p - s; + return neg ? -long(value) : long(value); +} - ulong value = str[i .. str.length].parse_uint_with_decimal(fixed_point_divisor, bytes_taken, base); +long parse_int_with_exponent_and_base(const(char)[] str, out int exponent, out uint base, size_t* bytes_taken = null) pure +{ + const(char)* s = str.ptr, e = s + str.length, p = s; + uint neg = parse_sign(p, e); + base = parse_base_prefix(p, e); + ulong value = p[0 .. e - p].parse_uint_with_exponent(exponent, bytes_taken, base); if (bytes_taken && *bytes_taken != 0) - *bytes_taken += i; - return neg ? -cast(long)value : cast(long)value; + *bytes_taken += p - s; + return neg ? -long(value) : long(value); } -ulong parse_uint(const(char)[] str, size_t* bytes_taken = null, int base = 10) pure +ulong parse_uint(const(char)[] str, size_t* bytes_taken = null, uint base = 10) pure { - assert(base > 1 && base <= 36, "Invalid base"); + debug assert(base > 1 && base <= 36, "Invalid base"); ulong value = 0; @@ -81,11 +86,19 @@ ulong parse_uint(const(char)[] str, size_t* bytes_taken = null, int base = 10) p return value; } -ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* bytes_taken = null, uint base = 10) pure +ulong parse_uint_with_base(const(char)[] str, size_t* bytes_taken = null) pure { - import urt.util : ctz, is_power_of_2; + const(char)* s = str.ptr, e = s + str.length, p = s; + uint base = parse_base_prefix(p, e); + ulong i = p[0 .. e - p].parse_uint(bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += p - s; + return i; +} - assert(base > 1 && base <= 36, "Invalid base"); +ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* bytes_taken = null, uint base = 10) pure +{ + debug assert(base > 1 && base <= 36, "Invalid base"); const(char)* s = str.ptr; const(char)* e = s + str.length; @@ -129,8 +142,11 @@ ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* byte } // number has no decimal point, tail zeroes are positive exp - exp = digits ? zero_seq : 0; - goto done; + if (!digits) + goto nothing; + + exp = zero_seq; + goto check_exp; parse_decimal: for (; s < e; ++s) @@ -159,120 +175,83 @@ parse_decimal: zero_seq = 0; } if (!digits) - exp = 0; // didn't parse any digits; reset exp to 0 + goto nothing; -done: - exponent = exp; - if (bytes_taken) - *bytes_taken = s - str.ptr; - return value; -} - -unittest -{ - int e; - size_t taken; - assert("0001023000".parse_uint_with_exponent(e, &taken, 10) == 1023 && e == 3 && taken == 10); - assert("0.0012003000".parse_uint_with_exponent(e, &taken, 10) == 12003 && e == -7 && taken == 12); - assert("00010.23000".parse_uint_with_exponent(e, &taken, 10) == 1023 && e == -2 && taken == 11); - assert("00012300.0".parse_uint_with_exponent(e, &taken, 10) == 123 && e == 2 && taken == 10); - assert("00100.00230".parse_uint_with_exponent(e, &taken, 10) == 1000023 && e == -4 && taken == 11); - assert("0.0".parse_uint_with_exponent(e, &taken, 10) == 0 && e == 0 && taken == 3); - assert(".01".parse_uint_with_exponent(e, &taken, 10) == 0 && e == 0 && taken == 0); -} - -ulong parse_uint_with_decimal(const(char)[] str, out ulong fixed_point_divisor, size_t* bytes_taken = null, int base = 10) pure -{ - assert(base > 1 && base <= 36, "Invalid base"); - - ulong value = 0; - ulong divisor = 1; - - const(char)* s = str.ptr; - const(char)* e = s + str.length; - - // TODO: we could optimise the common base <= 10 case... - - for (; s < e; ++s) +check_exp: + // check for exponent part + if (s < e && (*s == 'e' || *s == 'E')) { - char c = *s; - - if (c == '.') + ++s; + bool exp_neg = false; + if (s < e) { - if (s == str.ptr) - goto done; - ++s; - goto parse_decimal; + char c = *s; + exp_neg = c == '-'; + if (exp_neg || c == '+') + ++s; } - uint digit = get_digit(c); - if (digit >= base) - break; - value = value*base + digit; - } - goto done; - -parse_decimal: - for (; s < e; ++s) - { - uint digit = get_digit(*s); - if (digit >= base) + int exp_value = 0; + const(char)* t = s; + for (; t < e; ++t) { - // if i == 1, then the first char was a '.' and the next was not numeric, so this is not a number! - if (s == str.ptr + 1) - s = str.ptr; - break; + uint digit = *t - '0'; + if (digit > 9) + break; + exp_value = exp_value * 10 + digit; + } + if (t > s) + { + exp += exp_neg ? -exp_value : exp_value; + s = t; } - value = value*base + digit; - divisor *= base; } done: - fixed_point_divisor = divisor; + exponent = exp; if (bytes_taken) *bytes_taken = s - str.ptr; return value; -} -long parse_int_with_base(const(char)[] str, size_t* bytes_taken = null) pure -{ - const(char)* p = str.ptr; - int base = str.parse_base_prefix(); - if (base == 10) - return str.parse_int(bytes_taken); - ulong i = str.parse_uint(bytes_taken, base); - if (bytes_taken && *bytes_taken != 0) - *bytes_taken += str.ptr - p; - return i; +nothing: + exp = 0; + goto done; } -ulong parse_uint_with_base(const(char)[] str, size_t* bytes_taken = null) pure +ulong parse_uint_with_exponent_and_base(const(char)[] str, out int exponent, out uint base, size_t* bytes_taken = null) pure { - const(char)* p = str.ptr; - int base = str.parse_base_prefix(); - ulong i = str.parse_uint(bytes_taken, base); - if (bytes_taken && *bytes_taken != 0) - *bytes_taken += str.ptr - p; - return i; + const(char)* s = str.ptr, e = s + str.length, p = s; + base = parse_base_prefix(p, e); + ulong value = p[0 .. e - p].parse_uint_with_exponent(exponent, bytes_taken, base); + if (value && *bytes_taken != 0) + *bytes_taken += p - s; + return value; } - unittest { size_t taken; - ulong divisor; assert(parse_uint("123") == 123); assert(parse_int("+123.456") == 123); assert(parse_int("-123.456", null, 10) == -123); - assert(parse_uint_with_decimal("123.456", divisor, null, 10) == 123456 && divisor == 1000); - assert(parse_int_with_decimal("123.456.789", divisor, &taken, 16) == 1193046 && taken == 7 && divisor == 4096); assert(parse_int("11001", null, 2) == 25); - assert(parse_int_with_decimal("-AbCdE.f", divisor, null, 16) == -11259375 && divisor == 16); assert(parse_int("123abc", &taken, 10) == 123 && taken == 3); assert(parse_int("!!!", &taken, 10) == 0 && taken == 0); assert(parse_int("-!!!", &taken, 10) == 0 && taken == 0); assert(parse_int("Wow", &taken, 36) == 42368 && taken == 3); assert(parse_uint_with_base("0x100", &taken) == 0x100 && taken == 5); + assert(parse_int_with_base("-0x100", &taken) == -0x100 && taken == 6); + + int e; + assert("0001023000".parse_uint_with_exponent(e, &taken, 10) == 1023 && e == 3 && taken == 10); + assert("0.0012003000".parse_uint_with_exponent(e, &taken, 10) == 12003 && e == -7 && taken == 12); + assert("00010.23000".parse_uint_with_exponent(e, &taken, 10) == 1023 && e == -2 && taken == 11); + assert("00012300.0".parse_uint_with_exponent(e, &taken, 10) == 123 && e == 2 && taken == 10); + assert("00100.00230".parse_uint_with_exponent(e, &taken, 10) == 1000023 && e == -4 && taken == 11); + assert("0.0".parse_uint_with_exponent(e, &taken, 10) == 0 && e == 0 && taken == 3); + assert(".01".parse_uint_with_exponent(e, &taken, 10) == 0 && e == 0 && taken == 0); + assert("10e+2".parse_uint_with_exponent(e, &taken, 10) == 1 && e == 3 && taken == 5); + assert("0.01E+2".parse_uint_with_exponent(e, &taken, 10) == 1 && e == 0 && taken == 7); } int parse_int_fast(ref const(char)[] text, out bool success) pure @@ -337,17 +316,25 @@ unittest // on error or not-a-number, result will be nan and bytes_taken will contain 0 -double parse_float(const(char)[] str, size_t* bytes_taken = null, int base = 10) pure +double parse_float(const(char)[] str, size_t* bytes_taken = null, uint base = 10) pure { - // TODO: E-notation... - size_t taken = void; - ulong div = void; - long value = str.parse_int_with_decimal(div, &taken, base); + import urt.math : pow; + + int e; + size_t taken; + long mantissa = str.parse_int_with_exponent(e, &taken, base); if (bytes_taken) *bytes_taken = taken; if (taken == 0) return double.nan; - return cast(double)value / div; + + // TODO: the real work needs to happen here! + // we want all the bits of precision! + + if (__ctfe) + return mantissa * double(base)^^e; + else + return mantissa * pow(base, e); } unittest @@ -362,6 +349,7 @@ unittest assert(fcmp(parse_float("123.456"), 123.456)); assert(fcmp(parse_float("+123.456"), 123.456)); assert(fcmp(parse_float("-123.456.789"), -123.456)); + assert(fcmp(parse_float("-123.456e10"), -1.23456e+12)); assert(fcmp(parse_float("1101.11", &taken, 2), 13.75) && taken == 7); assert(parse_float("xyz", &taken) is double.nan && taken == 0); } @@ -626,7 +614,7 @@ template to(T) { long to(const(char)[] str) { - int base = parse_base_prefix(str); + uint base = parse_base_prefix(str); size_t taken; long r = parse_int(str, &taken, base); assert(taken == str.length, "String is not numeric"); @@ -637,7 +625,7 @@ template to(T) { double to(const(char)[] str) { - int base = parse_base_prefix(str); + uint base = parse_base_prefix(str); size_t taken; double r = parse_float(str, &taken, base); assert(taken == str.length, "String is not numeric"); @@ -681,35 +669,43 @@ template to(T) private: +// valid result is 0 .. 35; result is garbage outside that bound uint get_digit(char c) pure { uint zero_base = c - '0'; if (zero_base < 10) return zero_base; - uint A_base = c - 'A'; - if (A_base < 26) - return A_base + 10; - uint a_base = c - 'a'; - if (a_base < 26) - return a_base + 10; - return -1; + uint a_base = (c | 0x20) - 'a'; + return 10 + a_base; } -int parse_base_prefix(ref const(char)[] str) pure +uint parse_base_prefix(ref const(char)* str, const(char)* end) pure { - int base = 10; - if (str.length >= 2) + uint base = 10; + if (str + 2 <= end && str[0] == '0') { - if (str[0..2] == "0x") - base = 16, str = str[2..$]; - else if (str[0..2] == "0b") - base = 2, str = str[2..$]; - else if (str[0..2] == "0o") - base = 8, str = str[2..$]; + if (str[1] == 'x') + base = 16, str += 2; + else if (str[1] == 'b') + base = 2, str += 2; + else if (str[1] == 'o') + base = 8, str += 2; } return base; } +uint parse_sign(ref const(char)* str, const(char)* end) pure +{ + if (str == end) + return 0; + // NOTE: ascii is '+' = 43, '-' = 45 + uint neg = *str - '+'; + if (neg > 2 || neg == 1) + return 0; + ++str; + return neg; +} + /+ size_t format_struct(T)(ref T value, char[] buffer) nothrow @nogc diff --git a/src/urt/format/json.d b/src/urt/format/json.d index 9a4e7dd..5866a3c 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -399,31 +399,28 @@ Variant parse_node(ref const(char)[] text) } else if (text[0].is_numeric || (text[0] == '-' && text.length > 1 && text[1].is_numeric)) { - bool neg = text[0] == '-'; size_t taken = void; - ulong div = void; - ulong value = text[neg .. $].parse_uint_with_decimal(div, &taken, 10); + int e = void; + long value = text.parse_int_with_exponent(e, &taken, 10); assert(taken > 0); - text = text[taken + neg .. $]; + text = text[taken .. $]; - if (div > 1) + // let's work out if value*10^^e is an integer? + bool is_integer = e >= 0; + for (; e > 0; --e) { - double d = cast(double)value; - if (neg) - d = -d; - d /= div; - return Variant(d); - } - else - { - if (neg) + if (value < 0 ? (value < long.min / 10) : (value > long.max / 10)) { - assert(value <= long.max + 1); - return Variant(-cast(long)value); + is_integer = false; + break; } - else - return Variant(value); + value *= 10; } + + if (is_integer) + return Variant(value); + else + return Variant(value * 10.0^^e); } else assert(false, "Invalid JSON!"); diff --git a/src/urt/math.d b/src/urt/math.d index e3b06b8..ff63a20 100644 --- a/src/urt/math.d +++ b/src/urt/math.d @@ -63,6 +63,7 @@ extern(C) double exp(double x); double log(double x); double acos(double x); + double pow(double x, double e); } int float_is_integer(double f, out ulong i) diff --git a/src/urt/variant.d b/src/urt/variant.d index 8b2a944..033f072 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -1060,8 +1060,8 @@ nothrow @nogc: { size_t taken; ScaledUnit unit; - ulong div; - long i = s.parse_int_with_decimal(div, &taken, 10); + int e; + long i = s.parse_int_with_exponent(e, &taken, 10); if (taken < s.length) { size_t t2 = unit.fromString(s[taken .. $]); @@ -1070,8 +1070,8 @@ nothrow @nogc: } if (taken == s.length) { - if (div != 1) - this = double(i) / div; + if (e != 0) + this = i * 10.0^^e; else this = i; if (unit.pack) From a913c441e30bb1aa4bb8bfb2a98cbc0511dcf9e6 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 2 Feb 2026 17:12:04 +1000 Subject: [PATCH 069/138] Fix CTFE implementation of qsort! --- src/urt/algorithm.d | 64 +++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index 9995cfb..d44fec3 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -132,6 +132,9 @@ size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmp_arg void qsort(alias pred = void, T)(T[] arr) pure { + if (arr.length <= 1) + return; + version (SmallSize) enum use_small_size_impl = true; else @@ -157,39 +160,44 @@ void qsort(alias pred = void, T)(T[] arr) pure else { T* p = arr.ptr; - if (arr.length > 1) - { - size_t pivotIndex = arr.length / 2; - T* pivot = p + pivotIndex; + const n = cast(ptrdiff_t)arr.length; + + size_t pivotIndex = n / 2; + T* pivot = p + pivotIndex; - size_t i = 0; - size_t j = arr.length - 1; + size_t i = 0; + ptrdiff_t j = n - 1; - while (i <= j) + while (i <= j) + { + static if (is(pred == void)) { - static if (is(pred == void)) - { - while (p[i] < *pivot) i++; - while (p[j] > *pivot) j--; - } - else - { - while (pred(p[i], *pivot) < 0) i++; - while (pred(p[j], *pivot) > 0) j--; - } - if (i <= j) - { - swap(p[i], p[j]); - i++; - j--; - } + while (p[i] < *pivot) ++i; + while (p[j] > *pivot) --j; + } + else + { + while (pred(p[i], *pivot) < 0) ++i; + while (pred(p[j], *pivot) > 0) --j; + } + if (i <= j) + { + // track pivot value across swaps + if (p + i == pivot) + pivot = p + j; + else if (p + j == pivot) + pivot = p + i; + + swap(p[i], p[j]); + ++i; + --j; } - - if (j > 0) - qsort!pred(p[0 .. j + 1]); - if (i < arr.length) - qsort!pred(p[i .. arr.length]); } + + if (j >= 0) + qsort!pred(p[0 .. j + 1]); + if (i < n) + qsort!pred(p[i .. n]); } } From 73eb47134c6a817a302c5477742985d5ebec191d Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 2 Feb 2026 17:11:47 +1000 Subject: [PATCH 070/138] Quantity fix. --- src/urt/si/quantity.d | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index 0d5a6b2..bf9ae9c 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -146,11 +146,12 @@ nothrow @nogc: return mixin("this.value " ~ op ~ " value"); else { - Quantity!(TypeForOp!(op, T, U), unit) r; - r.value = mixin("this.value " ~ op ~ " value"); + alias RT = TypeForOp!(op, T, U); + RT v = mixin("this.value " ~ op ~ " value"); static if (Dynamic) - r.unit = unit; - return r; + return Quantity!RT(v, unit); + else + return Quantity!(RT, unit)(v); } } From 6925ddd0f3ab5e15f7d20a1c92e5f416750ad538 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sat, 7 Feb 2026 02:10:40 +1000 Subject: [PATCH 071/138] Fixed integer division. --- src/urt/si/unit.d | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index 8994aef..b9be825 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -423,12 +423,12 @@ nothrow: return unit == b.unit; } - double scale(bool inv = false)() const pure + double scale(bool invert = false)() const pure { if (siScale) { int e = exp(); - if (inv) + if (invert) e = -e; if (uint(e + 9) < 19) return sciScaleFactor[e + 9]; @@ -436,9 +436,9 @@ nothrow: } if (isExtended()) - return extScaleFactor[(pack >> 29) ^ (inv << 2)]; + return extScaleFactor[(pack >> 29) ^ (invert << 2)]; - double s = scaleFactor[(pack >> 31) ^ inv][sf()]; + double s = scaleFactor[(pack >> 31) ^ invert][sf()]; for (uint i = ((pack >> 29) & 3); i > 0; --i) s *= s; return s; @@ -809,6 +809,17 @@ nothrow: size_t toHash() const pure => pack; + auto __debugOverview() + { + debug { + char[] buffer = new char[32]; + ptrdiff_t len = toString(buffer, null, null); + return buffer[0 .. len]; + } + else + return pack; + } + package: this(uint pack) pure { @@ -881,9 +892,9 @@ immutable double[16][2] scaleFactor = [ [ PI/180, // Degrees double.nan // extended... ], [ - 1/60, // Minute - 1/3600, // Hour - 1/86400, // Day + 1/60.0, // Minute + 1/3600.0, // Hour + 1/86400.0, // Day 1/0.0254, // Inch 1/0.3048, // Foot 1/1609.344, // Mile From 08df31921c796d6072da3aba7e2db30b6acf7583 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 5 Feb 2026 00:19:47 +1000 Subject: [PATCH 072/138] Reserve null in the string cache. --- src/urt/string/string.d | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/urt/string/string.d b/src/urt/string/string.d index be23b03..ccb06f9 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -37,13 +37,17 @@ struct StringCacheBuilder nothrow @nogc: this(char[] buffer) pure { - assert(buffer.length <= ushort.max, "Buffer too long"); + assert(buffer.length >= 2 && buffer.length <= ushort.max, "Invalid buffer length"); + buffer[0..2] = 0; this._buffer = buffer; - this._offset = 0; + this._offset = 2; } ushort add_string(const(char)[] s) pure { + if (s.length == 0) + return 0; + assert(s.length <= MaxStringLen, "String too long"); assert(_offset + s.length + 2 + (s.length & 1) <= _buffer.length, "Not enough space in buffer"); if (__ctfe) From 14bb0e67a9595c62cc493472bbde9cdd22354685 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 8 Feb 2026 18:37:14 +1000 Subject: [PATCH 073/138] Added parse_quantity which is like parse_float, but with a unit suffix. --- src/urt/conv.d | 92 +++++++++++++++++++++++++++++++--------- src/urt/si/quantity.d | 2 +- src/urt/si/unit.d | 29 +++++++------ src/urt/string/package.d | 61 ++++++++++++-------------- 4 files changed, 117 insertions(+), 67 deletions(-) diff --git a/src/urt/conv.d b/src/urt/conv.d index dfdc9f0..f0ab5c1 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -1,6 +1,7 @@ module urt.conv; import urt.meta; +import urt.si.quantity; import urt.string; public import urt.string.format : toString; @@ -107,10 +108,11 @@ ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* byte int exp = 0; uint digits = 0; uint zero_seq = 0; + char c = void; for (; s < e; ++s) { - char c = *s; + c = *s; if (c == '.') { @@ -151,7 +153,7 @@ ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* byte parse_decimal: for (; s < e; ++s) { - char c = *s; + c = *s; if (c == '0') { @@ -179,32 +181,32 @@ parse_decimal: check_exp: // check for exponent part - if (s < e && (*s == 'e' || *s == 'E')) + if (s + 1 < e && ((*s | 0x20) == 'e')) { - ++s; - bool exp_neg = false; - if (s < e) + c = s[1]; + bool exp_neg = c == '-'; + if (exp_neg || c == '+') { - char c = *s; - exp_neg = c == '-'; - if (exp_neg || c == '+') - ++s; + if (s + 2 >= e || !s[2].is_numeric) + goto done; + s += 2; + } + else + { + if (!c.is_numeric) + goto done; + ++s; } int exp_value = 0; - const(char)* t = s; - for (; t < e; ++t) + for (; s < e; ++s) { - uint digit = *t - '0'; + uint digit = *s - '0'; if (digit > 9) break; exp_value = exp_value * 10 + digit; } - if (t > s) - { - exp += exp_neg ? -exp_value : exp_value; - s = t; - } + exp += exp_neg ? -exp_value : exp_value; } done: @@ -250,8 +252,12 @@ unittest assert("00100.00230".parse_uint_with_exponent(e, &taken, 10) == 1000023 && e == -4 && taken == 11); assert("0.0".parse_uint_with_exponent(e, &taken, 10) == 0 && e == 0 && taken == 3); assert(".01".parse_uint_with_exponent(e, &taken, 10) == 0 && e == 0 && taken == 0); - assert("10e+2".parse_uint_with_exponent(e, &taken, 10) == 1 && e == 3 && taken == 5); + assert("10e2".parse_uint_with_exponent(e, &taken, 10) == 1 && e == 3 && taken == 4); assert("0.01E+2".parse_uint_with_exponent(e, &taken, 10) == 1 && e == 0 && taken == 7); + assert("0.01E".parse_uint_with_exponent(e, &taken, 10) == 1 && e == -2 && taken == 4); + assert("0.01Ex".parse_uint_with_exponent(e, &taken, 10) == 1 && e == -2 && taken == 4); + assert("0.01E-".parse_uint_with_exponent(e, &taken, 10) == 1 && e == -2 && taken == 4); + assert("0.01E-x".parse_uint_with_exponent(e, &taken, 10) == 1 && e == -2 && taken == 4); } int parse_int_fast(ref const(char)[] text, out bool success) pure @@ -354,6 +360,50 @@ unittest assert(parse_float("xyz", &taken) is double.nan && taken == 0); } +VarQuantity parse_quantity(const(char)[] text, size_t* bytes_taken = null) nothrow +{ + import urt.si.unit; + + int e; + uint base; + size_t taken; + long raw_value = text.parse_int_with_exponent_and_base(e, base, &taken); + if (taken == 0) + { + if (bytes_taken) + *bytes_taken = 0; + return VarQuantity(double.nan); + } + + // we parsed a number! + auto r = VarQuantity(e == 0 ? raw_value : raw_value * double(base)^^e); + + if (taken < text.length) + { + // try and parse a unit... + ScaledUnit su; + float pre_scale; + ptrdiff_t unit_taken = su.parse_unit(text[taken .. $], pre_scale, false); + if (unit_taken > 0) + { + taken += unit_taken; + r = VarQuantity(r.value * pre_scale, su); + } + } + if (bytes_taken) + *bytes_taken = taken; + return r; +} + +unittest +{ + import urt.si.unit; + + size_t taken; + assert("10V".parse_quantity(&taken) == Volts(10) && taken == 3); + assert("10.2e+2Wh".parse_quantity(&taken) == WattHours(1020) && taken == 9); +} + ptrdiff_t parse(T)(const char[] text, out T result) { @@ -676,7 +726,7 @@ uint get_digit(char c) pure if (zero_base < 10) return zero_base; uint a_base = (c | 0x20) - 'a'; - return 10 + a_base; + return 10 + (a_base & 0xFF); } uint parse_base_prefix(ref const(char)* str, const(char)* end) pure @@ -703,7 +753,7 @@ uint parse_sign(ref const(char)* str, const(char)* end) pure if (neg > 2 || neg == 1) return 0; ++str; - return neg; + return neg; // neg is 0 (+) or 2 (-) } diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index bf9ae9c..e293ebb 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -7,7 +7,7 @@ import urt.traits; nothrow @nogc: -alias VarQuantity = Quantity!(double); +alias VarQuantity = Quantity!double; alias Scalar = Quantity!(double, ScaledUnit()); alias Metres = Quantity!(double, ScaledUnit(Metre)); alias Seconds = Quantity!(double, ScaledUnit(Second)); diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index b9be825..7bc059b 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -32,7 +32,7 @@ nothrow @nogc: // -enum ScaledUnit unit(const(char)[] desc) = () { ScaledUnit r; float f; ptrdiff_t e = r.parseUnit(desc, f); assert(e > 0, "Invalid unit"); assert(f == 1, "Unit requires pre-scale"); return r; }(); +enum ScaledUnit unit(const(char)[] desc) = () { ScaledUnit r; float f; ptrdiff_t e = r.parse_unit(desc, f); assert(e > 0, "Invalid unit"); assert(f == 1, "Unit requires pre-scale"); return r; }(); // base units @@ -267,7 +267,7 @@ nothrow: char sep; while (const(char)[] unit = s.split!('/', '*')(sep)) { - int p = unit.takePower(); + int p = unit.take_power(); if (p == 0) return -1; // invalid power @@ -565,11 +565,12 @@ nothrow: bool opEquals(Unit rh) const pure => (pack & 0xFF000000) ? false : unit == rh; - ptrdiff_t parseUnit(const(char)[] s, out float preScale) pure + alias parseUnit = parse_unit; // TODO: DELETE ME!!! + ptrdiff_t parse_unit(const(char)[] s, out float pre_scale, bool allow_unit_scale = true) pure { import urt.conv : parse_uint_with_exponent; - preScale = 1; + pre_scale = 1; if (s.length == 0) { @@ -582,18 +583,20 @@ nothrow: { if (s.length == 1) return -1; - preScale = -1; + pre_scale = -1; s = s[1 .. $]; } ScaledUnit r; bool invert; char sep; - while (const(char)[] term = s.split!('/', '*')(sep)) + while (const(char)[] term = s.split!(['/', '*'], false, false)(&sep)) { - int p = term.takePower(); + int p = term.take_power(); if (p == 0) return -1; // invalid exponent + if (term.length == 0) + return -1; size_t offset = 0; @@ -601,8 +604,10 @@ nothrow: int e = 0; if (term[0].is_numeric) { + if (!allow_unit_scale) + return -1; // no numeric scale factor allowed ulong sf = term.parse_uint_with_exponent(e, &offset); - preScale *= sf; + pre_scale *= sf; } if (offset == term.length) @@ -610,7 +615,7 @@ nothrow: else if (const ScaledUnit* su = term[offset .. $] in noScaleUnitMap) { r *= (*su) ^^ (invert ? -p : p); - preScale *= 10.0^^e; + pre_scale *= 10.0^^e; } else { @@ -670,7 +675,7 @@ nothrow: else if (const ScaledUnit* su = term in noScaleUnitMapSI) { r *= (*su) ^^ (invert ? -p : p); - preScale *= 10.0^^e; + pre_scale *= 10.0^^e; } else return -1; // string was not taken? @@ -800,7 +805,7 @@ nothrow: ptrdiff_t fromString(const(char)[] s) pure { float scale; - ptrdiff_t r = parseUnit(s, scale); + ptrdiff_t r = parse_unit(s, scale); if (scale != 1) return -1; return r; @@ -1066,7 +1071,7 @@ immutable ScaledUnit[string] noScaleUnitMapSI = [ "varh" : WattHour, ]; -int takePower(ref const(char)[] s) pure +int take_power(ref const(char)[] s) pure { size_t e = s.findFirst('^'); if (e < s.length) diff --git a/src/urt/string/package.d b/src/urt/string/package.d index 4fd0e24..77ec36b 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -265,20 +265,26 @@ inout(char)[] takeLine(ref inout(char)[] s) pure return t; } -inout(char)[] split(char Separator, bool HandleQuotes = true)(ref inout(char)[] s) pure +inout(char)[] split(char[] separators, bool handle_quotes = true, bool do_trim = true)(ref inout(char)[] s, char* separator = null) pure { - static if (HandleQuotes) + static if (handle_quotes) int inQuotes = 0; else enum inQuotes = false; size_t i = 0; - for (; i < s.length; ++i) + loop: for (; i < s.length; ++i) { - if (s[i] == Separator && !inQuotes) - break; - - static if (HandleQuotes) + static foreach (sep; separators) + { + if (s[i] == sep && !inQuotes) + { + if (separator) + *separator = s[i]; + break loop; + } + } + static if (handle_quotes) { if (s[i] == '"' && !(inQuotes & 0x6)) inQuotes = 1 - inQuotes; @@ -288,37 +294,26 @@ inout(char)[] split(char Separator, bool HandleQuotes = true)(ref inout(char)[] inQuotes = 4 - inQuotes; } } - inout(char)[] t = s[0 .. i].trimBack; - s = i < s.length ? s[i+1 .. $].trimFront : null; + static if (do_trim) + { + inout(char)[] t = s[0 .. i].trimBack; + s = i < s.length ? s[i+1 .. $].trimFront : null; + } + else + { + inout(char)[] t = s[0 .. i]; + s = i < s.length ? s[i+1 .. $] : null; + } return t; } -inout(char)[] split(Separator...)(ref inout(char)[] s, out char sep) pure +alias split(char separator, bool handle_quotes = true, bool do_trim = true) = split!([separator], handle_quotes, do_trim); + +// TODO: deprecate this one... +inout(char)[] split(separators...)(ref inout(char)[] s, out char sep) pure { sep = '\0'; - int inQuotes = 0; - size_t i = 0; - loop: for (; i < s.length; ++i) - { - static foreach (S; Separator) - { - static assert(is(typeof(S) == char), "Only single character separators supported"); - if (s[i] == S && !inQuotes) - { - sep = s[i]; - break loop; - } - } - if (s[i] == '"' && !(inQuotes & 0x6)) - inQuotes = 1 - inQuotes; - else if (s[i] == '\'' && !(inQuotes & 0x5)) - inQuotes = 2 - inQuotes; - else if (s[i] == '`' && !(inQuotes & 0x3)) - inQuotes = 4 - inQuotes; - } - inout(char)[] t = s[0 .. i].trimBack; - s = i < s.length ? s[i+1 .. $].trimFront : null; - return t; + return split!([separators], true, true)(s, &sep); } char[] unQuote(const(char)[] s, char[] buffer) pure From a19dac19c1602042983b099885a77ac3e51e192d Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 10 Feb 2026 17:00:18 +1000 Subject: [PATCH 074/138] - Improve maps - Add some hacks for unification of timestamp types - Fix compile error for user types with no comparison operator --- src/urt/meta/package.d | 9 +++ src/urt/variant.d | 138 +++++++++++++++++++++++++++-------------- 2 files changed, 101 insertions(+), 46 deletions(-) diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index c703fc5..defbd9a 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -187,6 +187,12 @@ nothrow @nogc: return get_key(_lookup_tables[count*2 + i]); } + const(char)[] key_by_sorted_index(size_t i) const pure + { + assert(i < count, "Declaration index out of range"); + return get_key(i); + } + const(void)* value_for(const(char)[] key) const pure { size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); @@ -280,6 +286,9 @@ template EnumInfo(E) const(char)[] key_by_decl_index(size_t i) const pure => _base.key_by_decl_index(i); + const(char)[] key_by_sorted_index(size_t i) const pure + => _base.key_by_sorted_index(i); + const(UE)* value_for(const(char)[] key) const pure { size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); diff --git a/src/urt/variant.d b/src/urt/variant.d index 033f072..0d8a19e 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -9,7 +9,7 @@ import urt.map; import urt.mem.allocator; import urt.si.quantity; import urt.si.unit : ScaledUnit, Second, Nanosecond; -import urt.time : Duration, dur; +import urt.time; import urt.traits; nothrow @nogc: @@ -257,35 +257,40 @@ nothrow @nogc: this(T)(auto ref T thing) if (ValidUserType!T) { - alias dummy = MakeTypeDetails!T; - - flags = Flags.User; - static if (is(T == class)) - { - count = UserTypeId!T; - alloc = type_detail_index!T(); - ptr = cast(void*)thing; - } - else static if (EmbedUserType!T) + static if (is(Unqual!T == MonoTime)) + this(cast(SysTime)thing); + else { - alloc = UserTypeId!T; - flags |= Flags.Embedded; + alias dummy = MakeTypeDetails!T; - if (TypeDetailsFor!T.destroy) // TODO: we should check the same condition that determined if there is a destruct function... - flags |= Flags.NeedDestruction; + flags = Flags.User; + static if (is(T == class)) + { + count = UserTypeId!T; + alloc = type_detail_index!T(); + ptr = cast(void*)thing; + } + else static if (EmbedUserType!T) + { + alloc = UserTypeId!T; + flags |= Flags.Embedded; - emplace(cast(T*)embed.ptr, forward!thing); - } - else - { - count = UserTypeId!T; - alloc = type_detail_index!T(); + if (TypeDetailsFor!T.destroy) // TODO: we should check the same condition that determined if there is a destruct function... + flags |= Flags.NeedDestruction; + + emplace(cast(T*)embed.ptr, forward!thing); + } + else + { + count = UserTypeId!T; + alloc = type_detail_index!T(); - if (TypeDetailsFor!T.destroy) // TODO: we should check the same condition that determined if there is a destruct function... - flags |= Flags.NeedDestruction; + if (TypeDetailsFor!T.destroy) // TODO: we should check the same condition that determined if there is a destruct function... + flags |= Flags.NeedDestruction; - ptr = defaultAllocator().alloc(T.sizeof, T.alignof).ptr; - emplace(cast(T*)ptr, forward!thing); + ptr = defaultAllocator().alloc(T.sizeof, T.alignof).ptr; + emplace(cast(T*)ptr, forward!thing); + } } } @@ -812,23 +817,50 @@ nothrow @nogc: else return *cast(inout(T)*)ptr; } - inout(T) asUser(T)() inout pure + inout(T) asUser(T)() inout if (ValidUserType!(Unqual!T) && !UserTypeReturnByRef!T) { alias U = Unqual!T; - if (!isUser!U) - assert(false, "Variant is not a " ~ U.stringof); - static if (is(U == class)) - return cast(inout(T))ptr; - else static if (EmbedUserType!U) + + // some hacks for builtin time types... + static if (is(U == MonoTime)) + return cast(MonoTime)asUser!SysTime; + else static if (is(T == SysTime)) { - // make a copy on the stack and return by value - U r = void; - TypeDetailsFor!U.copy_emplace(embed.ptr, &r, false); - return r; + static assert (EmbedUserType!SysTime && EmbedUserType!DateTime); + if (isUser!SysTime) + return *cast(inout(SysTime)*)embed.ptr; + if (isUser!DateTime) + return getSysTime(*cast(DateTime*)embed.ptr); + assert(false, "Variant is not a timestamp"); + } + else static if (is(T == DateTime)) + { + static assert (EmbedUserType!SysTime && EmbedUserType!DateTime); + if (isUser!DateTime) + return *cast(inout(DateTime)*)embed.ptr; + if (isUser!SysTime) + return getDateTime(*cast(SysTime*)embed.ptr); + assert(false, "Variant is not a timestamp"); } else - static assert(false, "Should be impossible?"); + { + if (!isUser!U) + assert(false, "Variant is not a " ~ U.stringof); + static if (is(U == class)) + return cast(inout(T))ptr; + else static if (EmbedUserType!U) + { + // TODO: it would be nice to support trivial types and just cast/copy rather than copy_emplace(...) + + // make a copy on the stack and return by value + U r = void; + TypeDetailsFor!U.copy_emplace(cast(U*)embed.ptr, &r, false); + return r; + } + else + static assert(false, "Should be impossible?"); + } } auto as(T)() inout pure @@ -907,6 +939,21 @@ nothrow @nogc: bool empty() const pure => isObject() ? count == 0 : length() == 0; + Variant* insert(const(char)[] key, Variant value) + { + if (flags == Flags.Null) + flags = Flags.Map; + else + { + assert(isObject()); + if (getMember(key)) + return null; + } + nodeArray.emplaceBack(key); + move(value, nodeArray.pushBack()); + return &nodeArray.back(); + } + inout(Variant)* getMember(const(char)[] member) inout pure { assert(isObject()); @@ -1295,7 +1342,7 @@ template UserTypeId(T) enum ushort UserTypeId = cast(ushort)Hash ^ (Hash >> 16); } enum bool EmbedUserType(T) = is(T == struct) && T.sizeof <= Variant.embed.sizeof - 2 && T.alignof <= Variant.alignof; -enum bool UserTypeReturnByRef(T) = is(T == struct); +enum bool UserTypeReturnByRef(T) = is(T == struct) && !EmbedUserType!T; ptrdiff_t newline(char[] buffer, ref ptrdiff_t offset, int level) { @@ -1311,6 +1358,8 @@ template MakeTypeDetails(T) { static assert(is(Unqual!T == T), "Only instantiate for mutable types"); +// pragma(msg, "Add user type: ", T.stringof, EmbedUserType!T ? " - embedded" : ""); + // this is a hack which populates an array of user type details when the program starts // TODO: we can probably NOT do this for class types, and just use RTTI instead... shared static this() @@ -1344,7 +1393,7 @@ struct TypeDetails ptrdiff_t function(void* val, char[] buffer, bool do_format, const(char)[] format_spec, const(FormatArg)[] format_args) nothrow @nogc stringify; int function(const void* a, const void* b, int type) pure nothrow @nogc cmp; } -__gshared TypeDetails[8] g_type_details; +__gshared TypeDetails[16] g_type_details; __gshared ushort g_num_type_details = 0; typeof(g_type_details)* type_details() => &g_type_details; @@ -1449,16 +1498,13 @@ public template TypeDetailsFor(T) return a.opCmp(b); else static if (__traits(compiles, { b.opCmp(a); })) return -b.opCmp(a); - else + else static if (is(T == class)) { - static if (is(T == class)) - { - ptrdiff_t r = cast(ptrdiff_t)pa - cast(ptrdiff_t)pb; - return r < 0 ? -1 : r > 0 ? 1 : 0; - } - else - return a < b ? -1 : a > b ? 1 : 0; + ptrdiff_t r = cast(ptrdiff_t)pa - cast(ptrdiff_t)pb; + return r < 0 ? -1 : r > 0 ? 1 : 0; } + else + assert(false, "No comparison!"); // TODO: hash or stringify the values and order that way? case 1: static if (is(T == class) || is(T == U*, U) || is(T == V[], V)) { From 5870e2a2c97bcb8f55b1914052de556f9a5cb2dc Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sat, 14 Feb 2026 13:33:01 +1000 Subject: [PATCH 075/138] Split the EnumInfo code into its own module. --- src/urt/meta/enuminfo.d | 394 ++++++++++++++++++++++++++++++++++++++++ src/urt/meta/package.d | 374 -------------------------------------- 2 files changed, 394 insertions(+), 374 deletions(-) create mode 100644 src/urt/meta/enuminfo.d diff --git a/src/urt/meta/enuminfo.d b/src/urt/meta/enuminfo.d new file mode 100644 index 0000000..75f10c1 --- /dev/null +++ b/src/urt/meta/enuminfo.d @@ -0,0 +1,394 @@ +module urt.meta.enum_; + +import urt.algorithm : binary_search, qsort; +import urt.traits :EnumType, is_enum, Unqual; +import urt.meta : Iota, STATIC_MAP; +import urt.variant; + +nothrow @nogc: + + +const(E)* enum_from_key(E)(const(char)[] key) pure + if (is_enum!E) + => enum_info!E.value_for(key); + +const(char)[] enum_key_from_value(E)(EnumType!E value) pure + if (is_enum!E) + => enum_info!E.key_for(value); + +const(char)[] enum_key_by_decl_index(E)(size_t value) pure + if (is_enum!E) + => enum_info!E.key_by_decl_index(value); + +struct VoidEnumInfo +{ + import urt.algorithm : binary_search; +nothrow @nogc: + + // keys and values are sorted for binary search + ushort count; + ushort stride; + uint type_hash; + + const(char)[] key_for(const void* value, int function(const void* a, const void* b) pure nothrow @nogc pred) const pure + { + size_t i = binary_search(_values[0 .. count*stride], stride, value, pred); + if (i < count) + return get_key(_lookup_tables[count + i]); + return null; + } + + const(char)[] key_for(const void* value, int delegate(const void* a, const void* b) pure nothrow @nogc pred) const pure + { + size_t i = binary_search(_values[0 .. count*stride], stride, value, pred); + if (i < count) + return get_key(_lookup_tables[count + i]); + return null; + } + + const(char)[] key_by_decl_index(size_t i) const pure + { + assert(i < count, "Declaration index out of range"); + return get_key(_lookup_tables[count*2 + i]); + } + + const(char)[] key_by_sorted_index(size_t i) const pure + { + assert(i < count, "Declaration index out of range"); + return get_key(i); + } + + Variant value_for(const(char)[] key) const pure + { + size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); + if (i == count) + return Variant(); + i = _lookup_tables[i]; + return _get_value(_values + i*stride); + } + + bool contains(const(char)[] key) const pure + { + size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); + return i < count; + } + +private: + alias GetFun = Variant function(const(void)*) pure; + + const void* _values; + const ushort* _keys; + const char* _string_buffer; + + // these tables map between indices of keys and values + const ubyte* _lookup_tables; + + GetFun _get_value; + + this(ubyte count, ushort stride, uint type_hash, inout void* values, inout ushort* keys, inout char* strings, inout ubyte* lookup, GetFun get_value) inout pure + { + this.count = count; + this.stride = stride; + this.type_hash = type_hash; + this._keys = keys; + this._values = values; + this._string_buffer = strings; + this._lookup_tables = lookup; + this._get_value = get_value; + } + + const(char)[] get_key(size_t i) const pure + { + const(char)* s = _string_buffer + _keys[i]; + return s[0 .. s.key_length]; + } +} + +template EnumInfo(E) +{ + alias UE = Unqual!E; + + static if (is(UE == void)) + alias EnumInfo = VoidEnumInfo; + else + { + struct EnumInfo + { + import urt.algorithm : binary_search; + nothrow @nogc: + + static assert (EnumInfo.sizeof == EnumInfo.sizeof, "Template EnumInfo must not add any members!"); + + static if (is(UE T == enum)) + alias V = T; + else + static assert(false, E.string ~ " is not an enum type!"); + + // keys and values are sorted for binary search + union { + VoidEnumInfo _base; + struct { + ubyte[VoidEnumInfo._values.offsetof] _pad; + const UE* _values; // shadows the _values in _base with a typed version + } + } + alias _base this; + + inout(VoidEnumInfo*) make_void() inout pure + => &_base; + + this(ubyte count, uint type_hash, inout UE* values, inout ushort* keys, inout char* strings, inout ubyte* lookup) inout pure + { + _base = inout(VoidEnumInfo)(count, UE.sizeof, type_hash, values, keys, strings, lookup, cast(VoidEnumInfo.GetFun)&get_value!UE); + } + + const(UE)[] values() const pure + => _values[0 .. count]; + + const(char)[] key_for(V value) const pure + { + size_t i = binary_search(values[0 .. count], value); + if (i < count) + return get_key(_lookup_tables[count + i]); + return null; + } + + const(char)[] key_by_decl_index(size_t i) const pure + => _base.key_by_decl_index(i); + + const(char)[] key_by_sorted_index(size_t i) const pure + => _base.key_by_sorted_index(i); + + const(UE)* value_for(const(char)[] key) const pure + { + size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); + if (i == count) + return null; + return _values + _lookup_tables[i]; + } + + bool contains(const(char)[] key) const pure + => _base.contains(key); + } + } +} + +template enum_info(E) + if (is(Unqual!E == enum)) +{ + alias UE = Unqual!E; + + enum ubyte num_items = enum_members.length; + static assert(num_items <= ubyte.max, "Too many enum items!"); + + __gshared immutable enum_info = immutable(EnumInfo!UE)( + num_items, + fnv1a(cast(ubyte[])UE.stringof), + _values.ptr, + _keys.ptr, + _strings.ptr, + _lookup.ptr + ); + +private: + import urt.algorithm : binary_search, compare, qsort; + import urt.hash : fnv1a; + import urt.string.uni : uni_compare; + + // keys and values are sorted for binary search + __gshared immutable UE[num_items] _values = [ STATIC_MAP!(GetValue, iota) ]; + + // keys are stored as offsets info the string buffer + __gshared immutable ushort[num_items] _keys = () { + ushort[num_items] key_offsets; + size_t offset = 2; + foreach (i; 0 .. num_items) + { + const(char)[] key = by_key[i].k; + key_offsets[i] = cast(ushort)offset; + offset += 2 + key.length; + if (key.length & 1) + offset += 1; // align to 2 bytes + } + return key_offsets; + }(); + + // build the string buffer + __gshared immutable char[total_strings] _strings = () { + char[total_strings] str_data; + char* ptr = str_data.ptr; + foreach (i; 0 .. num_items) + { + const(char)[] key = by_key[i].k; + version (LittleEndian) + { + *ptr++ = key.length & 0xFF; + *ptr++ = (key.length >> 8) & 0xFF; + } + else + { + *ptr++ = (key.length >> 8) & 0xFF; + *ptr++ = key.length & 0xFF; + } + ptr[0 .. key.length] = key[]; + ptr += key.length; + if (key.length & 1) + *ptr++ = 0; // align to 2 bytes + } + return str_data; + }(); + + // these tables map between indices of keys and values + __gshared immutable ubyte[num_items * 3] _lookup = [ STATIC_MAP!(GetKeyRedirect, iota), + STATIC_MAP!(GetValRedirect, iota), + STATIC_MAP!(GetKeyOrig, iota) ]; + + // a whole bunch of nonsense to build the tables... + struct KI + { + string k; + ubyte i; + } + struct VI + { + UE v; + ubyte i; + } + + alias iota = Iota!(enum_members.length); + enum enum_members = __traits(allMembers, E); + enum by_key = (){ KI[num_items] r = [ STATIC_MAP!(MakeKI, iota) ]; r.qsort!((ref a, ref b) => uni_compare(a.k, b.k)); return r; }(); + enum by_value = (){ VI[num_items] r = [ STATIC_MAP!(MakeVI, iota) ]; r.qsort!((ref a, ref b) => compare(a.v, b.v)); return r; }(); + enum inv_key = (){ KI[num_items] bk = by_key; ubyte[num_items] r; foreach (ubyte i, ref ki; bk) r[ki.i] = i; return r; }(); + enum inv_val = (){ VI[num_items] bv = by_value; ubyte[num_items] r; foreach (ubyte i, ref vi; bv) r[vi.i] = i; return r; }(); + + // calculate the total size of the string buffer + enum total_strings = () { + size_t total = 0; + static foreach (k; enum_members) + total += 2 + k.length + (k.length & 1); + return total; + }(); + + enum MakeKI(ushort i) = KI(trim_key!(enum_members[i]), i); + enum MakeVI(ushort i) = VI(__traits(getMember, E, enum_members[i]), i); + enum GetValue(size_t i) = by_value[i].v; + enum GetKeyRedirect(size_t i) = inv_val[by_key[i].i]; + enum GetValRedirect(size_t i) = inv_key[by_value[i].i]; + enum GetKeyOrig(size_t i) = inv_key[i]; +} + +VoidEnumInfo* make_enum_info(T)(const(char)[] name, const(char)[][] keys, T[] values) +{ + import urt.algorithm; + import urt.hash : fnv1a; + import urt.mem.allocator; + import urt.string; + import urt.string.uni; + import urt.util; + + assert(keys.length == values.length, "keys and values must have the same length"); + assert(keys.length <= ubyte.max, "Too many enum items!"); + + size_t count = keys.length; + + struct VI(T) + { + T v; + ubyte i; + } + + // first we'll sort the keys and values for binary searching + // we need to associate their original indices for the lookup tables + auto ksort = tempAllocator().allocArray!(VI!(const(char)[]))(count); + auto vsort = tempAllocator().allocArray!(VI!T)(count); + foreach (i; 0 .. count) + { + ksort[i] = VI!(const(char)[])(keys[i], cast(ubyte)i); + vsort[i] = VI!T(values[i], cast(ubyte)i); + } + ksort.qsort!((ref a, ref b) => uni_compare(a.v, b.v)); + vsort.qsort!((ref a, ref b) => compare(a.v, b.v)); + + // build the reverse lookup tables + ubyte[] inv_k = tempAllocator().allocArray!ubyte(count); + ubyte[] inv_v = tempAllocator().allocArray!ubyte(count); + foreach (i, ref ki; ksort) + inv_k[ki.i] = cast(ubyte)i; + foreach (i, ref vi; vsort) + inv_v[vi.i] = cast(ubyte)i; + + // count the string memory + size_t total_string; + foreach (i; 0 .. count) + total_string += 2 + keys[i].length + (keys[i].length & 1); + + // calculate the total size + size_t total_size = VoidEnumInfo.sizeof + T.sizeof*count; + total_size += (total_size & 1) + ushort.sizeof*count + count*3; + total_size += (total_size & 1) + total_string; + + // allocate a buffer and assign all the sub-buffers + void[] info = defaultAllocator().alloc(total_size); + VoidEnumInfo* result = cast(VoidEnumInfo*)info.ptr; + T* value_ptr = cast(T*)&result[1]; + char* str_data = cast(char*)&value_ptr[count]; + if (cast(size_t)str_data & 1) + *str_data++ = 0; // align to 2 bytes + ushort* key_ptr = cast(ushort*)str_data; + ubyte* lookup = cast(ubyte*)&key_ptr[count]; + str_data = cast(char*)&lookup[count*3]; + if (cast(size_t)str_data & 1) + *str_data++ = 0; // align to 2 bytes + char* str_ptr = str_data + 2; + + // populate the enum info data + foreach (i; 0 .. count) + { + value_ptr[i] = vsort[i].v; + + // write the string data and store the key offset + const(char)[] key = ksort[i].v; + key_ptr[i] = cast(ushort)(str_ptr - str_data); + writeString(str_ptr, key); + if (key.length & 1) + (str_ptr++)[key.length] = 0; // align to 2 bytes + str_ptr += 2 + key.length; + + lookup[i] = inv_v[ksort[i].i]; + lookup[count + i] = inv_k[vsort[i].i]; + lookup[count*2 + i] = inv_k[i]; + } + + // build and return the object + return new(*result) VoidEnumInfo(cast(ubyte)keys.length, cast(ushort)T.sizeof, fnv1a(cast(ubyte[])name), value_ptr, key_ptr, str_data, lookup, cast(VoidEnumInfo.GetFun)&get_value!T); +} + + +private: + +Variant get_value(E)(const(void)* ptr) + => Variant(*cast(const(E)*)ptr); + +import urt.string : trim; +enum trim_key(string key) = key.trim!(c => c == '_'); + +ushort key_length(const(char)* key) pure +{ + if (__ctfe) + { + version (LittleEndian) + return key[-2] | cast(ushort)(key[-1] << 8); + else + return key[-1] | cast(ushort)(key[-2] << 8); + } + else + return *cast(ushort*)(key - 2); +} + +int key_compare(ushort a, const(char)[] b, const(char)* strings) pure +{ + import urt.string.uni : uni_compare; + const(char)* s = strings + a; + return uni_compare(s[0 .. s.key_length], b); +} diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index defbd9a..62bf1a2 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -142,363 +142,9 @@ template INTERLEAVE_SEPARATOR(alias sep, Args...) } -const(E)* enum_from_key(E)(const(char)[] key) pure - if (is_enum!E) - => enum_info!E.value_for(key); - -const(char)[] enum_key_from_value(E)(EnumType!E value) pure - if (is_enum!E) - => enum_info!E.key_for(value); - -const(char)[] enum_key_by_decl_index(E)(size_t value) pure - if (is_enum!E) - => enum_info!E.key_by_decl_index(value); - -struct VoidEnumInfo -{ - import urt.algorithm : binary_search; - import urt.string; -nothrow @nogc: - - // keys and values are sorted for binary search - ushort count; - ushort stride; - uint type_hash; - - const(char)[] key_for(const void* value, int function(const void* a, const void* b) pure nothrow @nogc pred) const pure - { - size_t i = binary_search(_values[0 .. count*stride], stride, value, pred); - if (i < count) - return get_key(_lookup_tables[count + i]); - return null; - } - - const(char)[] key_for(const void* value, int delegate(const void* a, const void* b) pure nothrow @nogc pred) const pure - { - size_t i = binary_search(_values[0 .. count*stride], stride, value, pred); - if (i < count) - return get_key(_lookup_tables[count + i]); - return null; - } - - const(char)[] key_by_decl_index(size_t i) const pure - { - assert(i < count, "Declaration index out of range"); - return get_key(_lookup_tables[count*2 + i]); - } - - const(char)[] key_by_sorted_index(size_t i) const pure - { - assert(i < count, "Declaration index out of range"); - return get_key(i); - } - - const(void)* value_for(const(char)[] key) const pure - { - size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); - if (i == count) - return null; - i = _lookup_tables[i]; - return _values + i*stride; - } - - bool contains(const(char)[] key) const pure - { - size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); - return i < count; - } - -private: - const void* _values; - const ushort* _keys; - const char* _string_buffer; - - // these tables map between indices of keys and values - const ubyte* _lookup_tables; - - this(ubyte count, ushort stride, uint type_hash, inout void* values, inout ushort* keys, inout char* strings, inout ubyte* lookup) inout pure - { - this.count = count; - this.stride = stride; - this.type_hash = type_hash; - this._keys = keys; - this._values = values; - this._string_buffer = strings; - this._lookup_tables = lookup; - } - - const(char)[] get_key(size_t i) const pure - { - const(char)* s = _string_buffer + _keys[i]; - return s[0 .. s.key_length]; - } -} - -template EnumInfo(E) -{ - alias UE = Unqual!E; - - static if (is(UE == void)) - alias EnumInfo = VoidEnumInfo; - else - { - struct EnumInfo - { - import urt.algorithm : binary_search; - nothrow @nogc: - - static assert (EnumInfo.sizeof == EnumInfo.sizeof, "Template EnumInfo must not add any members!"); - - static if (is(UE T == enum)) - alias V = T; - else - static assert(false, E.string ~ " is not an enum type!"); - - // keys and values are sorted for binary search - union { - VoidEnumInfo _base; - struct { - ubyte[VoidEnumInfo._values.offsetof] _pad; - const UE* _values; // shadows the _values in _base with a typed version - } - } - alias _base this; - - inout(VoidEnumInfo*) make_void() inout pure - => &_base; - - this(ubyte count, uint type_hash, inout UE* values, inout ushort* keys, inout char* strings, inout ubyte* lookup) inout pure - { - _base = inout(VoidEnumInfo)(count, UE.sizeof, type_hash, values, keys, strings, lookup); - } - - const(UE)[] values() const pure - => _values[0 .. count]; - - const(char)[] key_for(V value) const pure - { - size_t i = binary_search(values[0 .. count], value); - if (i < count) - return get_key(_lookup_tables[count + i]); - return null; - } - - const(char)[] key_by_decl_index(size_t i) const pure - => _base.key_by_decl_index(i); - - const(char)[] key_by_sorted_index(size_t i) const pure - => _base.key_by_sorted_index(i); - - const(UE)* value_for(const(char)[] key) const pure - { - size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); - if (i == count) - return null; - return _values + _lookup_tables[i]; - } - - bool contains(const(char)[] key) const pure - => _base.contains(key); - } - } -} - -template enum_info(E) - if (is(Unqual!E == enum)) -{ - alias UE = Unqual!E; - - enum ubyte num_items = enum_members.length; - static assert(num_items <= ubyte.max, "Too many enum items!"); - - __gshared immutable enum_info = immutable(EnumInfo!UE)( - num_items, - fnv1a(cast(ubyte[])UE.stringof), - _values.ptr, - _keys.ptr, - _strings.ptr, - _lookup.ptr - ); - -private: - import urt.algorithm : binary_search, compare, qsort; - import urt.hash : fnv1a; - import urt.string.uni : uni_compare; - - // keys and values are sorted for binary search - __gshared immutable UE[num_items] _values = [ STATIC_MAP!(GetValue, iota) ]; - - // keys are stored as offsets info the string buffer - __gshared immutable ushort[num_items] _keys = () { - ushort[num_items] key_offsets; - size_t offset = 2; - foreach (i; 0 .. num_items) - { - const(char)[] key = by_key[i].k; - key_offsets[i] = cast(ushort)offset; - offset += 2 + key.length; - if (key.length & 1) - offset += 1; // align to 2 bytes - } - return key_offsets; - }(); - - // build the string buffer - __gshared immutable char[total_strings] _strings = () { - char[total_strings] str_data; - char* ptr = str_data.ptr; - foreach (i; 0 .. num_items) - { - const(char)[] key = by_key[i].k; - version (LittleEndian) - { - *ptr++ = key.length & 0xFF; - *ptr++ = (key.length >> 8) & 0xFF; - } - else - { - *ptr++ = (key.length >> 8) & 0xFF; - *ptr++ = key.length & 0xFF; - } - ptr[0 .. key.length] = key[]; - ptr += key.length; - if (key.length & 1) - *ptr++ = 0; // align to 2 bytes - } - return str_data; - }(); - - // these tables map between indices of keys and values - __gshared immutable ubyte[num_items * 3] _lookup = [ STATIC_MAP!(GetKeyRedirect, iota), - STATIC_MAP!(GetValRedirect, iota), - STATIC_MAP!(GetKeyOrig, iota) ]; - - // a whole bunch of nonsense to build the tables... - struct KI - { - string k; - ubyte i; - } - struct VI - { - UE v; - ubyte i; - } - - alias iota = Iota!(enum_members.length); - enum enum_members = __traits(allMembers, E); - enum by_key = (){ KI[num_items] r = [ STATIC_MAP!(MakeKI, iota) ]; r.qsort!((ref a, ref b) => uni_compare(a.k, b.k)); return r; }(); - enum by_value = (){ VI[num_items] r = [ STATIC_MAP!(MakeVI, iota) ]; r.qsort!((ref a, ref b) => compare(a.v, b.v)); return r; }(); - enum inv_key = (){ KI[num_items] bk = by_key; ubyte[num_items] r; foreach (ubyte i, ref ki; bk) r[ki.i] = i; return r; }(); - enum inv_val = (){ VI[num_items] bv = by_value; ubyte[num_items] r; foreach (ubyte i, ref vi; bv) r[vi.i] = i; return r; }(); - - // calculate the total size of the string buffer - enum total_strings = () { - size_t total = 0; - static foreach (k; enum_members) - total += 2 + k.length + (k.length & 1); - return total; - }(); - - enum MakeKI(ushort i) = KI(trim_key!(enum_members[i]), i); - enum MakeVI(ushort i) = VI(__traits(getMember, E, enum_members[i]), i); - enum GetValue(size_t i) = by_value[i].v; - enum GetKeyRedirect(size_t i) = inv_val[by_key[i].i]; - enum GetValRedirect(size_t i) = inv_key[by_value[i].i]; - enum GetKeyOrig(size_t i) = inv_key[i]; -} - -VoidEnumInfo* make_enum_info(T)(const(char)[] name, const(char)[][] keys, T[] values) -{ - import urt.algorithm; - import urt.hash : fnv1a; - import urt.mem.allocator; - import urt.string; - import urt.string.uni; - import urt.util; - - assert(keys.length == values.length, "keys and values must have the same length"); - assert(keys.length <= ubyte.max, "Too many enum items!"); - - size_t count = keys.length; - - struct VI(T) - { - T v; - ubyte i; - } - - // first we'll sort the keys and values for binary searching - // we need to associate their original indices for the lookup tables - auto ksort = tempAllocator().allocArray!(VI!(const(char)[]))(count); - auto vsort = tempAllocator().allocArray!(VI!T)(count); - foreach (i; 0 .. count) - { - ksort[i] = VI!(const(char)[])(keys[i], cast(ubyte)i); - vsort[i] = VI!T(values[i], cast(ubyte)i); - } - ksort.qsort!((ref a, ref b) => uni_compare(a.v, b.v)); - vsort.qsort!((ref a, ref b) => compare(a.v, b.v)); - - // build the reverse lookup tables - ubyte[] inv_k = tempAllocator().allocArray!ubyte(count); - ubyte[] inv_v = tempAllocator().allocArray!ubyte(count); - foreach (i, ref ki; ksort) - inv_k[ki.i] = cast(ubyte)i; - foreach (i, ref vi; vsort) - inv_v[vi.i] = cast(ubyte)i; - - // count the string memory - size_t total_string; - foreach (i; 0 .. count) - total_string += 2 + keys[i].length + (keys[i].length & 1); - - // calculate the total size - size_t total_size = VoidEnumInfo.sizeof + T.sizeof*count; - total_size += (total_size & 1) + ushort.sizeof*count + count*3; - total_size += (total_size & 1) + total_string; - - // allocate a buffer and assign all the sub-buffers - void[] info = defaultAllocator().alloc(total_size); - VoidEnumInfo* result = cast(VoidEnumInfo*)info.ptr; - T* value_ptr = cast(T*)&result[1]; - char* str_data = cast(char*)&value_ptr[count]; - if (cast(size_t)str_data & 1) - *str_data++ = 0; // align to 2 bytes - ushort* key_ptr = cast(ushort*)str_data; - ubyte* lookup = cast(ubyte*)&key_ptr[count]; - str_data = cast(char*)&lookup[count*3]; - if (cast(size_t)str_data & 1) - *str_data++ = 0; // align to 2 bytes - char* str_ptr = str_data + 2; - - // populate the enum info data - foreach (i; 0 .. count) - { - value_ptr[i] = vsort[i].v; - - // write the string data and store the key offset - const(char)[] key = ksort[i].v; - key_ptr[i] = cast(ushort)(str_ptr - str_data); - writeString(str_ptr, key); - if (key.length & 1) - (str_ptr++)[key.length] = 0; // align to 2 bytes - str_ptr += 2 + key.length; - - lookup[i] = inv_v[ksort[i].i]; - lookup[count + i] = inv_k[vsort[i].i]; - lookup[count*2 + i] = inv_k[i]; - } - - // build and return the object - return new(*result) VoidEnumInfo(cast(ubyte)keys.length, cast(ushort)T.sizeof, fnv1a(cast(ubyte[])name), value_ptr, key_ptr, str_data, lookup); -} private: -import urt.string : trim; -enum trim_key(string key) = key.trim!(c => c == '_'); - template is_same(alias a, alias b) { static if (!is(typeof(&a && &b)) // at least one is an rvalue @@ -512,23 +158,3 @@ template is_same(A, B) { enum is_same = is(A == B); } - -ushort key_length(const(char)* key) pure -{ - if (__ctfe) - { - version (LittleEndian) - return key[-2] | cast(ushort)(key[-1] << 8); - else - return key[-1] | cast(ushort)(key[-2] << 8); - } - else - return *cast(ushort*)(key - 2); -} - -int key_compare(ushort a, const(char)[] b, const(char)* strings) pure -{ - import urt.string.uni : uni_compare; - const(char)* s = strings + a; - return uni_compare(s[0 .. s.key_length], b); -} From 69f16c16838b7eb5651848df16a34d676291c6b8 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sat, 14 Feb 2026 13:39:58 +1000 Subject: [PATCH 076/138] Improved socket API with functions that can send multiple buffers. --- src/urt/socket.d | 165 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 129 insertions(+), 36 deletions(-) diff --git a/src/urt/socket.d b/src/urt/socket.d index 7902c74..f87ec91 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -34,7 +34,7 @@ else version (Posix) import core.sys.posix.netinet.in_ : in6_addr, sockaddr_in6; alias _bind = urt.internal.os.bind, _listen = urt.internal.os.listen, _connect = urt.internal.os.connect, - _accept = urt.internal.os.accept, _send = urt.internal.os.send, _sendto = urt.internal.os.sendto, + _accept = urt.internal.os.accept, _send = urt.internal.os.send, _sendto = urt.internal.os.sendto, _sendmsg = urt.internal.os.sendmsg, _recv = urt.internal.os.recv, _recvfrom = urt.internal.os.recvfrom, _shutdown = urt.internal.os.shutdown; alias _poll = core.sys.posix.poll.poll; @@ -146,10 +146,9 @@ enum SocketOption : ubyte enum MsgFlags : ubyte { none = 0, - oob = 1 << 0, - peek = 1 << 1, - confirm = 1 << 2, - no_sig = 1 << 3, + peek = 1 << 0, + confirm = 1 << 1, + no_sig = 1 << 2, //... } @@ -251,11 +250,11 @@ Result shutdown(Socket socket, SocketShutdownMode how) Result bind(Socket socket, ref const InetAddress address) { ubyte[512] buffer = void; - size_t addrLen; - sockaddr* sock_addr = make_sockaddr(address, buffer, addrLen); + size_t addr_len; + sockaddr* sock_addr = make_sockaddr(address, buffer, addr_len); assert(sock_addr, "Invalid socket address"); - if (_bind(socket.handle, sock_addr, cast(int)addrLen) < 0) + if (_bind(socket.handle, sock_addr, cast(int)addr_len) < 0) return socket_getlasterror(); return Result.success; } @@ -270,11 +269,11 @@ Result listen(Socket socket, uint backlog = -1) Result connect(Socket socket, ref const InetAddress address) { ubyte[512] buffer = void; - size_t addrLen; - sockaddr* sock_addr = make_sockaddr(address, buffer, addrLen); + size_t addr_len; + sockaddr* sock_addr = make_sockaddr(address, buffer, addr_len); assert(sock_addr, "Invalid socket address"); - if (_connect(socket.handle, sock_addr, cast(int)addrLen) < 0) + if (_connect(socket.handle, sock_addr, cast(int)addr_len) < 0) return socket_getlasterror(); return Result.success; } @@ -303,41 +302,114 @@ Result accept(Socket socket, out Socket connection, InetAddress* remote_address } Result send(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.none, size_t* bytes_sent = null) -{ - Result r = Result.success; + => send(socket, flags, bytes_sent, (&message)[0..1]); - ptrdiff_t sent = _send(socket.handle, message.ptr, cast(int)message.length, map_message_flags(flags)); - if (sent < 0) +Result send(Socket socket, MsgFlags flags, size_t* bytes_sent, const void[][] buffers...) +{ + version (Windows) { - r = socket_getlasterror(); - sent = 0; + uint sent = void; + WSABUF[32] bufs = void; + assert(buffers.length <= bufs.length, "Too many buffers!"); + + uint n = 0; + foreach(buffer; buffers) + { + if (buffer.length == 0) + continue; + assert(buffer.length <= uint.max, "Buffer too large!"); + bufs[n].buf = cast(char*)buffer.ptr; + bufs[n++].len = cast(uint)buffer.length; + } + + int rc = WSASend(socket.handle, bufs.ptr, n, &sent, /+map_message_flags(flags)+/ 0, null, null); // there are no meaningful flags on Windows + if (rc == SOCKET_ERROR) + return socket_getlasterror(); + if (bytes_sent) + *bytes_sent = sent; + return Result.success; } - if (bytes_sent) - *bytes_sent = sent; - return r; + else + return sendmsg(socket, null, flags, null, bytes_sent, buffers); } Result sendto(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.none, const InetAddress* address = null, size_t* bytes_sent = null) + => sendmsg(socket, address, flags, null, bytes_sent, (&message)[0..1]); + +Result sendto(Socket socket, const InetAddress* address, size_t* bytes_sent, const void[][] buffers...) + => sendmsg(socket, address, MsgFlags.none, null, bytes_sent, buffers); + +Result sendmsg(Socket socket, const InetAddress* address, MsgFlags flags, const(void)[] control, size_t* bytes_sent, const void[][] buffers) { ubyte[sockaddr_storage.sizeof] tmp = void; - size_t addrLen; + size_t addr_len; sockaddr* sock_addr = null; if (address) { - sock_addr = make_sockaddr(*address, tmp, addrLen); + sock_addr = make_sockaddr(*address, tmp, addr_len); assert(sock_addr, "Invalid socket address"); } - Result r = Result.success; - ptrdiff_t sent = _sendto(socket.handle, message.ptr, cast(int)message.length, map_message_flags(flags), sock_addr, cast(int)addrLen); - if (sent < 0) + version (Windows) + { + uint sent = void; + WSAMSG msg; + WSABUF[32] bufs = void; + assert(buffers.length <= bufs.length, "Too many buffers!"); + + uint n = 0; + foreach(buffer; buffers) + { + if (buffer.length == 0) + continue; + assert(buffer.length <= uint.max, "Buffer too large!"); + bufs[n].buf = cast(char*)buffer.ptr; + bufs[n++].len = cast(uint)buffer.length; + } + + msg.name = sock_addr; + msg.namelen = cast(int)addr_len; + msg.lpBuffers = bufs.ptr; + msg.dwBufferCount = n; + msg.Control.buf = cast(char*)control.ptr; + msg.Control.len = cast(uint)control.length; + msg.dwFlags = 0; + + int rc = WSASendMsg(socket.handle, &msg, /+map_message_flags(flags)+/ 0, &sent, null, null); // there are no meaningful flags on Windows + if (rc == SOCKET_ERROR) + return socket_getlasterror(); + } + else { - r = socket_getlasterror(); - sent = 0; + msghdr hdr; + iovec[32] iov = void; + assert(buffers.length <= iov.length, "Too many buffers!"); + + size_t n = 0; + foreach(buffer; buffers) + { + if (buffer.length == 0) + continue; + assert(buffer.length <= uint.max, "Buffer too large!"); + iov[n].iov_base = cast(void*)buffer.ptr; + iov[n++].iov_len = buffer.length; + } + + hdr.msg_name = sock_addr; + hdr.msg_namelen = cast(socklen_t)addr_len; + hdr.msg_iov = iov.ptr; + hdr.msg_iovlen = n; + hdr.msg_control = cast(void*)control.ptr; + hdr.msg_controllen = control.length; + hdr.msg_flags = 0; + + ptrdiff_t sent = _sendmsg(socket.handle, &hdr, map_message_flags(flags)); + if (sent < 0) + return socket_getlasterror(); } if (bytes_sent) *bytes_sent = sent; - return r; + return Result.success; } Result recv(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.none, size_t* bytes_received) @@ -1046,7 +1118,7 @@ SocketResult socket_result(Result result) } -sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_t addrLen) +sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_t addr_len) { sockaddr* sock_addr = cast(sockaddr*)buffer.ptr; @@ -1054,7 +1126,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ { case AddressFamily.ipv4: { - addrLen = sockaddr_in.sizeof; + addr_len = sockaddr_in.sizeof; if (buffer.length < sockaddr_in.sizeof) return null; @@ -1079,7 +1151,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ { version (HasIPv6) { - addrLen = sockaddr_in6.sizeof; + addr_len = sockaddr_in6.sizeof; if (buffer.length < sockaddr_in6.sizeof) return null; @@ -1107,7 +1179,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ { // version (HasUnixSocket) // { -// addrLen = sockaddr_un.sizeof; +// addr_len = sockaddr_un.sizeof; // if (buffer.length < sockaddr_un.sizeof) // return null; // @@ -1124,7 +1196,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ default: { sock_addr = null; - addrLen = 0; + addr_len = 0; assert(false, "Unsupported address family"); break; @@ -1422,7 +1494,6 @@ else int map_message_flags(MsgFlags flags) { int r = 0; - if (flags & MsgFlags.oob) r |= MSG_OOB; if (flags & MsgFlags.peek) r |= MSG_PEEK; version (linux) { @@ -1471,24 +1542,29 @@ version (Windows) // this is truly the worst thing I ever wrote!! enum SIO_GET_EXTENSION_FUNCTION_POINTER = 0xC8000006; struct GUID { uint Data1; ushort Data2, Data3; ubyte[8] Data4; } + __gshared immutable GUID WSAID_WSASENDMSG = GUID(0xA441E712, 0x754F, 0x43CA, [0x84,0xA7,0x0D,0xEE,0x44,0xCF,0x60,0x6D]); __gshared immutable GUID WSAID_WSARECVMSG = GUID(0xF689D7C8, 0x6F1F, 0x436B, [0x8A,0x53,0xE5,0x4F,0xE3,0x51,0xC3,0x22]); Socket dummy; uint bytes = 0; if (!create_socket(AddressFamily.ipv4, SocketType.datagram, Protocol.udp, dummy)) goto FAIL; + if (WSAIoctl(dummy.handle, SIO_GET_EXTENSION_FUNCTION_POINTER, cast(void*)&WSAID_WSASENDMSG, cast(uint)GUID.sizeof, + &WSASendMsg, cast(uint)WSASendMsgFn.sizeof, &bytes, null, null) != 0) + goto FAIL; + assert(bytes == WSASendMsgFn.sizeof); if (WSAIoctl(dummy.handle, SIO_GET_EXTENSION_FUNCTION_POINTER, cast(void*)&WSAID_WSARECVMSG, cast(uint)GUID.sizeof, &WSARecvMsg, cast(uint)WSARecvMsgFn.sizeof, &bytes, null, null) != 0) goto FAIL; assert(bytes == WSARecvMsgFn.sizeof); dummy.close(); - if (!WSARecvMsg) + if (!WSASendMsg || !WSARecvMsg) goto FAIL; return; FAIL: import urt.log; - writeWarning("Failed to get WSARecvMsg function pointer - recvfrom() won't be able to report the dst address"); + writeWarning("Failed to get WSASendMsg/WSARecvMsg function pointers - sendmsg() won't work, recvfrom() won't be able to report the dst address"); } pragma(crt_destructor) @@ -1539,7 +1615,9 @@ version (Windows) } alias LPWSABUF = WSABUF*; + alias WSASendMsgFn = extern(Windows) int function(SOCKET s, LPWSAMSG lpMsg, uint dwFlags, uint* lpNumberOfBytesSent, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); alias WSARecvMsgFn = extern(Windows) int function(SOCKET s, LPWSAMSG lpMsg, uint* lpdwNumberOfBytesRecvd, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); + __gshared WSASendMsgFn WSASendMsg; __gshared WSARecvMsgFn WSARecvMsg; struct IN_PKTINFO @@ -1581,4 +1659,19 @@ version (Windows) size_t WSA_CMSGDATA_ALIGN(size_t length) => (length + size_t.alignof-1) & ~(size_t.alignof-1); + + struct WSAOVERLAPPED + { + uint Internal; + uint InternalHigh; + uint Offset; + uint OffsetHigh; + HANDLE hEvent; + } + alias LPWSAOVERLAPPED = WSAOVERLAPPED*; + + alias LPWSAOVERLAPPED_COMPLETION_ROUTINE = void function(uint dwError, uint cbTransferred, LPWSAOVERLAPPED lpOverlapped, uint dwFlags); + + extern(Windows) int WSASend(SOCKET s, LPWSABUF lpBuffers, uint dwBufferCount, uint* lpNumberOfBytesSent, uint dwFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); + extern(Windows) int WSASendTo(SOCKET s, LPWSABUF lpBuffers, uint dwBufferCount, uint* lpNumberOfBytesSent, uint dwFlags, const(sockaddr)* lpTo, int iTolen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); } From 578450b214fe773ef4c889956cb1c384446a0969 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sat, 14 Feb 2026 15:31:31 +1000 Subject: [PATCH 077/138] Oops, didn't update the module name! --- src/urt/meta/enuminfo.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/urt/meta/enuminfo.d b/src/urt/meta/enuminfo.d index 75f10c1..d69a58a 100644 --- a/src/urt/meta/enuminfo.d +++ b/src/urt/meta/enuminfo.d @@ -1,4 +1,4 @@ -module urt.meta.enum_; +module urt.meta.enuminfo; import urt.algorithm : binary_search, qsort; import urt.traits :EnumType, is_enum, Unqual; From 6fdb2a9f7aa2f67576f7e16b8875684330434b32 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 15 Feb 2026 02:08:25 +1000 Subject: [PATCH 078/138] Move parse_quantity() to urt.quantity. --- src/urt/conv.d | 45 ------------------------------------------ src/urt/si/quantity.d | 46 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/src/urt/conv.d b/src/urt/conv.d index f0ab5c1..3d600ba 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -1,7 +1,6 @@ module urt.conv; import urt.meta; -import urt.si.quantity; import urt.string; public import urt.string.format : toString; @@ -360,50 +359,6 @@ unittest assert(parse_float("xyz", &taken) is double.nan && taken == 0); } -VarQuantity parse_quantity(const(char)[] text, size_t* bytes_taken = null) nothrow -{ - import urt.si.unit; - - int e; - uint base; - size_t taken; - long raw_value = text.parse_int_with_exponent_and_base(e, base, &taken); - if (taken == 0) - { - if (bytes_taken) - *bytes_taken = 0; - return VarQuantity(double.nan); - } - - // we parsed a number! - auto r = VarQuantity(e == 0 ? raw_value : raw_value * double(base)^^e); - - if (taken < text.length) - { - // try and parse a unit... - ScaledUnit su; - float pre_scale; - ptrdiff_t unit_taken = su.parse_unit(text[taken .. $], pre_scale, false); - if (unit_taken > 0) - { - taken += unit_taken; - r = VarQuantity(r.value * pre_scale, su); - } - } - if (bytes_taken) - *bytes_taken = taken; - return r; -} - -unittest -{ - import urt.si.unit; - - size_t taken; - assert("10V".parse_quantity(&taken) == Volts(10) && taken == 3); - assert("10.2e+2Wh".parse_quantity(&taken) == WattHours(1020) && taken == 9); -} - ptrdiff_t parse(T)(const char[] text, out T result) { diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index e293ebb..8656a2c 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -497,3 +497,49 @@ unittest assert(DegreesC(100).opEquals!epsilon(DegreesF(212))); assert(DegreesF(100).opEquals!epsilon(DegreesC(37.77777777777777))); } + + +VarQuantity parse_quantity(const(char)[] text, size_t* bytes_taken = null) nothrow +{ + import urt.si.unit; + import urt.conv; + + int e; + uint base; + size_t taken; + long raw_value = text.parse_int_with_exponent_and_base(e, base, &taken); + if (taken == 0) + { + if (bytes_taken) + *bytes_taken = 0; + return VarQuantity(double.nan); + } + + // we parsed a number! + auto r = VarQuantity(e == 0 ? raw_value : raw_value * double(base)^^e); + + if (taken < text.length) + { + // try and parse a unit... + ScaledUnit su; + float pre_scale; + ptrdiff_t unit_taken = su.parse_unit(text[taken .. $], pre_scale, false); + if (unit_taken > 0) + { + taken += unit_taken; + r = VarQuantity(r.value * pre_scale, su); + } + } + if (bytes_taken) + *bytes_taken = taken; + return r; +} + +unittest +{ + import urt.si.unit; + + size_t taken; + assert("10V".parse_quantity(&taken) == Volts(10) && taken == 3); + assert("10.2e+2Wh".parse_quantity(&taken) == WattHours(1020) && taken == 9); +} From 6762313bd84be34207ff0f44294f5236b856f5a8 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 19 Feb 2026 11:42:48 +1000 Subject: [PATCH 079/138] Removed IsQuantity flag; it's implied for any Number with `count != 0` --- src/urt/variant.d | 62 ++++++++++++----------------------------------- 1 file changed, 15 insertions(+), 47 deletions(-) diff --git a/src/urt/variant.d b/src/urt/variant.d index 0d8a19e..faf23cd 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -165,17 +165,12 @@ nothrow @nogc: this(U, ScaledUnit _U)(Quantity!(U, _U) q) { this(q.value); - if (q.unit.pack) - { - flags |= Flags.IsQuantity; - count = q.unit.pack; - } + count = q.unit.pack; } this(Duration dur) { this(dur.as!"nsecs"); - flags |= Flags.IsQuantity; count = Nanosecond.pack; } @@ -367,12 +362,10 @@ nothrow @nogc: } else static if (is_some_int!T || is_some_float!T) { - if (!isNumber) + if (!isNumber || isQuantity) return false; static if (is_some_int!T) if (!canFitInt!T) return false; - if (isQuantity) - return asQuantity!double() == Quantity!T(rhs); return as!T == rhs; } else static if (is(T == Quantity!(U, _U), U, ScaledUnit _U)) @@ -431,8 +424,8 @@ nothrow @nogc: static double asDoubleWithBool(ref const Variant v) => v.isBool() ? double(v.asBool()) : v.asDouble(); - uint aunit = a.isQuantity ? a.count : 0; - uint bunit = b.isQuantity ? b.count : 0; + uint aunit = a.count; + uint bunit = b.count; if (aunit || bunit) { // we can't compare different units @@ -445,8 +438,8 @@ nothrow @nogc: // matching units, but we'll only do quantity comparison if there is some scaling if ((aunit >> 24) != (bunit >> 24)) { - Quantity!double aq = a.isQuantity ? a.asQuantity!double() : Quantity!double(asDoubleWithBool(*a)); - Quantity!double bq = b.isQuantity ? b.asQuantity!double() : Quantity!double(asDoubleWithBool(*b)); + VarQuantity aq = a.isNumber ? a.asQuantity!double() : VarQuantity(double(a.asBool())); + VarQuantity bq = b.isNumber ? b.asQuantity!double() : VarQuantity(double(b.asBool())); r = aq.opCmp(bq); break; } @@ -596,9 +589,9 @@ nothrow @nogc: bool isDouble() const pure => (flags & Flags.DoubleFlag) != 0; bool isQuantity() const pure - => (flags & Flags.IsQuantity) != 0; + => isNumber && count != 0; bool isDuration() const pure - => isQuantity && (count & 0xFFFFFF) == Second.pack; + => isNumber && (count & 0xFFFFFF) == Second.pack; bool isBuffer() const pure => type == Type.Buffer; bool isString() const pure @@ -729,15 +722,14 @@ nothrow @nogc: } Quantity!T asQuantity(T = double)() const pure @property - if (is_some_float!T || isSomeInt!T) + if (is_some_float!T || is_some_int!T) { if (isNull) return Quantity!T(0); assert(isNumber); Quantity!T r; r.value = as!T; - if (isQuantity) - r.unit.pack = count; + r.unit.pack = count; return r; } @@ -747,26 +739,11 @@ nothrow @nogc: alias Nanoseconds = Quantity!(long, Nanosecond); Nanoseconds ns; if (size_t.sizeof < 8 && isFloat) // TODO: better way to detect if double is NOT supported in hardware? - { - Quantity!float q; - q.value = asFloat; - q.unit.pack = count; - ns = cast(Nanoseconds)q; - } + ns = cast(Nanoseconds)asQuantity!float(); else if (isDouble) - { - Quantity!double q; - q.value = asDouble; - q.unit.pack = count; - ns = cast(Nanoseconds)q; - } + ns = cast(Nanoseconds)asQuantity!double(); else - { - Quantity!long q; - q.value = asLong; - q.unit.pack = count; - ns = q; - } + ns = asQuantity!long(); return ns.value.dur!"nsecs"; } @@ -969,10 +946,6 @@ nothrow @nogc: { assert(isNumber()); count = unit.pack; - if (count != 0) - flags |= Flags.IsQuantity; - else - flags &= ~Flags.IsQuantity; } // TODO: this seems to interfere with UFCS a lot... @@ -1103,7 +1076,7 @@ nothrow @nogc: assert(false, "String has no closing quote"); } - if (s[0].is_numeric) + if (s[0].is_numeric || ((s[0] == '+' || s[0] == '-') && s.length > 1 && s[1].is_numeric)) { size_t taken; ScaledUnit unit; @@ -1121,11 +1094,7 @@ nothrow @nogc: this = i * 10.0^^e; else this = i; - if (unit.pack) - { - flags |= Flags.IsQuantity; - count = unit.pack; - } + count = unit.pack; return taken; } } @@ -1292,7 +1261,6 @@ package: Uint64Flag = 1 << (TypeBits + 6), FloatFlag = 1 << (TypeBits + 7), DoubleFlag = 1 << (TypeBits + 8), - IsQuantity = 1 << (TypeBits + 9), Embedded = 1 << (TypeBits + 10), NeedDestruction = 1 << (TypeBits + 11), // CopyFlag = 1 << (TypeBits + 12), // maybe we want to know if a thing is a copy, or a reference to an external one? From da99617779679e5bbcf42aca9ae5e6a30e1fb973 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 18 Feb 2026 16:47:21 +1000 Subject: [PATCH 080/138] Add enum support to Variant --- src/urt/meta/enuminfo.d | 42 ++++++++++++++++++++--------------------- src/urt/variant.d | 35 ++++++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/urt/meta/enuminfo.d b/src/urt/meta/enuminfo.d index d69a58a..1a1ef5e 100644 --- a/src/urt/meta/enuminfo.d +++ b/src/urt/meta/enuminfo.d @@ -64,7 +64,7 @@ nothrow @nogc: if (i == count) return Variant(); i = _lookup_tables[i]; - return _get_value(_values + i*stride); + return _get_value(_values + i*stride, &this); } bool contains(const(char)[] key) const pure @@ -74,8 +74,6 @@ nothrow @nogc: } private: - alias GetFun = Variant function(const(void)*) pure; - const void* _values; const ushort* _keys; const char* _string_buffer; @@ -83,7 +81,7 @@ private: // these tables map between indices of keys and values const ubyte* _lookup_tables; - GetFun _get_value; + const GetFun _get_value; this(ubyte count, ushort stride, uint type_hash, inout void* values, inout ushort* keys, inout char* strings, inout ubyte* lookup, GetFun get_value) inout pure { @@ -106,9 +104,9 @@ private: template EnumInfo(E) { - alias UE = Unqual!E; + static assert (is(E == Unqual!E), "EnumInfo can only be instantiated with unqualified types!"); - static if (is(UE == void)) + static if (is(E == void)) alias EnumInfo = VoidEnumInfo; else { @@ -119,7 +117,7 @@ template EnumInfo(E) static assert (EnumInfo.sizeof == EnumInfo.sizeof, "Template EnumInfo must not add any members!"); - static if (is(UE T == enum)) + static if (is(E T == enum)) alias V = T; else static assert(false, E.string ~ " is not an enum type!"); @@ -129,7 +127,7 @@ template EnumInfo(E) VoidEnumInfo _base; struct { ubyte[VoidEnumInfo._values.offsetof] _pad; - const UE* _values; // shadows the _values in _base with a typed version + const E* _values; // shadows the _values in _base with a typed version } } alias _base this; @@ -137,12 +135,12 @@ template EnumInfo(E) inout(VoidEnumInfo*) make_void() inout pure => &_base; - this(ubyte count, uint type_hash, inout UE* values, inout ushort* keys, inout char* strings, inout ubyte* lookup) inout pure + this(ubyte count, uint type_hash, inout(E)* values, inout ushort* keys, inout char* strings, inout ubyte* lookup) inout pure { - _base = inout(VoidEnumInfo)(count, UE.sizeof, type_hash, values, keys, strings, lookup, cast(VoidEnumInfo.GetFun)&get_value!UE); + _base = inout(VoidEnumInfo)(count, E.sizeof, type_hash, values, keys, strings, lookup, cast(GetFun)&get_value!V); } - const(UE)[] values() const pure + const(E)[] values() const pure => _values[0 .. count]; const(char)[] key_for(V value) const pure @@ -159,7 +157,7 @@ template EnumInfo(E) const(char)[] key_by_sorted_index(size_t i) const pure => _base.key_by_sorted_index(i); - const(UE)* value_for(const(char)[] key) const pure + const(E)* value_for(const(char)[] key) const pure { size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); if (i == count) @@ -174,16 +172,16 @@ template EnumInfo(E) } template enum_info(E) - if (is(Unqual!E == enum)) + if (is(E == enum)) { - alias UE = Unqual!E; + static assert (is(E == Unqual!E), "EnumInfo can only be instantiated with unqualified types!"); enum ubyte num_items = enum_members.length; static assert(num_items <= ubyte.max, "Too many enum items!"); - __gshared immutable enum_info = immutable(EnumInfo!UE)( + __gshared immutable enum_info = immutable(EnumInfo!E)( num_items, - fnv1a(cast(ubyte[])UE.stringof), + fnv1a(cast(ubyte[])E.stringof), _values.ptr, _keys.ptr, _strings.ptr, @@ -196,7 +194,7 @@ private: import urt.string.uni : uni_compare; // keys and values are sorted for binary search - __gshared immutable UE[num_items] _values = [ STATIC_MAP!(GetValue, iota) ]; + __gshared immutable E[num_items] _values = [ STATIC_MAP!(GetValue, iota) ]; // keys are stored as offsets info the string buffer __gshared immutable ushort[num_items] _keys = () { @@ -251,7 +249,7 @@ private: } struct VI { - UE v; + E v; ubyte i; } @@ -361,14 +359,16 @@ VoidEnumInfo* make_enum_info(T)(const(char)[] name, const(char)[][] keys, T[] va } // build and return the object - return new(*result) VoidEnumInfo(cast(ubyte)keys.length, cast(ushort)T.sizeof, fnv1a(cast(ubyte[])name), value_ptr, key_ptr, str_data, lookup, cast(VoidEnumInfo.GetFun)&get_value!T); + return new(*result) VoidEnumInfo(cast(ubyte)keys.length, cast(ushort)T.sizeof, fnv1a(cast(ubyte[])name), value_ptr, key_ptr, str_data, lookup, cast(GetFun)&get_value!T); } private: -Variant get_value(E)(const(void)* ptr) - => Variant(*cast(const(E)*)ptr); +alias GetFun = Variant function(const(void)*, const(VoidEnumInfo)*) pure; + +Variant get_value(T)(const(void)* ptr, const(VoidEnumInfo)* info) + => Variant(*cast(T*)ptr, info); import urt.string : trim; enum trim_key(string key) = key.trim!(c => c == '_'); diff --git a/src/urt/variant.d b/src/urt/variant.d index faf23cd..6834a90 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -7,6 +7,7 @@ import urt.kvp; import urt.lifetime; import urt.map; import urt.mem.allocator; +import urt.meta.enuminfo : enum_info, VoidEnumInfo; import urt.si.quantity; import urt.si.unit : ScaledUnit, Second, Nanosecond; import urt.time; @@ -152,14 +153,28 @@ nothrow @nogc: } } - this(E)(E e) + this(E)(const E e) if (is(E == enum)) { static if (is(E T == enum)) + this(T(e), enum_info!E.make_void()); + } + + this(T)(T value, const(VoidEnumInfo)* e) + { + static assert(!is(T == enum), "T should be a numeric type"); + + this(value); + static if (size_t.sizeof == 8) { - this(T(e)); - // TODO: do we keep a record of the enum keys for stringification? + size_t ptr = cast(size_t)e; + assert((ptr >> 48) == 0, "Uh on! High ptr bits set... we must be in the distant future!"); + count = cast(uint)ptr; + alloc = cast(ushort)(ptr >> 32); } + else + count = cast(size_t)e; + flags |= Flags.Enum; } this(U, ScaledUnit _U)(Quantity!(U, _U) q) @@ -589,9 +604,11 @@ nothrow @nogc: bool isDouble() const pure => (flags & Flags.DoubleFlag) != 0; bool isQuantity() const pure - => isNumber && count != 0; + => isNumber && count != 0 && !is_enum; bool isDuration() const pure => isNumber && (count & 0xFFFFFF) == Second.pack; + bool is_enum() const pure + => (flags & Flags.Enum) != 0; bool isBuffer() const pure => type == Type.Buffer; bool isString() const pure @@ -901,6 +918,15 @@ nothrow @nogc: if (ValidUserType!(Unqual!T) && UserTypeReturnByRef!T) => asUser!T; + const(VoidEnumInfo)* get_enum_info() const pure + { + assert(is_enum); + static if (size_t.sizeof == 8) + return cast(VoidEnumInfo*)(count | (size_t(alloc) << 32)); + else + return cast(VoidEnumInfo*)count; + } + size_t length() const pure { if (flags == Flags.Null) @@ -1261,6 +1287,7 @@ package: Uint64Flag = 1 << (TypeBits + 6), FloatFlag = 1 << (TypeBits + 7), DoubleFlag = 1 << (TypeBits + 8), + Enum = 1 << (TypeBits + 9), Embedded = 1 << (TypeBits + 10), NeedDestruction = 1 << (TypeBits + 11), // CopyFlag = 1 << (TypeBits + 12), // maybe we want to know if a thing is a copy, or a reference to an external one? From a804e14a1e0941d776abaa7286d967e97c1c388e Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 15 Feb 2026 14:36:49 +1000 Subject: [PATCH 081/138] Remove implicit conversion from String -> const(char)[]. - also don't attempt to compare user types if the types doesn't match! --- src/urt/array.d | 5 - src/urt/string/format.d | 753 ++++++++++++++++++++-------------------- src/urt/string/string.d | 105 ++++-- src/urt/variant.d | 206 +++++++---- 4 files changed, 609 insertions(+), 460 deletions(-) diff --git a/src/urt/array.d b/src/urt/array.d index 4f61066..1159e0b 100644 --- a/src/urt/array.d +++ b/src/urt/array.d @@ -878,11 +878,6 @@ struct SharedArray(T) nothrow @nogc: - void opAssign(typeof(null)) - { - clear(); - } - void opAssign(ref typeof(this) val) { clear(); diff --git a/src/urt/string/format.d b/src/urt/string/format.d index ae564c6..c34917d 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -68,7 +68,7 @@ char[] concat(Args...)(char[] buffer, auto ref Args args) } return buffer.ptr[0 .. offset]; } - static if (allAreStrings!Args) + else static if (allAreStrings!Args) { // TODO: why can't inline this?! // pragma(inline, true); @@ -242,468 +242,473 @@ struct DefInt(T) ptrdiff_t defToString(T)(ref const(T) value, char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) nothrow @nogc => (cast(DefFormat!T*)&value).toString(buffer, format, formatArgs); -struct DefFormat(T) +template DefFormat(T) { - T value; - - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc + static if (is(T == const U, U) || is(T == immutable U, U)) + alias DefFormat = DefFormat!U; + else static if (is(T == const(U)[], U) || is(T == immutable(U)[], U)) + alias DefFormat = DefFormat!(U[]); + else struct DefFormat { - static if (is(T == typeof(null))) + static assert(!is(T == const), "How did this slip through?"); + + const T value; + + ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { - if (!buffer.ptr) + static if (is(T == U[N], U, size_t N)) + return defToString!(U[])(value[], buffer, format, formatArgs); + else static if (is(T : String) || is(T : MutableString!N, size_t N) || is(T : Array!(char, N), size_t N)) + return defToString!(char[])(value[], buffer, format, formatArgs); + else static if (is(T == typeof(null))) + { + if (!buffer.ptr) + return 4; + if (buffer.length < 4) + return -1; + buffer[0 .. 4] = "null"; return 4; - if (buffer.length < 4) - return -1; - buffer[0 .. 4] = "null"; - return 4; - } - else static if (is(T == bool)) - { - size_t len = value ? 4 : 5; - if (!buffer.ptr) + } + else static if (is(T == bool)) + { + size_t len = value ? 4 : 5; + if (!buffer.ptr) + return len; + if (buffer.length < len) + return -1; + buffer[0 .. len] = value ? "true" : "false"; return len; - if (buffer.length < len) - return -1; - buffer[0 .. len] = value ? "true" : "false"; - return len; - } - else static if (is(T == char) || is(T == wchar) || is(T == dchar)) - { - if (is(T == char) || value <= 0x7F) + } + else static if (is(T == char) || is(T == wchar) || is(T == dchar)) { - if (buffer.ptr) + if (is(T == char) || value <= 0x7F) { - if (buffer.length < 1) - return -1; - buffer[0] = cast(char)value; + if (buffer.ptr) + { + if (buffer.length < 1) + return -1; + buffer[0] = cast(char)value; + } + return 1; } - return 1; - } - else if (value <= 0x7FF) - { - if (buffer.ptr) + else if (value <= 0x7FF) { - if (buffer.length < 2) - return -1; - buffer[0] = cast(char)(0xC0 | (value >> 6)); - buffer[1] = cast(char)(0x80 | (value & 0x3F)); + if (buffer.ptr) + { + if (buffer.length < 2) + return -1; + buffer[0] = cast(char)(0xC0 | (value >> 6)); + buffer[1] = cast(char)(0x80 | (value & 0x3F)); + } + return 2; } - return 2; - } - else if (value <= 0xFFFF) - { - if (buffer.ptr) + else if (value <= 0xFFFF) { - if (buffer.length < 3) - return -1; - buffer[0] = cast(char)(0xE0 | (value >> 12)); - buffer[1] = cast(char)(0x80 | ((value >> 6) & 0x3F)); - buffer[2] = cast(char)(0x80 | (value & 0x3F)); + if (buffer.ptr) + { + if (buffer.length < 3) + return -1; + buffer[0] = cast(char)(0xE0 | (value >> 12)); + buffer[1] = cast(char)(0x80 | ((value >> 6) & 0x3F)); + buffer[2] = cast(char)(0x80 | (value & 0x3F)); + } + return 3; } - return 3; - } - else if (value <= 0x10FFFF) - { - if (buffer.ptr) + else if (value <= 0x10FFFF) { - if (buffer.length < 4) - return -1; - buffer[0] = cast(char)(0xF0 | (value >> 18)); - buffer[1] = cast(char)(0x80 | ((value >> 12) & 0x3F)); - buffer[2] = cast(char)(0x80 | ((value >> 6) & 0x3F)); - buffer[3] = cast(char)(0x80 | (value & 0x3F)); + if (buffer.ptr) + { + if (buffer.length < 4) + return -1; + buffer[0] = cast(char)(0xF0 | (value >> 18)); + buffer[1] = cast(char)(0x80 | ((value >> 12) & 0x3F)); + buffer[2] = cast(char)(0x80 | ((value >> 6) & 0x3F)); + buffer[3] = cast(char)(0x80 | (value & 0x3F)); + } + return 4; + } + else + { + assert(false, "Invalid code point"); + return 0; } - return 4; } - else + else static if (is(T == double) || is(T == float)) { - assert(false, "Invalid code point"); - return 0; - } - } - else static if (is(T == double) || is(T == float)) - { - import urt.conv : format_float, format_int; + import urt.conv : format_float, format_int; - char[16] tmp = void; - if (format.length && format[0] == '*') - { - bool success; - size_t arg = format[1..$].parse_int_fast(success); - if (!success || !formatArgs[arg].canInt) - return -2; - size_t width = formatArgs[arg].getInt; - size_t len = width.format_int(tmp); - format = tmp[0..len]; + char[16] tmp = void; + if (format.length && format[0] == '*') + { + bool success; + size_t arg = format[1..$].parse_int_fast(success); + if (!success || !formatArgs[arg].canInt) + return -2; + size_t width = formatArgs[arg].getInt; + size_t len = width.format_int(tmp); + format = tmp[0..len]; + } + return format_float(value, buffer, format); } - return format_float(value, buffer, format); - } - else static if (is(T == ulong) || is(T == long)) - { - import urt.conv : format_int, format_uint; + else static if (is(T == ulong) || is(T == long)) + { + import urt.conv : format_int, format_uint; - // TODO: what formats are interesting for ints? + // TODO: what formats are interesting for ints? - bool leadingZeroes = false; - bool to_lower = false; - bool varLen = false; - ptrdiff_t padding = 0; - uint base = 10; + bool leadingZeroes = false; + bool to_lower = false; + bool varLen = false; + ptrdiff_t padding = 0; + uint base = 10; - static if (is(T == long)) - { - bool show_sign = false; - if (format.length && format[0] == '+') + static if (is(T == long)) + { + bool show_sign = false; + if (format.length && format[0] == '+') + { + show_sign = true; + format.popFront; + } + } + if (format.length && format[0] == '0') { - show_sign = true; + leadingZeroes = true; format.popFront; } - } - if (format.length && format[0] == '0') - { - leadingZeroes = true; - format.popFront; - } - if (format.length && format[0] == '*') - { - varLen = true; - format.popFront; - } - if (format.length && format[0].is_numeric) - { - bool success; - padding = format.parse_int_fast(success); - if (varLen) + if (format.length && format[0] == '*') { - if (padding < 0 || !formatArgs[padding].canInt) - return -2; - padding = formatArgs[padding].getInt; + varLen = true; + format.popFront; } - } - if (format.length) - { - char b = format[0] | 0x20; - if (b == 'x') + if (format.length && format[0].is_numeric) { - base = 16; - to_lower = format[0] == 'x' && buffer.ptr; + bool success; + padding = format.parse_int_fast(success); + if (varLen) + { + if (padding < 0 || !formatArgs[padding].canInt) + return -2; + padding = formatArgs[padding].getInt; + } + } + if (format.length) + { + char b = format[0] | 0x20; + if (b == 'x') + { + base = 16; + to_lower = format[0] == 'x' && buffer.ptr; + } + else if (b == 'b') + base = 2; + else if (b == 'o') + base = 8; + else if (b == 'd') + base = 10; + format.popFront; } - else if (b == 'b') - base = 2; - else if (b == 'o') - base = 8; - else if (b == 'd') - base = 10; - format.popFront; - } - static if (is(T == long)) - ptrdiff_t len = format_int(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', show_sign); - else - ptrdiff_t len = format_uint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); + static if (is(T == long)) + ptrdiff_t len = format_int(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', show_sign); + else + ptrdiff_t len = format_uint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); + + if (to_lower && len > 0) + { + for (size_t i = 0; i < len; ++i) + if (cast(uint)(buffer.ptr[i] - 'A') < 26) + buffer.ptr[i] |= 0x20; + } - if (to_lower && len > 0) + return len; + } + else static if (is(T == ubyte) || is(T == ushort) || is(T == uint)) { - for (size_t i = 0; i < len; ++i) - if (cast(uint)(buffer.ptr[i] - 'A') < 26) - buffer.ptr[i] |= 0x20; + return defToString(ulong(value), buffer, format, formatArgs); } - - return len; - } - else static if (is(T == ubyte) || is(T == ushort) || is(T == uint)) - { - return defToString(ulong(value), buffer, format, formatArgs); - } - else static if (is(T == byte) || is(T == short) || is(T == int)) - { - return defToString(long(value), buffer, format, formatArgs); - } - else static if (is(T == const(char)*) || is(T == const(char*))) - { - const char[] t = value[0 .. value.strlen]; - return t.defToString(buffer, format, formatArgs); - } - else static if (is(T : const(U)*, U)) - { - static assert(size_t.sizeof == 4 || size_t.sizeof == 8); - enum Fmt = "0" ~ (size_t.sizeof == 4 ? "8" : "16") ~ "X"; - size_t p = cast(size_t)value; - return p.defToString(buffer, Fmt, null); - } - else static if (is(T == const char[])) - { - bool leftJustify = false; - bool varLen = false; - ptrdiff_t width = value.length; - if (format.length && format[0] == '-') + else static if (is(T == byte) || is(T == short) || is(T == int)) { - leftJustify = true; - format.popFront; + return defToString(long(value), buffer, format, formatArgs); } - if (format.length && format[0] == '*') + else static if (is(T == const(char)*)) { - varLen = true; - format.popFront; + const char[] t = value[0 .. value.strlen]; + return t.defToString(buffer, format, formatArgs); } - if (varLen && (!format.length || !format[0].is_numeric)) - return -2; - if (format.length && format[0].is_numeric) + else static if (is(T : const(U)*, U)) { - bool success; - width = format.parse_int_fast(success); - if (varLen) + static assert(size_t.sizeof == 4 || size_t.sizeof == 8); + enum Fmt = "0" ~ (size_t.sizeof == 4 ? "8" : "16") ~ "X"; + size_t p = cast(size_t)value; + return p.defToString(buffer, Fmt, null); + } + else static if (is(T == char[])) + { + bool leftJustify = false; + bool varLen = false; + ptrdiff_t width = value.length; + if (format.length && format[0] == '-') { - if (width < 0 || !formatArgs[width].canInt) - return -2; - width = formatArgs[width].getInt; + leftJustify = true; + format.popFront; + } + if (format.length && format[0] == '*') + { + varLen = true; + format.popFront; + } + if (varLen && (!format.length || !format[0].is_numeric)) + return -2; + if (format.length && format[0].is_numeric) + { + bool success; + width = format.parse_int_fast(success); + if (varLen) + { + if (width < 0 || !formatArgs[width].canInt) + return -2; + width = formatArgs[width].getInt; + } + if (width < value.length) + width = value.length; } - if (width < value.length) - width = value.length; - } - if (!buffer.ptr) - return width; - if (buffer.length < width) - return -1; + if (!buffer.ptr) + return width; + if (buffer.length < width) + return -1; - // TODO: accept padd string in the formatSpec? + // TODO: accept padd string in the formatSpec? - size_t padding = width - value.length; - size_t pad = 0, len = 0; - if (!leftJustify && padding > 0) - { - pad = buffer.length < padding ? buffer.length : padding; - buffer[0 .. pad] = ' '; - buffer.takeFront(pad); + size_t padding = width - value.length; + size_t pad = 0, len = 0; + if (!leftJustify && padding > 0) + { + pad = buffer.length < padding ? buffer.length : padding; + buffer[0 .. pad] = ' '; + buffer.takeFront(pad); + } + len = buffer.length < value.length ? buffer.length : value.length; + buffer[0 .. len] = value[0 .. len]; + if (padding > 0 && leftJustify) + { + buffer.takeFront(len); + pad = buffer.length < padding ? buffer.length : padding; + buffer[0 .. pad] = ' '; + } + return pad + len; } - len = buffer.length < value.length ? buffer.length : value.length; - buffer[0 .. len] = value[0 .. len]; - if (padding > 0 && leftJustify) +// else static if (is(T == wchar[])) +// { +// } +// else static if (is (T == U[], U : dchar)) +// { +// // TODO: UTF ENCODE... +// } + else static if (is(T == void[])) { - buffer.takeFront(len); - pad = buffer.length < padding ? buffer.length : padding; - buffer[0 .. pad] = ' '; - } - return pad + len; - } - else static if (is(T : const char[])) - { - return defToString!(const char[])(cast(const char[])value[], buffer, format, formatArgs); - } -// else static if (is(T : const(wchar)[])) -// { -// } -// else static if (is (T : const(U)[], U : dchar)) -// { -// // TODO: UTF ENCODE... -// } - else static if (is(T == void[]) || is(T == const(void)[])) - { - if (!value.length) - return 0; + if (!value.length) + return 0; - int grp1 = 1, grp2 = 0; - if (format.length && format[0].is_numeric) - { - bool success; - grp1 = cast(int)format.parse_int_fast(success); - if (success && format.length > 0 && format[0] == ':' && - format.length > 1 && format[1].is_numeric) + int grp1 = 1, grp2 = 0; + if (format.length && format[0].is_numeric) { - format.popFront(); - grp2 = cast(int)format.parse_int_fast(success); + bool success; + grp1 = cast(int)format.parse_int_fast(success); + if (success && format.length > 0 && format[0] == ':' && + format.length > 1 && format[1].is_numeric) + { + format.popFront(); + grp2 = cast(int)format.parse_int_fast(success); + } + if (!success) + return -2; } - if (!success) - return -2; - } - if (!buffer.ptr) - { - size_t len = value.length*2; - if (grp1) - len += (value.length-1) / grp1; - return len; - } + if (!buffer.ptr) + { + size_t len = value.length*2; + if (grp1) + len += (value.length-1) / grp1; + return len; + } - char[] hex = toHexString(cast(ubyte[])value, buffer, grp1, grp2); - if (!hex.ptr) - return -1; - return hex.length; - } - else static if (is(T : const U[], U)) - { - // arrays of other stuff - size_t len = 1; - if (buffer.ptr) - { - if (buffer.length < 1) + char[] hex = toHexString(cast(ubyte[])value, buffer, grp1, grp2); + if (!hex.ptr) return -1; - buffer[0] = '['; + return hex.length; } - - for (size_t i = 0; i < value.length; ++i) + else static if (is(T : const U[], U)) { - if (i > 0) + // arrays of other stuff + size_t len = 1; + if (buffer.ptr) + { + if (buffer.length < 1) + return -1; + buffer[0] = '['; + } + + for (size_t i = 0; i < value.length; ++i) { + if (i > 0) + { + if (buffer.ptr) + { + if (len == buffer.length) + return -1; + buffer[len] = ','; + } + ++len; + } + + FormatArg arg = FormatArg(value[i]); if (buffer.ptr) { - if (len == buffer.length) - return -1; - buffer[len] = ','; + ptrdiff_t argLen = arg.getString(buffer.ptr[len .. buffer.length], format, formatArgs); + if (argLen < 0) + return argLen; + len += argLen; } - ++len; + else + len += arg.getLength(format, formatArgs); } - FormatArg arg = FormatArg(value[i]); if (buffer.ptr) { - ptrdiff_t argLen = arg.getString(buffer.ptr[len .. buffer.length], format, formatArgs); - if (argLen < 0) - return argLen; - len += argLen; + if (len == buffer.length) + return -1; + buffer[len] = ']'; } - else - len += arg.getLength(format, formatArgs); + return ++len; } - - if (buffer.ptr) + else static if (is(T B == enum)) { - if (len == buffer.length) - return -1; - buffer[len] = ']'; - } - return ++len; - } - else static if (is(T B == enum)) - { - // TODO: optimise short enums with a TABLE! + // TODO: optimise short enums with a TABLE! - // TODO: should probably return FQN ??? - string key = null; - val: switch (value) - { - static foreach (i, KeyName; __traits(allMembers, T)) + // TODO: should probably return FQN ??? + string key = null; + val: switch (value) { - static if (!EnumKeyIsDuplicate!(T, i)) + static foreach (i, KeyName; __traits(allMembers, T)) { - case __traits(getMember, T, KeyName): - key = KeyName; - break val; + static if (!EnumKeyIsDuplicate!(T, i)) + { + case __traits(getMember, T, KeyName): + key = KeyName; + break val; + } } - } - default: - if (!buffer.ptr) - return T.stringof.length + 2 + defToString!B(cast(B)value, null, null, null); + default: + if (!buffer.ptr) + return T.stringof.length + 2 + defToString!B(cast(B)value, null, null, null); - if (buffer.length < T.stringof.length + 2) - return -1; - buffer[0 .. T.stringof.length] = T.stringof; - buffer[T.stringof.length] = '('; - ptrdiff_t len = defToString!B(*cast(B*)&value, buffer[T.stringof.length + 1 .. $], null, null); - if (len < 0) + if (buffer.length < T.stringof.length + 2) + return -1; + buffer[0 .. T.stringof.length] = T.stringof; + buffer[T.stringof.length] = '('; + ptrdiff_t len = defToString!B(*cast(B*)&value, buffer[T.stringof.length + 1 .. $], null, null); + if (len < 0) + return len; + len = T.stringof.length + 2 + len; + if (buffer.length < len) + return -1; + buffer[len - 1] = ')'; return len; - len = T.stringof.length + 2 + len; - if (buffer.length < len) - return -1; - buffer[len - 1] = ')'; + } + + size_t len = T.stringof.length + 1 + key.length; + if (!buffer.ptr) return len; - } - size_t len = T.stringof.length + 1 + key.length; - if (!buffer.ptr) + if (buffer.length < len) + return -1; + buffer[0 .. T.stringof.length] = T.stringof; + buffer[T.stringof.length] = '.'; + buffer[T.stringof.length + 1 .. len] = key[]; return len; - - if (buffer.length < len) - return -1; - buffer[0 .. T.stringof.length] = T.stringof; - buffer[T.stringof.length] = '.'; - buffer[T.stringof.length + 1 .. len] = key[]; - return len; - } - else static if (is(T == class)) - { - // HACK: class toString is not @nogc, so we'll just stringify the pointer for right now... - return defToString(cast(void*)value, buffer, format, formatArgs); -/+ - try + } + else static if (is(T == class)) { - const(char)[] t = (cast()value).toString(); - if (!buffer.ptr) + // HACK: class toString is not @nogc, so we'll just stringify the pointer for right now... + return defToString(cast(void*)value, buffer, format, formatArgs); +/+ + try + { + const(char)[] t = (cast()value).toString(); + if (!buffer.ptr) + return t.length; + if (buffer.length < t.length) + return -1; + buffer[0 .. t.length] = t[]; return t.length; - if (buffer.length < t.length) + } + catch (Exception) return -1; - buffer[0 .. t.length] = t[]; - return t.length; - } - catch (Exception) - return -1; +/ - } - else static if (is(T == const)) - { - return defToString!(Unqual!T)(cast()value, buffer, format, formatArgs); - } - else static if (is(T == struct)) - { - static assert(!__traits(hasMember, T, "toString"), "Struct with custom toString not properly selected!"); - - // general structs - if (buffer.ptr) - { - if (buffer.length < T.stringof.length + 2) - return -1; - buffer[0 .. T.stringof.length] = T.stringof; - buffer[T.stringof.length] = '('; } + else static if (is(T == struct)) + { + static assert(!__traits(hasMember, T, "toString"), "Struct with custom toString not properly selected!"); - size_t len = T.stringof.length + 1; - static foreach (i; 0 .. value.tupleof.length) - {{ - static if (i > 0) + // general structs + if (buffer.ptr) { + if (buffer.length < T.stringof.length + 2) + return -1; + buffer[0 .. T.stringof.length] = T.stringof; + buffer[T.stringof.length] = '('; + } + + size_t len = T.stringof.length + 1; + static foreach (i; 0 .. value.tupleof.length) + {{ + static if (i > 0) + { + if (buffer.ptr) + { + if (len + 2 > buffer.length) + return -1; + buffer[len .. len + 2] = ", "; + } + len += 2; + } + + FormatArg arg = FormatArg(value.tupleof[i]); if (buffer.ptr) { - if (len + 2 > buffer.length) - return -1; - buffer[len .. len + 2] = ", "; + ptrdiff_t argLen = arg.getString(buffer.ptr[len .. buffer.length], null, null); + if (argLen < 0) + return argLen; + len += argLen; } - len += 2; - } + else + len += arg.getLength(null, null); + + }} - FormatArg arg = FormatArg(value.tupleof[i]); if (buffer.ptr) { - ptrdiff_t argLen = arg.getString(buffer.ptr[len .. buffer.length], null, null); - if (argLen < 0) - return argLen; - len += argLen; + if (len == buffer.length) + return -1; + buffer[len] = ')'; } - else - len += arg.getLength(null, null); - - }} - - if (buffer.ptr) + return ++len; + } + else static if (is(T == function)) { - if (len == buffer.length) - return -1; - buffer[len] = ')'; + assert(false, "TODO"); + return 0; } - return ++len; - } - else static if (is(T == function)) - { - assert(false, "TODO"); - return 0; - } - else static if (is(T == delegate)) - { - assert(false, "TODO"); - return 0; + else static if (is(T == delegate)) + { + assert(false, "TODO"); + return 0; + } + else + static assert(false, "Not implemented for type: ", T.stringof); } - else - static assert(false, "Not implemented for type: ", T.stringof); } } diff --git a/src/urt/string/string.d b/src/urt/string/string.d index ccb06f9..440c395 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -215,8 +215,7 @@ inout(char)[] as_dstring(inout(char)* s) pure nothrow @nogc struct String { nothrow @nogc: - - alias toString this; + alias This = typeof(this); const(char)* ptr; @@ -225,17 +224,15 @@ nothrow @nogc: this.ptr = null; } - this(ref inout typeof(this) rhs) inout pure + this(ref inout This rhs) inout pure { ptr = rhs.ptr; - if (ptr) + if (!ptr) + return; + if (ushort* rc = ((cast(ushort*)ptr)[-1] >> 15) ? cast(ushort*)ptr - 2 : null) { - ushort* rc = ((cast(ushort*)ptr)[-1] >> 15) ? cast(ushort*)ptr - 2 : null; - if (rc) - { - assert((*rc & 0x3FFF) < 0x3FFF, "Reference count overflow"); - ++*rc; - } + assert((*rc & 0x3FFF) < 0x3FFF, "Reference count overflow"); + ++*rc; } } @@ -293,6 +290,9 @@ nothrow @nogc: return ptr ? ((cast(ushort*)ptr)[-1] & 0x7FFF) : 0; } + bool empty() const pure + => length() == 0; + bool opCast(T : bool)() const pure => ptr != null && ((cast(ushort*)ptr)[-1] & 0x7FFF) != 0; @@ -321,7 +321,6 @@ nothrow @nogc: ptr = cs.ptr; } - bool opEquals(const(char)[] rhs) const pure { if (!ptr) @@ -330,6 +329,15 @@ nothrow @nogc: return len == rhs.length && (ptr == rhs.ptr || ptr[0 .. len] == rhs[]); } + int opEquals(ref const String rhs) const pure + => opEquals(rhs[]); + + int opEquals(size_t N)(ref const MutableString!N rhs) const pure + => opEquals(rhs[]); + + int opEquals(size_t N)(ref const Array!N rhs) const pure + => opEquals(rhs[]); + int opCmp(const(char)[] rhs) const pure { import urt.algorithm : compare; @@ -338,14 +346,24 @@ nothrow @nogc: return compare(ptr[0 .. length()], rhs); } + int opCmp(ref const String rhs) const pure + => opCmp(rhs[]); + + int opCmp(size_t N)(ref const MutableString!N rhs) const pure + => opCmp(rhs[]); + + int opCmp(size_t N)(ref const Array!N rhs) const pure + => opCmp(rhs[]); + size_t toHash() const pure { if (!ptr) return 0; + ushort len = (cast(ushort*)ptr)[-1] & 0x7FFF; static if (size_t.sizeof == 4) - return fnv1a(cast(ubyte[])ptr[0 .. length]); + return fnv1a(cast(ubyte[])ptr[0 .. len]); else - return fnv1a64(cast(ubyte[])ptr[0 .. length]); + return fnv1a64(cast(ubyte[])ptr[0 .. len]); } const(char)[] opIndex() const pure @@ -366,6 +384,9 @@ nothrow @nogc: size_t opDollar() const pure => length(); + bool has_rc() const pure + => ptr && ((cast(ushort*)ptr)[-1] >> 15) != 0; + private: auto __debugOverview() const pure { debug return ptr[0 .. length].debugExcapeString(); else return ptr[0 .. length]; } auto __debugExpanded() const pure => ptr[0 .. length]; @@ -517,8 +538,6 @@ nothrow @nogc: static assert(Embed == 0, "Not without move semantics!"); - alias toString this; - char* ptr; // TODO: DELETE POSTBLIT! @@ -590,9 +609,57 @@ nothrow @nogc: ushort length() const pure => ptr ? ((cast(ushort*)ptr)[-1] & 0x7FFF) : 0; + bool empty() const pure + => length() == 0; + bool opCast(T : bool)() const pure => ptr != null && ((cast(ushort*)ptr)[-1] & 0x7FFF) != 0; + bool opEquals(const(char)[] rhs) const pure + { + if (!ptr) + return rhs.length == 0; + ushort len = (cast(ushort*)ptr)[-1] & 0x7FFF; + return len == rhs.length && (ptr == rhs.ptr || ptr[0 .. len] == rhs[]); + } + + int opEquals(ref const String rhs) const pure + => opEquals(rhs[]); + + int opEquals(size_t N)(ref const MutableString!N rhs) const pure + => opEquals(rhs[]); + + int opEquals(size_t N)(ref const Array!N rhs) const pure + => opEquals(rhs[]); + + int opCmp(const(char)[] rhs) const pure + { + import urt.algorithm : compare; + if (!ptr) + return rhs.length == 0 ? 0 : -1; + return compare(ptr[0 .. length], rhs); + } + + int opCmp(ref const String rhs) const pure + => opCmp(rhs[]); + + int opCmp(size_t N)(ref const MutableString!N rhs) const pure + => opCmp(rhs[]); + + int opCmp(size_t N)(ref const Array!N rhs) const pure + => opCmp(rhs[]); + + size_t toHash() const pure + { + if (!ptr) + return 0; + ushort len = (cast(ushort*)ptr)[-1] & 0x7FFF; + static if (size_t.sizeof == 4) + return fnv1a(cast(ubyte[])ptr[0 .. len]); + else + return fnv1a64(cast(ubyte[])ptr[0 .. len]); + } + void opAssign(ref const typeof(this) rh) { opAssign(rh[]); @@ -602,11 +669,6 @@ nothrow @nogc: opAssign(rh[]); } - void opAssign(typeof(null)) - { - clear(); - } - void opAssign(char c) { reserve(1); @@ -895,6 +957,9 @@ unittest assert(s == "Hello, world!\n"); assert(s.length == 14); + s = null; + assert(s.length == 0); + MutableString!0 s_long; s_long.reserve(4567); s_long = "Start"; diff --git a/src/urt/variant.d b/src/urt/variant.d index 0d8a19e..88ba0a6 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -9,8 +9,10 @@ import urt.map; import urt.mem.allocator; import urt.si.quantity; import urt.si.unit : ScaledUnit, Second, Nanosecond; +import urt.string; import urt.time; import urt.traits; +import urt.util : swap; nothrow @nogc: @@ -22,7 +24,9 @@ enum ValidUserType(T) = (is(T == struct) || is(T == class)) && !is(T == Array!U, U) && !is(T : const(char)[]) && !is(T == Quantity!(T, U), T, alias U) && - !is(T == Duration); + !is(T == Duration) && + !is(T == String) && + !is(T == MutableString!N, size_t N); alias VariantKVP = KVP!(const(char)[], Variant); @@ -37,14 +41,28 @@ nothrow @nogc: if (rh.type == Type.Map || rh.type == Type.Array) { Array!Variant arr = rh.nodeArray; - takeNodeArray(arr); + take_node_array(arr); flags = rh.flags; } - else + else if ((rh.flags & Flags.NeedDestruction) == 0) { - assert((rh.flags & Flags.NeedDestruction) == 0); __pack = rh.__pack; } + else if (rh.isString) + { + *cast(String*)&value.s = *cast(String*)&rh.value.s; + count = rh.count; + flags = rh.flags; + } + else if (rh.isBuffer) + { + ptr = defaultAllocator.alloc(rh.count).ptr; + ptr[0 .. rh.count] = rh.ptr[0 .. rh.count]; + count = rh.count; + flags = rh.flags; + } + else + assert(false, "What kind of thing is this?"); } version (EnableMoveSemantics) { @@ -179,9 +197,33 @@ nothrow @nogc: count = Nanosecond.pack; } - this(const(char)[] s) // TODO: (S)(S s) -// if (is(S : const(char)[])) + this(String s) + { + if (s.empty) + { + flags = Flags.Null; + return; + } + flags = Flags.String; + value.s = s.ptr; + count = s.length; + if (s.has_rc()) + flags |= Flags.NeedDestruction; + s.ptr = null; + } + + this(size_t N)(MutableString!N s) { + this(String(s.move)); + } + + this(const(char)[] s) + { + if (s.length == 0) + { + flags = Flags.Null; + return; + } if (s.length < embed.length) { flags = Flags.ShortString; @@ -190,13 +232,20 @@ nothrow @nogc: return; } flags = Flags.String; - value.s = s.ptr; + flags |= Flags.NeedDestruction; + String t = s.makeString(defaultAllocator); + value.s = t.ptr.swap(null); count = cast(uint)s.length; } this(T)(T[] buffer) if (is(Unqual!T == void)) { + if (buffer.length == 0) + { + flags = Flags.Null; + return; + } if (buffer.length < embed.length) { flags = Flags.ShortBuffer; @@ -205,7 +254,10 @@ nothrow @nogc: return; } flags = Flags.Buffer; - value.s = cast(char*)buffer.ptr; + flags |= Flags.NeedDestruction; + void[] mem = defaultAllocator.alloc(buffer.length); + mem[] = buffer[]; + ptr = mem.ptr; count = cast(uint)buffer.length; } @@ -226,7 +278,7 @@ nothrow @nogc: this(Array!Variant a) { - takeNodeArray(a); + take_node_array(a); flags = Flags.Array; } @@ -386,10 +438,10 @@ nothrow @nogc: // TODO: should we also do string key comparisons? return opEquals(cast(E)rhs); } - else static if (is(T : const(char)[])) + else static if (is(T : const(char)[]) || is(T : const String) || is(T : const MutableString!N, size_t N)) return isString && asString() == rhs[]; else static if (ValidUserType!T) - return asUser!T == rhs; + return isUser!T && asUser!T == rhs; else static assert(false, "TODO: variant comparison with '", T.stringof, "' not supported"); } @@ -777,7 +829,7 @@ nothrow @nogc: assert(isBuffer); if (flags & Flags.Embedded) return embed[0 .. embed[$-1]]; - return value.s[0 .. count]; + return ptr[0 .. count]; } const(char)[] asString() const pure @@ -863,72 +915,97 @@ nothrow @nogc: } } - auto as(T)() inout pure - if (!ValidUserType!(Unqual!T) || !UserTypeReturnByRef!T) - { - static if (is_some_int!T) + template as(T) + { + static if (!is(T == Unqual!T)) + alias as = as!(Unqual!T); + else static if (is_boolean!T) + alias as = asBool; + else static if (is(T == long)) + alias as = asLong; + else static if (is(T == int)) + alias as = asInt; + else static if (is(T == ulong)) + alias as = asUlong; + else static if (is(T == uint)) + alias as = asUint; + else static if (is_some_int!T) { - static if (is_signed_int!T) + T as() const pure { - static if (is(T == long)) - return asLong(); - else + static if (is_signed_int!T) { int i = asInt(); - static if (!is(T == int)) - assert(i >= T.min && i <= T.max, "Value out of range for " ~ T.stringof); - return cast(T)i; + assert(i >= T.min && i <= T.max, "Value out of range for " ~ T.stringof); } - } - else - { - static if (is(T == ulong)) - return asUlong(); else { - uint u = asUint(); - static if (!is(T == uint)) - assert(u <= T.max, "Value out of range for " ~ T.stringof); - return cast(T)u; + uint i = asUint(); + assert(i <= T.max, "Value out of range for " ~ T.stringof); } + return cast(T)i; } } - else static if (is_some_float!T) - { - static if (is(T == float)) - return asFloat(); - else - return asDouble(); - } + else static if (is(T == float)) + alias as = asFloat; + else static if (is(T == double)) + alias as = asDouble; + else static if (is(T == real)) + real as() const pure => asDouble; else static if (is(T == Quantity!(U, _U), U, ScaledUnit _U)) - { - return asQuantity!U(); - } + alias as = asQuantity!U; else static if (is(T == Duration)) + alias as = asDuration; + else static if (is(const(char)[] : T)) + alias as = asString; + else static if (is(T == String)) { - return asDuration; - } - else static if (is(T : const(char)[])) - { - static if (is(T == struct)) // for String/MutableString/etc - return T(asString); // TODO: error? shouldn't this NRVO?! - else - return asString; + String as() const pure + { + if (isNull) + return String(); + assert(isString); + if (flags & Flags.Embedded) + return embed[0 .. embed[$-1]].makeString(defaultAllocator); + return *cast(String*)&value.s; + } } else static if (ValidUserType!(Unqual!T)) - return asUser!T; + alias as = asUser!T; else static assert(false, "TODO!"); } - ref inout(T) as(T)() inout pure - if (ValidUserType!(Unqual!T) && UserTypeReturnByRef!T) - => asUser!T; + + Array!Variant take_array() + { + if (flags == Flags.Null) + return Array!Variant(); + assert(isArray()); + Array!Variant r; + swap(r, nodeArray); + flags = Flags.Null; + return r; + } + + String take_string() + { + if (flags == Flags.Null) + return String(); + assert(isString); + if (flags & Flags.Embedded) + return embed[0 .. embed[$-1]].makeString(defaultAllocator); + String s; + s.ptr = value.s; + value.s = null; + flags = Flags.Null; + return s; + } size_t length() const pure { if (flags == Flags.Null) return 0; - else if (isString()) + else if (isBuffer()) return (flags & Flags.Embedded) ? embed[$-1] : count; else if (isArray()) return count; @@ -1212,7 +1289,7 @@ package: return alloc; // short id return count; // long id } - inout(void)* userPtr() inout pure + inout(void)* user_ptr() inout pure { if (flags & Flags.Embedded) return embed.ptr; @@ -1221,7 +1298,7 @@ package: ref inout(Array!Variant) nodeArray() @property inout pure => *cast(inout(Array!Variant)*)&value.n; - void takeNodeArray(ref Array!Variant arr) + void take_node_array(ref Array!Variant arr) { value.n = arr[].ptr; count = cast(uint)arr.length; @@ -1231,22 +1308,29 @@ package: void destroy(bool reset = true)() { if (flags & Flags.NeedDestruction) - doDestroy(); + do_destroy(); static if (reset) __pack[] = 0; } - private void doDestroy() + private void do_destroy() { Type t = type(); - if ((t == Type.Map || t == Type.Array) && value.n) + if (isBuffer) + { + if (isString) + *cast(String*)&value.s = null; + else + defaultAllocator().free(ptr[0..count]); + } + else if ((t == Type.Map || t == Type.Array) && value.n) nodeArray.destroy!false(); else if (t == Type.User) { ref const TypeDetails td = (flags & Flags.Embedded) ? find_type_details(alloc) : g_type_details[alloc]; if (td.destroy) - td.destroy(userPtr); + td.destroy(user_ptr); if (!(flags & Flags.Embedded)) defaultAllocator().free(ptr[0..td.size]); } From 2626f4d476bf9cbb14e29a188fc20c6f6caf70a3 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 20 Feb 2026 10:13:27 +1000 Subject: [PATCH 082/138] Fix parse_uint not rejecting digits outside the base for base < 10. --- src/urt/conv.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/urt/conv.d b/src/urt/conv.d index 3d600ba..320d57e 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -65,7 +65,7 @@ ulong parse_uint(const(char)[] str, size_t* bytes_taken = null, uint base = 10) for (; s < e; ++s) { uint digit = *s - '0'; - if (digit > 9) + if (digit >= base) break; value = value*base + digit; } From 4711b3cc2944066d96ecc0f23ba1ac0ef9ec237f Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 20 Feb 2026 10:18:04 +1000 Subject: [PATCH 083/138] Accept a single space before the timezone when parsing a DateTime. --- src/urt/time.d | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/urt/time.d b/src/urt/time.d index 7cca87f..e970b75 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -621,10 +621,13 @@ pure nothrow @nogc: return offset + 1; } - if (s[offset] != '-' && s[offset] != '+') + size_t tz_offset = offset; + if (s[offset] == ' ') + ++tz_offset; + if (s[tz_offset] != '-' && s[tz_offset] != '+') return offset; - - size_t tz_offset = offset + 1; + bool tz_neg = s[tz_offset] == '-'; + tz_offset += 1; // parse timezone (00:00) int tz_hr, tz_min; @@ -664,7 +667,7 @@ pure nothrow @nogc: } } - if (s[offset] == '-') + if (tz_neg) tz_hr = -tz_hr; // assert(false, "TODO: we need to know our local timezone..."); From 1ecf69a1f07d202e2574c4d9080032923172f3fc Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 20 Feb 2026 10:17:36 +1000 Subject: [PATCH 084/138] Windows use WSASendTo because WSASendMsg is actually an extension, and not guaranteed to be available O_o --- src/urt/socket.d | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/urt/socket.d b/src/urt/socket.d index f87ec91..b1f4645 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -334,10 +334,50 @@ Result send(Socket socket, MsgFlags flags, size_t* bytes_sent, const void[][] bu } Result sendto(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.none, const InetAddress* address = null, size_t* bytes_sent = null) - => sendmsg(socket, address, flags, null, bytes_sent, (&message)[0..1]); +{ + version (Windows) + return sendto(socket, address, bytes_sent, (&message)[0..1]); + else + return sendmsg(socket, address, flags, null, bytes_sent, (&message)[0..1]); +} Result sendto(Socket socket, const InetAddress* address, size_t* bytes_sent, const void[][] buffers...) - => sendmsg(socket, address, MsgFlags.none, null, bytes_sent, buffers); +{ + version (Windows) + { + ubyte[sockaddr_storage.sizeof] tmp = void; + size_t addr_len; + sockaddr* sock_addr = null; + if (address) + { + sock_addr = make_sockaddr(*address, tmp, addr_len); + assert(sock_addr, "Invalid socket address"); + } + + uint sent = void; + WSABUF[32] bufs = void; + assert(buffers.length <= bufs.length, "Too many buffers!"); + + uint n = 0; + foreach(buffer; buffers) + { + if (buffer.length == 0) + continue; + assert(buffer.length <= uint.max, "Buffer too large!"); + bufs[n].buf = cast(char*)buffer.ptr; + bufs[n++].len = cast(uint)buffer.length; + } + + int r = WSASendTo(socket.handle, bufs.ptr, n, &sent, /+map_message_flags(flags)+/ 0, sock_addr, cast(int)addr_len, null, null); // there are no meaningful flags on Windows + if (r == SOCKET_ERROR) + return socket_getlasterror(); + if (bytes_sent) + *bytes_sent = sent; + return Result.success; + } + else + return sendmsg(socket, address, MsgFlags.none, null, bytes_sent, buffers); +} Result sendmsg(Socket socket, const InetAddress* address, MsgFlags flags, const(void)[] control, size_t* bytes_sent, const void[][] buffers) { From 5635505bb47e6bee40d2901059ced6057b87055d Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 18 Feb 2026 16:46:15 +1000 Subject: [PATCH 085/138] Renovated concat() a bit --- src/urt/array.d | 7 +- src/urt/string/format.d | 184 ++++++++++++++++++++++------------------ 2 files changed, 107 insertions(+), 84 deletions(-) diff --git a/src/urt/array.d b/src/urt/array.d index 1159e0b..51360f6 100644 --- a/src/urt/array.d +++ b/src/urt/array.d @@ -213,6 +213,10 @@ struct Array(T, size_t EmbedCount = 0) { static assert(EmbedCount == 0, "Not without move semantics!"); + alias This = typeof(this); + + T* ptr; + // constructors // TODO: DELETE POSTBLIT! @@ -225,7 +229,7 @@ struct Array(T, size_t EmbedCount = 0) this = t[]; } - this(ref typeof(this) val) + this(ref This val) { this(val[]); } @@ -796,7 +800,6 @@ nothrow @nogc: private: enum copy_elements = is(T == class) || is(T == interface) || is_primitive!T || is_trivial!T; - T* ptr; uint _length; static if (EmbedCount > 0) diff --git a/src/urt/string/format.d b/src/urt/string/format.d index c34917d..8747c4c 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -18,7 +18,7 @@ debug alias StringifyFunc = ptrdiff_t delegate(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) nothrow @nogc; -ptrdiff_t toString(T)(auto ref T value, char[] buffer, const(char)[] format = null, const(FormatArg)[] formatArgs = null) +ptrdiff_t toString(T)(ref const T value, char[] buffer, const(char)[] format = null, const(FormatArg)[] formatArgs = null) { debug InFormatFunction = true; ptrdiff_t r = get_to_string_func(value)(buffer, null, null); @@ -28,65 +28,83 @@ ptrdiff_t toString(T)(auto ref T value, char[] buffer, const(char)[] format = nu alias formatValue = toString; // TODO: remove me? -char[] concat(Args...)(char[] buffer, auto ref Args args) +char[] concat(Args...)(char[] buffer, ref const Args args) { + alias NormalisedArgs = NormaliseArgs!Args; + static if (Args.length == 1) + enum is_normalised = is(Args[0] == NormalisedArgs); + else + enum is_normalised = is(Args == NormalisedArgs); + static if (Args.length == 0) { return buffer.ptr[0 .. 0]; } - else static if ((Args.length == 1 && allAreStrings!Args) || allConstCorrectStrings!Args) + else static if (!is_normalised) { - // this implementation handles pure string concatenations - if (!buffer.ptr) + pragma(inline, true); + return concat!(NormalisedArgs)(buffer, args); + } + else + { + enum n = num_string_args!Args; + + static if (n == Args.length) { - size_t length = 0; - static foreach (i, s; args) + size_t[Args.length + 1] offsets = void; + size_t[Args.length] lens = void; + offsets[0] = 0; + static foreach (i; 0 .. Args.length) { - static if (is(typeof(s) : char)) - length += 1; + static if (is(Args[i] == char)) + offsets[i + 1] = offsets[i] + 1; else - length += s.length; - } - return (cast(char*)null)[0 .. length]; - } - size_t offset = 0; - static foreach (i, s; args) - { - static if (is(typeof(s) : char)) - { - if (buffer.length < offset + 1) - return null; - buffer.ptr[offset++] = s; + { + lens[i] = args[i].length; + offsets[i + 1] = offsets[i] + lens[i]; + } } - else + if (!buffer.ptr) + return buffer.ptr[0 .. offsets[Args.length]]; + if (offsets[Args.length] > buffer.length) + return null; + static foreach (i; 0 .. Args.length) { - if (buffer.length < offset + s.length) - return null; - buffer.ptr[offset .. offset + s.length] = s.ptr[0 .. s.length]; - offset += s.length; + static if (is(Args[i] == char)) + buffer.ptr[offsets[i]] = args[i]; + else + buffer.ptr[offsets[i] .. offsets[i + 1]] = args[i].ptr[0..lens[i]]; } + return buffer.ptr[0 .. offsets[Args.length]]; + } + else static if (Args.length == 1) + { + ptrdiff_t r = get_to_string_func(args[0])(buffer, null, null); + if (r < 0) + return null; + return buffer.ptr[0..r]; + } + else static if (n > 2) + { + char[] r = concat(buffer, args[0 .. n]); + if (buffer.ptr && !r.ptr) + return null; + char[] r2 = concat(buffer.ptr ? buffer.ptr[r.length .. buffer.length] : null, args[n .. $]); + if (buffer.ptr && !r2.ptr) + return null; + return buffer.ptr[0 .. r.length + r2.length]; + } + else + { + // this implementation handles all the other kinds of things! + debug if (!__ctfe) InFormatFunction = true; + StringifyFunc[Args.length] arg_funcs = void; + static foreach(i; 0 .. args.length) + arg_funcs[i] = get_to_string_func(args[i]); + char[] r = concat_impl(buffer, arg_funcs); + debug if (!__ctfe) InFormatFunction = false; + return r; } - return buffer.ptr[0 .. offset]; - } - else static if (allAreStrings!Args) - { - // TODO: why can't inline this?! -// pragma(inline, true); - - // avoid duplicate instantiations with different attributes... - return concat!(constCorrectedStrings!Args)(buffer, args); - } - else - { - // this implementation handles all the other kinds of things! - - debug if (!__ctfe) InFormatFunction = true; - StringifyFunc[Args.length] arg_funcs = void; - static foreach(i, arg; args) - arg_funcs[i] = get_to_string_func(arg); - char[] r = concatImpl(buffer, arg_funcs); - debug if (!__ctfe) InFormatFunction = false; - return r; } } @@ -135,6 +153,42 @@ private: private: +import urt.array; +enum is_some_string(T) = is_some_char!T || is(T : const char[]) || is(T : const(String)) || is(T : const(MutableString!N), size_t N) || is(T : const(Array!(char, N)), size_t N); + +template num_string_args(Args...) +{ + static if (Args.length == 0) + enum num_string_args = 0; + else static if (is_some_string!(Args[0])) + enum num_string_args = 1 + num_string_args!(Args[1 .. $]); + else + enum num_string_args = 0; +} + +template NormaliseArgs(Args...) +{ + import urt.meta : AliasSeq; + static if (Args.length == 0) + alias NormaliseArgs = AliasSeq!(); + else static if (Args.length == 1) + alias NormaliseArgs = NormaliseConst!(Args[0]); + else + alias NormaliseArgs = AliasSeq!(NormaliseConst!(Args[0]), NormaliseArgs!(Args[1 .. $])); +} + +template NormaliseConst(T) +{ + static if (is(T == const(U), U) || is(T == immutable(U), U)) + alias NormaliseConst = NormaliseConst!U; + else static if (is(T == U[], U)) + alias NormaliseConst = const(U)[]; + else static if (is(T == U[N], U, size_t N)) + alias NormaliseConst = const(U)[N]; + else + alias NormaliseConst = T; +} + alias StringifyFuncReduced = ptrdiff_t delegate(char[] buffer, const(char)[] format) nothrow @nogc; alias StringifyFuncReduced2 = ptrdiff_t delegate(char[] buffer) nothrow @nogc; @@ -712,7 +766,7 @@ template DefFormat(T) } } -char[] concatImpl(char[] buffer, const(StringifyFunc)[] args) nothrow @nogc +char[] concat_impl(char[] buffer, const(StringifyFunc)[] args) nothrow @nogc { size_t len = 0; foreach (a; args) @@ -985,40 +1039,6 @@ unittest } - - -// a template that tests is all template args are a char array, or a char -template allAreStrings(Args...) -{ - static if (Args.length == 1) - enum allAreStrings = is(Args[0] : const(char[])) || is(is_some_char!(Args[0])); - else - enum allAreStrings = (is(Args[0] : const(char[])) || is(is_some_char!(Args[0]))) && allAreStrings!(Args[1 .. $]); -} - -template allConstCorrectStrings(Args...) -{ - static if (Args.length == 1) - enum allConstCorrectStrings = is(Args[0] == const(char[])) || is(Args[0] == const char); - else - enum allConstCorrectStrings = (is(Args[0] == const(char[])) || is(Args[0] == const char)) && allConstCorrectStrings!(Args[1 .. $]); -} - -template constCorrectedStrings(Args...) -{ - import urt.meta : AliasSeq; - alias constCorrectedStrings = AliasSeq!(); - static foreach (Ty; Args) - { - static if (is(Ty : const(char)[])) - constCorrectedStrings = AliasSeq!(constCorrectedStrings, const(char[])); - else static if (is_some_char!Ty) - constCorrectedStrings = AliasSeq!(constCorrectedStrings, const(char)); - else - static assert(false, "Argument must be a char array or a char: ", T); - } -} - template EnumKeyIsDuplicate(Enum, size_t item) { alias Elements = __traits(allMembers, Enum); From fc3d8fc18a18c869a90e295ba3367b9db279cc42 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 23 Feb 2026 03:10:14 +1000 Subject: [PATCH 086/138] - Quantity support in json - Better Variant const support --- src/urt/format/json.d | 46 +++++++++++++++++++++++++++++++++++-------- src/urt/si/unit.d | 14 ++++++++++++- src/urt/variant.d | 46 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 9 deletions(-) diff --git a/src/urt/format/json.d b/src/urt/format/json.d index 5866a3c..f7455f0 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -5,6 +5,7 @@ import urt.conv; import urt.lifetime; import urt.kvp; import urt.mem.allocator; +import urt.si.unit; import urt.string; import urt.string.format; @@ -197,18 +198,47 @@ ptrdiff_t write_json(ref const Variant val, char[] buffer, bool dense = false, u case Variant.Type.Number: import urt.conv; - if (val.isQuantity()) - assert(false, "TODO: implement quantity formatting for JSON"); + char[] number_buffer = buffer; + bool is_q = val.isQuantity(); + size_t u_len = 0; + float pre_scale = void; + if (is_q) + { + if (buffer.ptr) + { + if (buffer.length < 15) // {"q":x,"u":"x"} + return -1; + number_buffer = buffer[5..$]; + } + u_len = val.get_unit.format_unit(null, pre_scale, true); // TODO: should we give false (force pre-scale) here? + if (u_len <= 0) + return u_len; + // TODO: apply the prescale... (if we change the above bool to false) + } + + size_t len = 0; if (val.isDouble()) - return val.asDouble().format_float(buffer); + len += val.asDouble().format_float(number_buffer); + else if (val.isUlong()) + len += val.asUlong().format_uint(number_buffer); + else + len += val.asLong().format_int(number_buffer); + if (len <= 0 || !is_q) + return len; - // TODO: parse args? - //format + size_t result_len = 5 + len + 6 + u_len + 2; // {"q":x,"u":"x"} + if (!buffer.ptr) + return result_len; + if (buffer.length < result_len) + return -1; - if (val.isUlong()) - return val.asUlong().format_uint(buffer); - return val.asLong().format_int(buffer); + size_t offset = 5 + len; + buffer[0..5] = "{\"q\":"; + buffer[offset..offset + 6] = ",\"u\":\""; + val.get_unit.format_unit(buffer[offset + 6..$], pre_scale, true); // TODO: should we give false (force pre-scale) here? + buffer[result_len-2..result_len] = "\"}"; + return result_len; case Variant.Type.User: // for custom types, we'll use the type's regular string format into a json string diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index 7bc059b..a183d98 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -689,8 +689,11 @@ nothrow: } import urt.string.format : FormatArg; - ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const pure + ptrdiff_t format_unit(char[] buffer, out float pre_scale, bool allow_unit_scale = true) const pure { + assert(allow_unit_scale == true, "TODO: support for no-scale formatting (require pre-scale)"); + pre_scale = 1; + if (!unit.pack) { if (siScale && exp == -2) @@ -802,6 +805,15 @@ nothrow: return len; } + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const pure + { + float pre_scale; + ptrdiff_t r = format_unit(buffer, pre_scale, true); + if (pre_scale != 1) + return -1; + return r; + } + ptrdiff_t fromString(const(char)[] s) pure { float scale; diff --git a/src/urt/variant.d b/src/urt/variant.d index 0270c7c..95ce4af 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -66,6 +66,37 @@ nothrow @nogc: assert(false, "What kind of thing is this?"); } + this(ref const Variant rh) inout + { + if (rh.type == Type.Map || rh.type == Type.Array) + { + auto arr = Array!Variant(Reserve, rh.nodeArray.length); + foreach (ref i; rh.nodeArray) + arr.pushBack(i); + flags = rh.flags; + } + else if ((rh.flags & Flags.NeedDestruction) == 0) + { + __pack = rh.__pack; + } + else if (rh.isString) + { + *cast(String*)&value.s = *cast(String*)&rh.value.s; + count = rh.count; + flags = rh.flags; + } + else if (rh.isBuffer) + { + void* mem = defaultAllocator.alloc(rh.count).ptr; + mem[0 .. rh.count] = rh.ptr[0 .. rh.count]; + ptr = cast(inout(void)*)mem; + count = rh.count; + flags = rh.flags; + } + else + assert(false, "What kind of thing is this?"); + } + version (EnableMoveSemantics) { this(Variant rh) { @@ -368,6 +399,15 @@ nothrow @nogc: destroy!false(); new(this) Variant(value); } + + void opAssign(ref const Variant value) + { + if (&this is &value) + return; // TODO: should this be an assert instead of a graceful handler? + destroy!false(); + new(this) Variant(value); + } + version (EnableMoveSemantics) { void opAssign(Variant value) { @@ -995,6 +1035,12 @@ nothrow @nogc: return s; } + ScaledUnit get_unit() const pure + { + assert(isQuantity()); + return *cast(ScaledUnit*)&count; + } + const(VoidEnumInfo)* get_enum_info() const pure { assert(is_enum); From c15e6a903ded287bcf272df18bcec8b79bf3c6b3 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 23 Feb 2026 12:33:33 +1000 Subject: [PATCH 087/138] Add Endian enum, which feels nicer than checking bools in various situations. --- src/urt/processor.d | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/urt/processor.d b/src/urt/processor.d index 4acc356..f35792d 100644 --- a/src/urt/processor.d +++ b/src/urt/processor.d @@ -1,9 +1,24 @@ module urt.processor; +enum Endian : byte +{ + Native = -1, // specifies the native/working endian + Little = 0, + Big = 1 +} + version (LittleEndian) +{ enum LittleEndian = true; + enum BigEndian = false; + enum Endian proc_endian = Endian.Little; +} else +{ enum LittleEndian = false; + enum BigEndian = true; + enum Endian proc_endian = Endian.Big; +} version (X86_64) { From 5b72aec26a815db3b466304c383a74a62c31d17f Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 24 Feb 2026 02:49:58 +1000 Subject: [PATCH 088/138] Overhaul the log API! --- src/urt/file.d | 2 +- src/urt/log.d | 247 ++++++++++++++++++++++++++++++++++++++++----- src/urt/mem/temp.d | 58 +++++++---- 3 files changed, 263 insertions(+), 44 deletions(-) diff --git a/src/urt/file.d b/src/urt/file.d index 37dfad0..1000bcf 100644 --- a/src/urt/file.d +++ b/src/urt/file.d @@ -785,7 +785,7 @@ Result get_temp_filename(ref char[] buffer, const(char)[] dstDir, const(char)[] else version (Posix) { // Construct a format string which will be the supplied dir with prefix and 8 generated random characters - char[] fn = tconcat(dstDir, (dstDir.length && dstDir[$-1] != '/') ? "/" : "", prefix, "XXXXXX\0"); + char[] fn = cast(char[])tconcat(dstDir, (dstDir.length && dstDir[$-1] != '/') ? "/" : "", prefix, "XXXXXX\0"); File file; file.fd = mkstemp(fn.ptr); if (file.fd == -1) diff --git a/src/urt/log.d b/src/urt/log.d index 9a08f0a..66c6a38 100644 --- a/src/urt/log.d +++ b/src/urt/log.d @@ -1,10 +1,187 @@ module urt.log; -import urt.mem.temp; +import urt.mem.temp : tconcat, tformat; +import urt.time; nothrow @nogc: +enum Severity : ubyte +{ + emergency = 0, + alert = 1, + critical = 2, + error = 3, + warning = 4, + notice = 5, + info = 6, + debug_ = 7, + trace = 8, +} + +immutable string[9] severity_names = [ + "Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Info", "Debug", "Trace" +]; + +struct LogMessage +{ + Severity severity; + const(char)[] tag; + const(char)[] object_name; + const(char)[] message; + MonoTime timestamp; +} + +struct LogFilter +{ + Severity max_severity = Severity.info; + const(char)[] tag_prefix; +} + +alias SinkOutputFn = void function(void* context, scope ref const LogMessage msg) nothrow @nogc; + +struct LogSinkHandle +{ + int index = -1; + bool valid() const pure nothrow @nogc + => index >= 0 && index < max_sinks; +} + +LogSinkHandle register_log_sink(SinkOutputFn output, void* context = null, LogFilter filter = LogFilter.init) +{ + foreach (i, ref sink; g_sinks) + { + if (!sink.active) + { + sink.output = output; + sink.context = context; + sink.filter = filter; + sink.enabled = true; + sink.active = true; + recalc_max_severity(); + return LogSinkHandle(cast(int)i); + } + } + return LogSinkHandle(-1); +} + +void unregister_log_sink(LogSinkHandle handle) +{ + if (!handle.valid) + return; + g_sinks[handle.index] = SinkSlot.init; + recalc_max_severity(); +} + +void set_sink_filter(LogSinkHandle handle, LogFilter filter) +{ + if (!handle.valid) + return; + g_sinks[handle.index].filter = filter; + recalc_max_severity(); +} + +void set_sink_enabled(LogSinkHandle handle, bool enabled) +{ + if (!handle.valid) + return; + g_sinks[handle.index].enabled = enabled; + recalc_max_severity(); +} + +void log_emergency(T...)(const(char)[] tag, ref T args) { write_log(Severity.emergency, tag, null, args); } +void log_alert(T...)(const(char)[] tag, ref T args) { write_log(Severity.alert, tag, null, args); } +void log_critical(T...)(const(char)[] tag, ref T args) { write_log(Severity.critical, tag, null, args); } +void log_error(T...)(const(char)[] tag, ref T args) { write_log(Severity.error, tag, null, args); } +void log_warning(T...)(const(char)[] tag, ref T args) { write_log(Severity.warning, tag, null, args); } +void log_notice(T...)(const(char)[] tag, ref T args) { write_log(Severity.notice, tag, null, args); } +void log_info(T...)(const(char)[] tag, ref T args) { write_log(Severity.info, tag, null, args); } + +void log_emergencyf(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.emergency, tag, null, fmt, args); } +void log_alertf(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.alert, tag, null, fmt, args); } +void log_criticalf(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.critical, tag, null, fmt, args); } +void log_errorf(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.error, tag, null, fmt, args); } +void log_warningf(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.warning, tag, null, fmt, args); } +void log_noticef(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.notice, tag, null, fmt, args); } +void log_infof(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.info, tag, null, fmt, args); } + +debug +{ + void log_debug(T...)(const(char)[] tag, ref T args) { write_log(Severity.debug_, tag, null, args); } + void log_trace(T...)(const(char)[] tag, ref T args) { write_log(Severity.trace, tag, null, args); } + void log_debugf(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.debug_, tag, null, fmt, args); } + void log_tracef(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.trace, tag, null, fmt, args); } +} + +// this can be declared in any scope to automatically prefix log messages with a tag (e.g. module name) +// eg: alias log = Log!"my.module"; +// log.warn("oh no!"); +template Log(string tag) +{ + void info(T...)(ref T args) { write_log(Severity.info, tag, null, args); } + void warning(T...)(ref T args) { write_log(Severity.warning, tag, null, args); } + void error(T...)(ref T args) { write_log(Severity.error, tag, null, args); } + void notice(T...)(ref T args) { write_log(Severity.notice, tag, null, args); } + void critical(T...)(ref T args) { write_log(Severity.critical, tag, null, args); } + void alert(T...)(ref T args) { write_log(Severity.alert, tag, null, args); } + void emergency(T...)(ref T args) { write_log(Severity.emergency, tag, null, args); } + + void infof(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.info, tag, null, fmt, args); } + void warningf(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.warning, tag, null, fmt, args); } + void errorf(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.error, tag, null, fmt, args); } + void noticef(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.notice, tag, null, fmt, args); } + void criticalf(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.critical, tag, null, fmt, args); } + void alertf(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.alert, tag, null, fmt, args); } + void emergencyf(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.emergency, tag, null, fmt, args); } + + debug + { + void debug_(T...)(ref T args) { write_log(Severity.debug_, tag, null, args); } + void trace(T...)(ref T args) { write_log(Severity.trace, tag, null, args); } + void debugf(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.debug_, tag, null, fmt, args); } + void tracef(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.trace, tag, null, fmt, args); } + } +} + +void write_log(T...)(Severity severity, const(char)[] tag, const(char)[] object_name, ref T args) +{ + if (severity > g_max_severity) + return; + import urt.string, urt.array; + static if (T.length == 1 && (is(T[0] : const(char)[]) || is(T[0] : const String) || is(T[0] : const MutableString!N, size_t N) || is(T[0] : const Array!char))) + auto msg = LogMessage(severity, tag, object_name, args[0][], getTime()); + else + auto msg = LogMessage(severity, tag, object_name, tconcat(args), getTime()); + write_log(msg); +} + +void write_logf(T...)(Severity severity, const(char)[] tag, const(char)[] object_name, const(char)[] fmt, ref T args) +{ + if (severity > g_max_severity) + return; + auto msg = LogMessage(severity, tag, object_name, tformat(fmt, args), getTime()); + write_log(msg); +} + +void write_log(scope ref const LogMessage msg) +{ + import urt.string : startsWith; + + foreach (ref sink; g_sinks) + { + if (!sink.active || !sink.enabled) + continue; + if (msg.severity > sink.filter.max_severity) + continue; + if (sink.filter.tag_prefix.length > 0 && !msg.tag.startsWith(sink.filter.tag_prefix)) + continue; + sink.output(sink.context, msg); + } +} + + +// --- backward compatibility (deprecated) --- + enum Level : ubyte { Error = 0, @@ -13,21 +190,21 @@ enum Level : ubyte Debug } -immutable string[] levelNames = [ "Error", "Warning", "Info", "Debug" ]; +immutable string[] levelNames = ["Error", "Warning", "Info", "Debug"]; -alias LogSink = void function(Level level, const(char)[] message) nothrow @nogc; - -__gshared Level logLevel = Level.Info; - -void register_log_sink(LogSink sink) nothrow @nogc +Severity level_to_severity(Level level) { - if (g_log_sink_count < g_log_sinks.length) + final switch (level) { - g_log_sinks[g_log_sink_count] = sink; - g_log_sink_count++; + case Level.Error: return Severity.error; + case Level.Warning: return Severity.warning; + case Level.Info: return Severity.info; + case Level.Debug: return Severity.debug_; } } +__gshared Level logLevel = Level.Info; + void writeDebug(T...)(ref T things) { writeLog(Level.Debug, things); } void writeInfo(T...)(ref T things) { writeLog(Level.Info, things); } void writeWarning(T...)(ref T things) { writeLog(Level.Warning, things); } @@ -40,28 +217,52 @@ void writeErrorf(T...)(const(char)[] format, ref T things) { writeLogf(Level.Err void writeLog(T...)(Level level, ref T things) { - if (level > logLevel) + Severity sev = level_to_severity(level); + if (sev > g_max_severity) return; - - const(char)[] message = tconcat(levelNames[level], ": ", things); - for (size_t i = 0; i < g_log_sink_count; i++) - g_log_sinks[i](level, message); + write_log(sev, null, null, things); } void writeLogf(T...)(Level level, const(char)[] format, ref T things) { - if (level > logLevel) - return; + write_logf(level_to_severity(level), null, null, format, things); +} - const(char)[] message = tformat("{-2}: {@-1}", things, levelNames[level], format); +alias LegacyLogSink = void function(Level level, scope const(char)[] message) nothrow @nogc; - for (size_t i = 0; i < g_log_sink_count; i++) - g_log_sinks[i](level, message); +private void legacy_sink_adapter(void* context, scope ref const LogMessage msg) nothrow @nogc +{ + __gshared immutable Level[9] map = [Level.Error, Level.Error, Level.Error, Level.Error, Level.Warning, Level.Info, Level.Info, Level.Debug, Level.Debug]; + (cast(LegacyLogSink)context)(map[msg.severity], msg.message); } +LogSinkHandle register_log_sink(LegacyLogSink sink) + => register_log_sink(&legacy_sink_adapter, cast(void*)sink); + private: -// HACK: temp until we have a proper registration process... -__gshared LogSink[8] g_log_sinks; -__gshared size_t g_log_sink_count = 0; +enum max_sinks = 16; + +struct SinkSlot +{ + SinkOutputFn output; + void* context; + LogFilter filter; + bool enabled; + bool active; +} + +__gshared SinkSlot[max_sinks] g_sinks; +__gshared Severity g_max_severity = Severity.info; + +void recalc_max_severity() +{ + Severity max_sev = Severity.emergency; + foreach (ref sink; g_sinks) + { + if (sink.active && sink.enabled && sink.filter.max_severity > max_sev) + max_sev = sink.filter.max_severity; + } + g_max_severity = max_sev; +} diff --git a/src/urt/mem/temp.d b/src/urt/mem/temp.d index f470dff..5f4eb08 100644 --- a/src/urt/mem/temp.d +++ b/src/urt/mem/temp.d @@ -99,26 +99,35 @@ wchar* twstringz(const(wchar)[] str) nothrow @nogc return r; } -char[] tstring(T)(auto ref T value) +const(char)[] tstring(T)(auto ref T value) { - import urt.string.format : toString; - ptrdiff_t r = toString(value, cast(char[])tempMem[alloc_offset..$]); - if (r < 0) + import urt.string, urt.array; + static if (is(T : const(char)[]) || is(T : const String) || is(T : const MutableString!N, size_t N) || is(T : const Array!char)) { - alloc_offset = 0; - r = toString(value, cast(char[])tempMem[0..TempMemSize / 2]); + pragma(inline, true); + return value[]; + } + else + { + import urt.string.format : toString; + ptrdiff_t r = toString(value, cast(char[])tempMem[alloc_offset..$]); if (r < 0) { -// assert(false, "Formatted string is too large for the temp buffer!"); - return null; + alloc_offset = 0; + r = toString(value, cast(char[])tempMem[0..TempMemSize / 2]); + if (r < 0) + { +// assert(false, "Formatted string is too large for the temp buffer!"); + return null; + } } + const(char)[] result = cast(char[])tempMem[alloc_offset .. alloc_offset + r]; + alloc_offset += r; + return result; } - char[] result = cast(char[])tempMem[alloc_offset .. alloc_offset + r]; - alloc_offset += r; - return result; } -dchar[] tdstring(T)(auto ref T value) nothrow @nogc +const(dchar)[] tdstring(T)(auto ref T value) nothrow @nogc { static if (is(T : const(char)[]) || is(T : const(wchar)[]) || is(T : const(dchar)[])) alias s = value; @@ -130,17 +139,26 @@ dchar[] tdstring(T)(auto ref T value) nothrow @nogc return r[0 .. len]; } -char[] tconcat(Args...)(ref Args args) +const(char)[] tconcat(Args...)(ref Args args) { - import urt.string.format : concat; - char[] r = concat(cast(char[])tempMem[alloc_offset..$], args); - if (!r) + import urt.string, urt.array; + static if (Args.length == 1 && (is(Args[0] : const(char)[]) || is(Args[0] : const String) || is(Args[0] : const MutableString!N, size_t N) || is(Args[0] : const Array!char))) { - alloc_offset = 0; - r = concat(cast(char[])tempMem[0..TempMemSize / 2], args); + pragma(inline, true); + return args[0][]; + } + else + { + import urt.string.format : concat; + const(char)[] r = concat(cast(char[])tempMem[alloc_offset..$], args); + if (!r) + { + alloc_offset = 0; + r = concat(cast(char[])tempMem[0..TempMemSize / 2], args); + } + alloc_offset += r.length; + return r; } - alloc_offset += r.length; - return r; } char[] tformat(Args...)(const(char)[] fmt, ref Args args) From 2271e0211a921c2327ebc4afaf75d35948733f45 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 24 Feb 2026 14:11:39 +1000 Subject: [PATCH 089/138] Fix a bug normalising the argument types in concat. --- src/urt/string/format.d | 50 ++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/urt/string/format.d b/src/urt/string/format.d index 8747c4c..f188cb3 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -31,10 +31,7 @@ alias formatValue = toString; // TODO: remove me? char[] concat(Args...)(char[] buffer, ref const Args args) { alias NormalisedArgs = NormaliseArgs!Args; - static if (Args.length == 1) - enum is_normalised = is(Args[0] == NormalisedArgs); - else - enum is_normalised = is(Args == NormalisedArgs); + enum is_normalised = is(Args == NormalisedArgs); static if (Args.length == 0) { @@ -51,31 +48,35 @@ char[] concat(Args...)(char[] buffer, ref const Args args) static if (n == Args.length) { - size_t[Args.length + 1] offsets = void; size_t[Args.length] lens = void; - offsets[0] = 0; + size_t length = 0; static foreach (i; 0 .. Args.length) { static if (is(Args[i] == char)) - offsets[i + 1] = offsets[i] + 1; + length += 1; else { lens[i] = args[i].length; - offsets[i + 1] = offsets[i] + lens[i]; + length += lens[i]; } } - if (!buffer.ptr) - return buffer.ptr[0 .. offsets[Args.length]]; - if (offsets[Args.length] > buffer.length) - return null; - static foreach (i; 0 .. Args.length) + if (buffer.ptr) { - static if (is(Args[i] == char)) - buffer.ptr[offsets[i]] = args[i]; - else - buffer.ptr[offsets[i] .. offsets[i + 1]] = args[i].ptr[0..lens[i]]; + if (length > buffer.length) + return null; + char* p = buffer.ptr; + static foreach (i; 0 .. Args.length) + { + static if (is(Args[i] == char)) + *p++ = args[i]; + else + { + p[0..lens[i]] = args[i].ptr[0..lens[i]]; + p += lens[i]; + } + } } - return buffer.ptr[0 .. offsets[Args.length]]; + return buffer.ptr[0 .. length]; } else static if (Args.length == 1) { @@ -154,7 +155,7 @@ private: private: import urt.array; -enum is_some_string(T) = is_some_char!T || is(T : const char[]) || is(T : const(String)) || is(T : const(MutableString!N), size_t N) || is(T : const(Array!(char, N)), size_t N); +enum is_some_string(T) = is(T == char) || is(T : const char[]) || is(T : const(String)) || is(T : const(MutableString!N), size_t N) || is(T : const(Array!(char, N)), size_t N); template num_string_args(Args...) { @@ -169,19 +170,16 @@ template num_string_args(Args...) template NormaliseArgs(Args...) { import urt.meta : AliasSeq; - static if (Args.length == 0) - alias NormaliseArgs = AliasSeq!(); - else static if (Args.length == 1) - alias NormaliseArgs = NormaliseConst!(Args[0]); - else - alias NormaliseArgs = AliasSeq!(NormaliseConst!(Args[0]), NormaliseArgs!(Args[1 .. $])); + alias NormaliseArgs = AliasSeq!(); + static foreach (Arg; Args) + NormaliseArgs = AliasSeq!(NormaliseArgs, NormaliseConst!Arg); } template NormaliseConst(T) { static if (is(T == const(U), U) || is(T == immutable(U), U)) alias NormaliseConst = NormaliseConst!U; - else static if (is(T == U[], U)) + else static if (is(T == immutable(U)[], U) || is(T == U[], U)) alias NormaliseConst = const(U)[]; else static if (is(T == U[N], U, size_t N)) alias NormaliseConst = const(U)[N]; From dd8885f170b229c2392ec430fdb2e1abacf920b8 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 25 Feb 2026 02:42:48 +1000 Subject: [PATCH 090/138] Fix json escape parsing. --- src/urt/format/json.d | 48 +++++++++++++++++++++++++++++++++++------ src/urt/string/format.d | 2 +- src/urt/string/uni.d | 6 +++--- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/urt/format/json.d b/src/urt/format/json.d index f7455f0..5cfd62c 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -370,20 +370,56 @@ Variant parse_node(ref const(char)[] text) { assert(text.length > 1); size_t i = 1; + Array!(char, 0) tmp; // TODO: needs a generous stack buffer! while (i < text.length && text[i] != '"') { - // TODO: we need to collapse the escape sequence, so we need to copy the string somewhere >_< - // ...overwrite the source buffer? if (text[i] == '\\') { - assert(i + 1 < text.length); - i += 2; + if (tmp.empty) + { + tmp.reserve(256); + tmp = text[1 .. i]; + } + if (++i == text.length) + break; + if (text[i] == 'u') + { + import urt.conv : parse_uint; + if (++i + 4 >= text.length) + break; + size_t taken; + ulong code = text[i .. i + 4].parse_uint(&taken, 16); + if (taken != 4) + break; + i += 4; + dchar c = cast(dchar)code; + if ((c >> 11) == 0x1B) + { + if (code >= 0xDC00) + break; // low surrogate without preceding high surrogate + if (i + 6 >= text.length || text[i] != '\\' || text[i+1] != 'u') + break; + code = text[i + 2 .. i + 6].parse_uint(&taken, 16); + if (taken != 4 || (code >> 10) != 0x37) + break; + c = 0x10000 + ((c & 0x3FF) << 10 | (cast(uint)code & 0x3FF)); + i += 6; + } + tmp ~= c; + } + else + goto do_concat; + } + else if (!tmp.empty) + { + do_concat: + tmp ~= text[i++]; } else - i++; + ++i; } assert(i < text.length); - Variant node = Variant(text[1 .. i]); + Variant node = Variant(tmp.empty ? text[1 .. i] : tmp[]); text = text[i + 1 .. $]; return node; } diff --git a/src/urt/string/format.d b/src/urt/string/format.d index 8747c4c..e715eff 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -154,7 +154,7 @@ private: private: import urt.array; -enum is_some_string(T) = is_some_char!T || is(T : const char[]) || is(T : const(String)) || is(T : const(MutableString!N), size_t N) || is(T : const(Array!(char, N)), size_t N); +enum is_some_string(T) = is(T == char) || is(T : const char[]) || is(T : const(String)) || is(T : const(MutableString!N), size_t N) || is(T : const(Array!(char, N)), size_t N); template num_string_args(Args...) { diff --git a/src/urt/string/uni.d b/src/urt/string/uni.d index 8447be9..56297fc 100644 --- a/src/urt/string/uni.d +++ b/src/urt/string/uni.d @@ -288,13 +288,13 @@ size_t uni_convert(const(wchar)[] s, dchar[] buffer) { if (b >= bend) return 0; // End of output buffer - if (p[0] >= 0xD800 && p[0] < 0xE000) + if ((p[0] >> 11) == 0x1B) { if (p + 1 >= pend) return 0; // Unexpected end of input - if (p[0] < 0xDC00) // Surrogate pair: 110110xxxxxxxxxx 110111xxxxxxxxxx + if (p[0] < 0xDC00 && (p[1] >> 10) == 0x37) // Surrogate pair: 110110xxxxxxxxxx 110111xxxxxxxxxx { - *b++ = 0x10000 + ((p[0] - 0xD800) << 10) + (p[1] - 0xDC00); + *b++ = 0x10000 + ((p[0] & 0x3FF) << 10 | (p[1] & 0x3FF)); p += 2; continue; } From c12bfe4e7d728b919dc683d444df8111c256a786 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 24 Feb 2026 14:13:57 +1000 Subject: [PATCH 091/138] Add SizeResult to formalise return values that return a length or a negative error code. --- src/urt/file.d | 6 +++--- src/urt/result.d | 51 ++++++++++++++++++++++++++++++++++++++++++----- src/urt/socket.d | 52 ++++++++++++++++++++++++------------------------ 3 files changed, 75 insertions(+), 34 deletions(-) diff --git a/src/urt/file.d b/src/urt/file.d index 1000bcf..7432f0b 100644 --- a/src/urt/file.d +++ b/src/urt/file.d @@ -647,7 +647,7 @@ Result read_at(ref File file, void[] buffer, ulong offset, out size_t bytesRead) if (!ReadFile(file.handle, buffer.ptr, cast(DWORD)buffer.length, &dwBytesRead, &o)) { Result error = getlasterror_result(); - if (error.systemCode != ERROR_HANDLE_EOF) + if (error.system_code != ERROR_HANDLE_EOF) return error; } bytesRead = dwBytesRead; @@ -734,7 +734,7 @@ FileResult file_result(Result result) { version (Windows) { - switch (result.systemCode) + switch (result.system_code) { case ERROR_SUCCESS: return FileResult.Success; case ERROR_DISK_FULL: return FileResult.DiskFull; @@ -749,7 +749,7 @@ FileResult file_result(Result result) else version (Posix) { static assert(EAGAIN == EWOULDBLOCK, "Expect EGAIN and EWOULDBLOCK are the same value"); - switch (result.systemCode) + switch (result.system_code) { case 0: return FileResult.Success; case ENOSPC: return FileResult.DiskFull; diff --git a/src/urt/result.d b/src/urt/result.d index 2462386..b5b67ea 100644 --- a/src/urt/result.d +++ b/src/urt/result.d @@ -8,15 +8,44 @@ struct Result nothrow @nogc: enum success = Result(); - uint systemCode = 0; + uint system_code = 0; bool opCast(T : bool)() const - => systemCode == 0; + => system_code == 0; bool succeeded() const - => systemCode == 0; + => system_code == 0; bool failed() const - => systemCode != 0; + => system_code != 0; +} + +struct SizeResult +{ +nothrow @nogc: + this(size_t size) + { + debug assert(size <= ptrdiff_t.max, "Size too large to fit in a signed integer!"); + this.size = size; + } + this(Result result) + { + static if (size_t.sizeof == 4) + assert(cast(int)result.system_code >= 0, "Negative result codes not supported on 32-bit machines!"); + this.size = -cast(ptrdiff_t)result.system_code; + } + + ptrdiff_t size = 0; + + bool opCast(T : bool)() const + => size >= 0; + + bool succeeded() const + => size >= 0; + bool failed() const + => size < 0; + + Result result() const + => size >= 0 ? Result.success : Result(cast(uint)-size); } struct StringResult @@ -45,10 +74,16 @@ version (Windows) enum InternalResult : Result { success = Result.success, + failed = Result(ERROR_GEN_FAILURE), buffer_too_small = Result(ERROR_INSUFFICIENT_BUFFER), invalid_parameter = Result(ERROR_INVALID_PARAMETER), data_error = Result(ERROR_INVALID_DATA), - unsupported = Result(ERROR_INVALID_FUNCTION), + unsupported = Result(ERROR_NOT_SUPPORTED), + out_of_range = Result(ERROR_ARITHMETIC_OVERFLOW), + already_exists = Result(ERROR_ALREADY_EXISTS), + timeout = Result(ERROR_TIMEOUT), + aborted = Result(ERROR_OPERATION_ABORTED), + no_memory = Result(ERROR_NOT_ENOUGH_MEMORY), } Result win32_result(uint err) @@ -63,10 +98,16 @@ else version (Posix) enum InternalResult : Result { success = Result.success, + failed = Result(EIO), // not a good general failure, but a lot of people use it this way buffer_too_small = Result(ERANGE), invalid_parameter = Result(EINVAL), data_error = Result(EILSEQ), unsupported = Result(ENOTSUP), + out_of_range = Result(ERANGE), + already_exists = Result(EEXIST), + timeout = Result(ETIMEDOUT), + aborted = Result(EINTR), + no_memory = Result(ENOMEM), } Result posix_result(int err) diff --git a/src/urt/socket.d b/src/urt/socket.d index b1f4645..ddcafdc 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -587,12 +587,12 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval version (Windows) { uint opt = value ? 1 : 0; - r.systemCode = ioctlsocket(socket.handle, FIONBIO, &opt); + r.system_code = ioctlsocket(socket.handle, FIONBIO, &opt); } else version (Posix) { int flags = fcntl(socket.handle, F_GETFL, 0); - r.systemCode = fcntl(socket.handle, F_SETFL, value ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)); + r.system_code = fcntl(socket.handle, F_SETFL, value ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)); } else assert(false, "Not implemented!"); @@ -664,7 +664,7 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval } // set the option - r.systemCode = setsockopt(socket.handle, s_sockOptLevel[level], opt_info.option, cast(const(char)*)arg, s_optTypePlatformSize[opt_info.platform_type]); + r.system_code = setsockopt(socket.handle, s_sockOptLevel[level], opt_info.option, cast(const(char)*)arg, s_optTypePlatformSize[opt_info.platform_type]); return r; } @@ -757,7 +757,7 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ socklen_t writtenLen = s_optTypePlatformSize[opt_info.platform_type]; // get the option - r.systemCode = getsockopt(socket.handle, s_sockOptLevel[level], opt_info.option, cast(char*)arg, &writtenLen); + r.system_code = getsockopt(socket.handle, s_sockOptLevel[level], opt_info.option, cast(char*)arg, &writtenLen); if (opt_info.rt_type != opt_info.platform_type) { @@ -1096,10 +1096,10 @@ Result socket_getlasterror() Result get_socket_error(Socket socket) { Result r; - socklen_t optlen = r.systemCode.sizeof; - int callResult = getsockopt(socket.handle, SOL_SOCKET, SO_ERROR, cast(char*)&r.systemCode, &optlen); + socklen_t optlen = r.system_code.sizeof; + int callResult = getsockopt(socket.handle, SOL_SOCKET, SO_ERROR, cast(char*)&r.system_code, &optlen); if (callResult) - r.systemCode = callResult; + r.system_code = callResult; return r; } @@ -1109,49 +1109,49 @@ SocketResult socket_result(Result result) { if (result) return SocketResult.success; - if (result.systemCode == ConnectionClosedResult.systemCode) + if (result.system_code == ConnectionClosedResult.system_code) return SocketResult.connection_closed; version (Windows) { - if (result.systemCode == WSAEWOULDBLOCK) + if (result.system_code == WSAEWOULDBLOCK) return SocketResult.would_block; - if (result.systemCode == WSAEINPROGRESS) + if (result.system_code == WSAEINPROGRESS) return SocketResult.would_block; - if (result.systemCode == WSAENOBUFS) + if (result.system_code == WSAENOBUFS) return SocketResult.no_buffer; - if (result.systemCode == WSAENETDOWN) + if (result.system_code == WSAENETDOWN) return SocketResult.network_down; - if (result.systemCode == WSAECONNREFUSED) + if (result.system_code == WSAECONNREFUSED) return SocketResult.connection_refused; - if (result.systemCode == WSAECONNRESET) + if (result.system_code == WSAECONNRESET) return SocketResult.connection_reset; - if (result.systemCode == WSAEINTR) + if (result.system_code == WSAEINTR) return SocketResult.interrupted; - if (result.systemCode == WSAENOTSOCK) + if (result.system_code == WSAENOTSOCK) return SocketResult.invalid_socket; - if (result.systemCode == WSAEINVAL) + if (result.system_code == WSAEINVAL) return SocketResult.invalid_argument; } else version (Posix) { static if (EAGAIN != EWOULDBLOCK) - if (result.systemCode == EAGAIN) + if (result.system_code == EAGAIN) return SocketResult.would_block; - if (result.systemCode == EWOULDBLOCK) + if (result.system_code == EWOULDBLOCK) return SocketResult.would_block; - if (result.systemCode == EINPROGRESS) + if (result.system_code == EINPROGRESS) return SocketResult.would_block; - if (result.systemCode == ENOMEM) + if (result.system_code == ENOMEM) return SocketResult.no_buffer; - if (result.systemCode == ENETDOWN) + if (result.system_code == ENETDOWN) return SocketResult.network_down; - if (result.systemCode == ECONNREFUSED) + if (result.system_code == ECONNREFUSED) return SocketResult.connection_refused; - if (result.systemCode == ECONNRESET) + if (result.system_code == ECONNRESET) return SocketResult.connection_reset; - if (result.systemCode == EINTR) + if (result.system_code == EINTR) return SocketResult.interrupted; - if (result.systemCode == EINVAL) + if (result.system_code == EINVAL) return SocketResult.invalid_argument; } return SocketResult.failure; From 00a16a6c304722e89fb7883c500a73a285c8bf54 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 27 Feb 2026 13:36:15 +1000 Subject: [PATCH 092/138] Quantity null pointer guard --- src/urt/si/quantity.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index 8656a2c..1ec1c61 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -352,7 +352,7 @@ nothrow @nogc: if (u.pack) { - ptrdiff_t l2 = u.toString(buffer[l .. $], null, null); + ptrdiff_t l2 = u.toString(buffer.ptr ? buffer.ptr[l .. buffer.length] : null, null, null); if (l2 < 0) return l2; l += l2; From 2239c6ac2641cdf0a1eadec4336c9f67f2055895 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 1 Mar 2026 23:55:17 +1000 Subject: [PATCH 093/138] Cumulative forms of hash functions. --- src/urt/hash.d | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/urt/hash.d b/src/urt/hash.d index 0068baa..4fd7433 100644 --- a/src/urt/hash.d +++ b/src/urt/hash.d @@ -9,24 +9,25 @@ nothrow @nogc: alias fnv1a = fnv1!(uint, true); alias fnv1a64 = fnv1!(ulong, true); -T fnv1(T, bool alternate)(const ubyte[] s) pure nothrow @nogc +template fnv1_initial(T) +{ + static if (is(T == ushort)) + enum T fnv1_initial = 0x811C; + else static if (is(T == uint)) + enum T fnv1_initial = 0x811C9DC5; + else static if (is(T == ulong)) + enum T fnv1_initial = 0XCBF29CE484222325; +} + +T fnv1(T, bool alternate)(const ubyte[] s, T hash = fnv1_initial!T) pure nothrow @nogc if (is(T == ushort) || is(T == uint) || is(T == ulong)) { static if (is(T == ushort)) - { - enum T prime = 0x0101; // 16-bit FNV prime - T hash = 0x811C; // 16-bit FNV offset basis - } + enum T prime = 0x0101; else static if (is(T == uint)) - { - enum T prime = 0x01000193; // 32-bit FNV prime - T hash = 0x811C9DC5; // 32-bit FNV offset basis - } + enum T prime = 0x01000193; else static if (is(T == ulong)) - { - enum T prime = 0x100000001B3; // 64-bit FNV prime - T hash = 0XCBF29CE484222325; // 64-bit FNV offset basis - } + enum T prime = 0x100000001B3; const ubyte* p = s.ptr; for (size_t i = 0; i < s.length; ++i) @@ -49,17 +50,21 @@ unittest { enum hash = fnv1a(cast(ubyte[])"hello world"); static assert(hash == 0xD58B3FA7); + + enum h1 = fnv1a(cast(ubyte[])"hello "); + enum h2 = fnv1a(cast(ubyte[])"world", h1); + static assert(h2 == hash); } -uint adler32(const void[] data) +uint adler32(const void[] data, uint init = 1) { enum A32_BASE = 65521; assert(data.length <= int.max, "Data length must be less than or equal to int.max"); - uint s1 = 1; - uint s2 = 0; + uint s1 = init & 0xFFFF; + uint s2 = (init >> 16) & 0xFFFF; version (SmallSize) { @@ -147,6 +152,9 @@ uint adler32(const void[] data) } +// NOTE: progressive accumulation via `initial` works only when prior chunks have even length!!! +// odd-length chunks misalign the 16-bit word pairing. fixing this requires carrying a pending byte between calls :/ +// maybe there's some way to protect against misuse? ushort internet_checksum(const void[] data, ushort initial = 0xFFFF) { auto bytes = cast(const(const ubyte)[])data; From ff1590de03a98fee2575b25ec3e36d4a83dac9a5 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 4 Mar 2026 04:04:39 +1000 Subject: [PATCH 094/138] Tweak binary_search with option to return insert_pos if the doesn't exist. --- src/urt/algorithm.d | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index d44fec3..00090d7 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -84,7 +84,7 @@ size_t binary_search(Pred)(const void[] arr, size_t stride, const void* value, a return count; } -size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmp_args) +size_t binary_search(alias pred = void, bool insert_pos = false, T, Cmp...)(T[] arr, auto ref Cmp cmp_args) if (!is(Unqual!T == void)) { static if (is(pred == void)) @@ -107,26 +107,33 @@ size_t binary_search(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmp_arg else { // should we chase the first in a sequence of same values? - int cmp = pred(p[mid], cmp_args); + auto cmp = pred(p[mid], cmp_args); if (cmp < 0) low = mid + 1; else high = mid; } } - if (low == arr.length) - return arr.length; - static if (is(pred == void)) + static if (insert_pos) { - if (p[low] == cmp_args[0]) - return low; + return low; } else { - if (pred(p[low], cmp_args) == 0) - return low; + if (low == arr.length) + return arr.length; + static if (is(pred == void)) + { + if (p[low] == cmp_args[0]) + return low; + } + else + { + if (pred(p[low], cmp_args) == 0) + return low; + } + return arr.length; } - return arr.length; } From 8bb650e124952aa2cd1b05d667241e795062c477 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 6 Mar 2026 21:12:41 +1000 Subject: [PATCH 095/138] Fix string tests. --- src/urt/string/string.d | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/urt/string/string.d b/src/urt/string/string.d index 440c395..974e771 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -121,6 +121,7 @@ String makeString(const(char)[] s) nothrow { if (s.length == 0) return String(null); + assert(__ctfe, "only for compile-time use"); return makeString(s, new char[2 + s.length]); } @@ -439,7 +440,8 @@ unittest assert(!emptyLit); // opCast!bool // Test makeString (default allocator) - String s1 = makeString("World"); +// String s1 = makeString("World"); + String s1 = StringLit!"World"; assert(s1.length == 5); assert(s1 == "World"); @@ -471,7 +473,7 @@ unittest assert(s3.length == 5); // Test equality - String s4 = makeString("World"); + String s4 = StringLit!"World"; assert(s3 == s4); // Different allocations, same content assert(s3 != "world"); // Case sensitive assert(s3 != "Worl"); From 5891a8637b5efa5239feaefea643298164c340a3 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 6 Mar 2026 21:12:17 +1000 Subject: [PATCH 096/138] Add save_file and create_directory. --- src/urt/file.d | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/urt/file.d b/src/urt/file.d index 7432f0b..0b54ae2 100644 --- a/src/urt/file.d +++ b/src/urt/file.d @@ -334,6 +334,49 @@ void[] load_file(const(char)[] path, NoGCAllocator allocator = defaultAllocator( return buffer[0..bytesRead]; } +Result save_file(const(char)[] path, const(void)[] data) +{ + File f; + Result r = f.open(path, FileOpenMode.WriteTruncate); + if (!r) + return r; + size_t written; + r = f.write(data, written); + f.close(); + if (!r) + return r; + if (written != data.length) + return InternalResult.failed; + return Result.success; +} + +Result create_directory(const(char)[] path) +{ + version (Windows) + { + if (!CreateDirectoryW(path.twstringz, null)) + { + DWORD err = GetLastError(); + if (err == ERROR_ALREADY_EXISTS) + return Result.success; + return getlasterror_result(); + } + return Result.success; + } + else version (Posix) + { + if (core.sys.posix.sys.stat.mkdir(tconcat(path, "\0").ptr, 493 /* 0755 */) != 0) + { + if (core.stdc.errno.errno == core.stdc.errno.EEXIST) + return Result.success; + return errno_result(); + } + return Result.success; + } + else + static assert(0, "Not implemented"); +} + Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags openFlags = FileOpenFlags.None) { version (Windows) From 056ff766167388e4bdde3f67dc5aecf84f5d68cb Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 6 Mar 2026 21:17:13 +1000 Subject: [PATCH 097/138] Add pki.d (private key infrastructure) which contains key sharing and certification primitives. --- src/urt/crypto/pki.d | 872 +++++++++++++++++++++++++++++++++++++++++++ src/urt/digest/sha.d | 2 +- src/urt/encoding.d | 253 ++++++++++--- 3 files changed, 1078 insertions(+), 49 deletions(-) create mode 100644 src/urt/crypto/pki.d diff --git a/src/urt/crypto/pki.d b/src/urt/crypto/pki.d new file mode 100644 index 0000000..b5bc061 --- /dev/null +++ b/src/urt/crypto/pki.d @@ -0,0 +1,872 @@ +module urt.crypto.pki; + +import urt.array; +import urt.encoding; +import urt.mem; +import urt.result; +import urt.string; +import urt.time; + +nothrow @nogc: + +//version = DebugPKI; + +enum KeyType +{ + rsa2048, + ecdsa_p256, +} + +struct KeyPair +{ +nothrow @nogc: + version (Windows) + { + // CAPI handles (RSA) + HCRYPTPROV hprov; + HCRYPTKEY hkey; + // CNG handles (ECDSA) + BCRYPT_ALG_HANDLE halg; + BCRYPT_KEY_HANDLE hcng; + } + + KeyType type; + + bool valid() const pure + { + version (Windows) + return hprov != 0 || hcng !is null; + else + return false; + } +} + +struct CertRef +{ +nothrow @nogc: + version (Windows) + { + PCCERT_CONTEXT context; + HCERTSTORE store; + } + + bool valid() const pure + { + version (Windows) + return context !is null; + else + return false; + } +} + + +Result generate_keypair(ref KeyPair kp, KeyType type) +{ + version (Windows) + { + DWORD prov_type; + ALG_ID alg; + DWORD flags; + + final switch (type) + { + case KeyType.rsa2048: + prov_type = PROV_RSA_AES; + alg = AT_KEYEXCHANGE; + flags = (2048 << 16) | CRYPT_EXPORTABLE; + break; + + case KeyType.ecdsa_p256: + return generate_ecdsa_p256(kp); + } + + // Use a named container (not CRYPT_VERIFYCONTEXT) so private key is + // accessible for signing operations like CSR generation. + if (!CryptAcquireContextA(&kp.hprov, "openwatt".ptr, null, prov_type, CRYPT_NEWKEYSET)) + { + // Container already exists, open it + if (!CryptAcquireContextA(&kp.hprov, "openwatt".ptr, null, prov_type, 0)) + return getlasterror_result(); + } + + if (!CryptGenKey(kp.hprov, alg, flags, &kp.hkey)) + { + auto r = getlasterror_result(); + CryptReleaseContext(kp.hprov, 0); + kp.hprov = 0; + return r; + } + + kp.type = KeyType.rsa2048; + return Result.success; + } + else version (Posix) + assert(false, "TODO: mbedtls"); + else + static assert(0, "Not implemented"); +} + +void free_keypair(ref KeyPair kp) +{ + version (Windows) + { + if (kp.hkey != 0) + CryptDestroyKey(kp.hkey); + if (kp.hprov != 0) + CryptReleaseContext(kp.hprov, 0); + if (kp.hcng !is null) + BCryptDestroyKey(kp.hcng); + if (kp.halg !is null) + BCryptCloseAlgorithmProvider(kp.halg, 0); + kp.hkey = 0; + kp.hprov = 0; + kp.hcng = null; + kp.halg = null; + } +} + +Result create_self_signed(ref CertRef cert, ref KeyPair key, const(char)[] cn, uint validity_days = 365) +{ + version (Windows) + { + if (!key.valid) + return InternalResult.invalid_parameter; + + char[256] cn_buf = void; + if (cn.length + 3 >= cn_buf.length) + return InternalResult.buffer_too_small; + cn_buf[0 .. 3] = "CN="; + cn_buf[3 .. 3 + cn.length] = cn[]; + cn_buf[3 + cn.length] = 0; + + ubyte[256] name_buf = void; + DWORD name_size = name_buf.sizeof; + if (!CertStrToNameA(X509_ASN_ENCODING, cn_buf.ptr, CERT_X500_NAME_STR, null, name_buf.ptr, &name_size, null)) + return getlasterror_result(); + + CERT_NAME_BLOB subject_blob; + subject_blob.cbData = name_size; + subject_blob.pbData = name_buf.ptr; + + CRYPT_KEY_PROV_INFO key_prov_info; + key_prov_info.pwszContainerName = null; + key_prov_info.pwszProvName = null; + key_prov_info.dwProvType = PROV_RSA_AES; + key_prov_info.dwKeySpec = AT_KEYEXCHANGE; + + SYSTEMTIME start_time = void, end_time = void; + GetSystemTime(&start_time); + end_time = start_time; + + FILETIME ft_start = void; + SystemTimeToFileTime(&start_time, &ft_start); + ulong ticks = *cast(ulong*)&ft_start; + ticks += cast(ulong)validity_days * 24 * 60 * 60 * 10_000_000; // 100ns ticks per day + FILETIME ft_end = *cast(FILETIME*)&ticks; + FileTimeToSystemTime(&ft_end, &end_time); + + PCCERT_CONTEXT ctx = CertCreateSelfSignCertificate( + key.hprov, + &subject_blob, + 0, + &key_prov_info, + null, + &start_time, + &end_time, + null + ); + + if (ctx is null) + return getlasterror_result(); + + cert.context = ctx; + return Result.success; + } + else version (Posix) + assert(false, "TODO: mbedtls"); + else + static assert(0, "Not implemented"); +} + +Result load_certificate(ref CertRef cert, const(ubyte)[] cert_data) +{ + version (Windows) + { + if (cert_data.length == 0) + return InternalResult.invalid_parameter; + + const(ubyte)[] der = cert_data; + Array!ubyte decoded; + + if (is_pem(cert_data)) + { + decoded = decode_pem(cert_data); + if (decoded.length == 0) + return InternalResult.data_error; + der = decoded[]; + } + + cert.store = CertOpenStore( + CERT_STORE_PROV_MEMORY, + 0, 0, 0, null + ); + if (cert.store is null) + return getlasterror_result(); + + if (!CertAddEncodedCertificateToStore( + cert.store, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + der.ptr, + cast(DWORD)der.length, + CERT_STORE_ADD_REPLACE_EXISTING, + cast(PCCERT_CONTEXT*)&cert.context)) + { + auto r = getlasterror_result(); + CertCloseStore(cert.store, 0); + cert.store = null; + return r; + } + + return Result.success; + } + else version (Posix) + assert(false, "TODO: mbedtls"); + else + static assert(0, "Not implemented"); +} + +Result load_private_key(ref KeyPair kp, const(ubyte)[] key_data) +{ + version (Windows) + { + if (key_data.length == 0) + return InternalResult.invalid_parameter; + + const(ubyte)[] der = key_data; + Array!ubyte decoded; + + if (is_pem(key_data)) + { + decoded = decode_pem(key_data); + if (decoded.length == 0) + return InternalResult.data_error; + der = decoded[]; + } + + DWORD blob_size = 0; + // try PKCS#1 first, fall back to PKCS#8 + if (!CryptDecodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + PKCS_RSA_PRIVATE_KEY, + der.ptr, + cast(DWORD)der.length, + 0, + null, + null, + &blob_size)) + { + // Try PKCS#8 wrapper + if (!CryptDecodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + PKCS_PRIVATE_KEY_INFO, + der.ptr, + cast(DWORD)der.length, + 0, + null, + null, + &blob_size)) + return getlasterror_result(); + } + + auto blob_buf = Array!ubyte(Alloc, blob_size); + if (!CryptDecodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + PKCS_RSA_PRIVATE_KEY, + der.ptr, + cast(DWORD)der.length, + 0, + null, + blob_buf.ptr, + &blob_size)) + return getlasterror_result(); + + if (!CryptAcquireContextA(&kp.hprov, "openwatt".ptr, null, PROV_RSA_AES, CRYPT_NEWKEYSET)) + { + if (!CryptAcquireContextA(&kp.hprov, "openwatt".ptr, null, PROV_RSA_AES, 0)) + return getlasterror_result(); + } + + if (!CryptImportKey(kp.hprov, blob_buf.ptr, blob_size, 0, CRYPT_EXPORTABLE, &kp.hkey)) + { + auto r = getlasterror_result(); + CryptReleaseContext(kp.hprov, 0); + kp.hprov = 0; + return r; + } + + return Result.success; + } + else version (Posix) + assert(false, "TODO: mbedtls"); + else + static assert(0, "Not implemented"); +} + +Result associate_key(ref CertRef cert, ref KeyPair key) +{ + version (Windows) + { + if (!cert.valid || !key.valid) + return InternalResult.invalid_parameter; + + CRYPT_KEY_PROV_INFO key_prov_info; + key_prov_info.pwszContainerName = null; + key_prov_info.pwszProvName = null; + key_prov_info.dwProvType = PROV_RSA_AES; + key_prov_info.dwKeySpec = AT_KEYEXCHANGE; + + if (!CertSetCertificateContextProperty( + cert.context, + CERT_KEY_PROV_INFO_PROP_ID, + 0, + &key_prov_info)) + return getlasterror_result(); + + return Result.success; + } + else version (Posix) + assert(false, "TODO: mbedtls"); + else + static assert(0, "Not implemented"); +} + +void free_cert(ref CertRef cert) +{ + version (Windows) + { + if (cert.context !is null) + CertFreeCertificateContext(cert.context); + if (cert.store !is null) + CertCloseStore(cert.store, 0); + cert.context = null; + cert.store = null; + } +} + +SysTime cert_expiry(ref const CertRef cert) +{ + version (Windows) + { + if (cert.context is null || cert.context.pCertInfo is null) + return SysTime(); + return SysTime(*cast(ulong*)&cert.context.pCertInfo.NotAfter); + } + else + return SysTime(); +} + +void* native_cert_context(ref CertRef cert) +{ + version (Windows) + return cast(void*)cert.context; + else + return null; +} + + +Result sign_hash(ref KeyPair kp, const(ubyte)[] hash, ref Array!ubyte signature) +{ + version (Windows) + { + if (kp.hcng is null) + return InternalResult.invalid_parameter; + + ULONG sig_size = 0; + NTSTATUS status = BCryptSignHash(kp.hcng, null, + cast(PUCHAR)hash.ptr, cast(ULONG)hash.length, + null, 0, &sig_size, 0); + if (status != 0) + return Result(cast(uint)status); + + signature = Array!ubyte(Alloc, sig_size); + status = BCryptSignHash(kp.hcng, null, + cast(PUCHAR)hash.ptr, cast(ULONG)hash.length, + signature.ptr, sig_size, &sig_size, 0); + if (status != 0) + { + signature = Array!ubyte(); + return Result(cast(uint)status); + } + + signature.resize(sig_size); + return Result.success; + } + else version (Posix) + assert(false, "TODO: mbedtls"); + else + static assert(0, "Not implemented"); +} + +Result export_public_key_raw(ref KeyPair kp, ref Array!ubyte x, ref Array!ubyte y) +{ + version (Windows) + { + if (kp.hcng is null) + return InternalResult.invalid_parameter; + + ULONG blob_size = 0; + NTSTATUS status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPUBLIC_BLOB.ptr, + null, 0, &blob_size, 0); + if (status != 0) + return Result(cast(uint)status); + + auto blob = Array!ubyte(Alloc, blob_size); + status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPUBLIC_BLOB.ptr, + blob.ptr, blob_size, &blob_size, 0); + if (status != 0) + return Result(cast(uint)status); + + if (blob_size < 8) + return InternalResult.data_error; + + auto hdr = cast(BCRYPT_ECCKEY_BLOB*)blob.ptr; + ULONG key_len = hdr.cbKey; + if (blob_size < 8 + 2 * key_len) + return InternalResult.data_error; + + x = blob[8 .. 8 + key_len]; + y = blob[8 + key_len .. 8 + 2 * key_len]; + return Result.success; + } + else version (Posix) + assert(false, "TODO: mbedtls"); + else + static assert(0, "Not implemented"); +} + + +Array!ubyte generate_csr(ref KeyPair kp, const(char)[] cn) +{ + version (Windows) + { + if (!kp.valid) + return Array!ubyte(); + + char[256] cn_buf = void; + if (cn.length + 3 >= cn_buf.length) + return Array!ubyte(); + cn_buf[0 .. 3] = "CN="; + cn_buf[3 .. 3 + cn.length] = cn[]; + cn_buf[3 + cn.length] = 0; + + ubyte[256] name_buf = void; + DWORD name_size = name_buf.sizeof; + if (!CertStrToNameA(X509_ASN_ENCODING, cn_buf.ptr, CERT_X500_NAME_STR, null, name_buf.ptr, &name_size, null)) + return Array!ubyte(); + + CERT_NAME_BLOB subject_blob; + subject_blob.cbData = name_size; + subject_blob.pbData = name_buf.ptr; + + CERT_REQUEST_INFO req_info; + req_info.dwVersion = CERT_REQUEST_V1; + req_info.Subject = subject_blob; + + DWORD pub_info_size = 0; + if (!CryptExportPublicKeyInfo(kp.hprov, AT_KEYEXCHANGE, X509_ASN_ENCODING, null, &pub_info_size)) + return Array!ubyte(); + auto pub_info_buf = Array!ubyte(Alloc, pub_info_size); + if (!CryptExportPublicKeyInfo(kp.hprov, AT_KEYEXCHANGE, X509_ASN_ENCODING, + cast(CERT_PUBLIC_KEY_INFO*)pub_info_buf.ptr, &pub_info_size)) + return Array!ubyte(); + req_info.SubjectPublicKeyInfo = *cast(CERT_PUBLIC_KEY_INFO*)pub_info_buf.ptr; + + CRYPT_ALGORITHM_IDENTIFIER sig_alg; + sig_alg.pszObjId = cast(LPSTR)szOID_RSA_SHA256RSA.ptr; + + DWORD csr_size = 0; + if (!CryptSignAndEncodeCertificate(kp.hprov, AT_KEYEXCHANGE, + X509_ASN_ENCODING, X509_CERT_REQUEST_TO_BE_SIGNED, + &req_info, &sig_alg, null, null, &csr_size)) + return Array!ubyte(); + + auto csr = Array!ubyte(Alloc, csr_size); + if (!CryptSignAndEncodeCertificate(kp.hprov, AT_KEYEXCHANGE, + X509_ASN_ENCODING, X509_CERT_REQUEST_TO_BE_SIGNED, + &req_info, &sig_alg, null, csr.ptr, &csr_size)) + return Array!ubyte(); + + csr.resize(csr_size); + return csr; + } + else version (Posix) + assert(false, "TODO: mbedtls"); + else + static assert(0, "Not implemented"); +} + + +Result export_private_key(ref KeyPair kp, ref Array!ubyte der_out) +{ + version (Windows) + { + if (kp.hkey == 0) + return InternalResult.invalid_parameter; + + DWORD blob_size = 0; + if (!CryptExportKey(kp.hkey, 0, PRIVATEKEYBLOB, 0, null, &blob_size)) + return getlasterror_result(); + + auto blob = Array!ubyte(Alloc, blob_size); + if (!CryptExportKey(kp.hkey, 0, PRIVATEKEYBLOB, 0, blob.ptr, &blob_size)) + return getlasterror_result(); + + // PRIVATEKEYBLOB → PKCS#1 DER + DWORD der_size = 0; + if (!CryptEncodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + PKCS_RSA_PRIVATE_KEY, + blob.ptr, + 0, + null, + null, + &der_size)) + return getlasterror_result(); + + der_out = Array!ubyte(Alloc, der_size); + if (!CryptEncodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + PKCS_RSA_PRIVATE_KEY, + blob.ptr, + 0, + null, + der_out.ptr, + &der_size)) + { + auto r = getlasterror_result(); + der_out = Array!ubyte(); + return r; + } + + der_out.resize(der_size); + return Result.success; + } + else version (Posix) + assert(false, "TODO: mbedtls"); + else + static assert(0, "Not implemented"); +} + +Array!ubyte encode_pem(const(ubyte)[] der, const(char)[] label) +{ + import urt.encoding : base64_encode, base64_encode_length; + + Array!ubyte result; + + // Header + result.concat(cast(const(ubyte)[])"-----BEGIN "); + result.concat(cast(const(ubyte)[])label); + result.concat(cast(const(ubyte)[])"-----\n"); + + // Base64 body in 64-char lines + size_t enc_len = base64_encode_length(der.length); + auto b64 = Array!char(Alloc, enc_len); + base64_encode(der, b64[0 .. enc_len]); + + size_t pos = 0; + while (pos < enc_len) + { + size_t line_len = enc_len - pos; + if (line_len > 64) + line_len = 64; + result.concat(cast(const(ubyte)[])b64[pos .. pos + line_len]); + result.concat(cast(const(ubyte)[])"\n"); + pos += line_len; + } + + // Footer + result.concat(cast(const(ubyte)[])"-----END "); + result.concat(cast(const(ubyte)[])label); + result.concat(cast(const(ubyte)[])"-----\n"); + + return result; +} + +Result export_ecdsa_private_key(ref KeyPair kp, ref Array!ubyte blob_out) +{ + version (Windows) + { + if (kp.hcng is null) + return InternalResult.invalid_parameter; + + ULONG blob_size = 0; + NTSTATUS status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPRIVATE_BLOB.ptr, + null, 0, &blob_size, 0); + if (status != 0) + return Result(cast(uint)status); + + blob_out = Array!ubyte(Alloc, blob_size); + status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPRIVATE_BLOB.ptr, + blob_out.ptr, blob_size, &blob_size, 0); + if (status != 0) + { + blob_out.clear(); + return Result(cast(uint)status); + } + + blob_out.resize(blob_size); + return Result.success; + } + else version (Posix) + assert(false, "TODO: mbedtls"); + else + static assert(0, "Not implemented"); +} + +Result import_ecdsa_private_key(ref KeyPair kp, const(ubyte)[] blob_data) +{ + version (Windows) + { + NTSTATUS status = BCryptOpenAlgorithmProvider(&kp.halg, BCRYPT_ECDSA_P256_ALGORITHM.ptr, null, 0); + if (status != 0) + return Result(cast(uint)status); + + status = BCryptImportKeyPair(kp.halg, null, BCRYPT_ECCPRIVATE_BLOB.ptr, + &kp.hcng, cast(ubyte*)blob_data.ptr, cast(ULONG)blob_data.length, 0); + if (status != 0) + { + BCryptCloseAlgorithmProvider(kp.halg, 0); + kp.halg = null; + return Result(cast(uint)status); + } + + kp.type = KeyType.ecdsa_p256; + return Result.success; + } + else version (Posix) + assert(false, "TODO: mbedtls"); + else + static assert(0, "Not implemented"); +} + + +private: + +Result generate_ecdsa_p256(ref KeyPair kp) +{ + version (Windows) + { + NTSTATUS status = BCryptOpenAlgorithmProvider(&kp.halg, BCRYPT_ECDSA_P256_ALGORITHM.ptr, null, 0); + if (status != 0) + return Result(cast(uint)status); + + status = BCryptGenerateKeyPair(kp.halg, &kp.hcng, 256, 0); + if (status != 0) + { + BCryptCloseAlgorithmProvider(kp.halg, 0); + kp.halg = null; + return Result(cast(uint)status); + } + + status = BCryptFinalizeKeyPair(kp.hcng, 0); + if (status != 0) + { + BCryptDestroyKey(kp.hcng); + kp.hcng = null; + BCryptCloseAlgorithmProvider(kp.halg, 0); + kp.halg = null; + return Result(cast(uint)status); + } + + kp.type = KeyType.ecdsa_p256; + return Result.success; + } + else version (Posix) + assert(false, "TODO: mbedtls"); + else + static assert(0, "Not implemented"); +} + +bool is_pem(const(ubyte)[] data) +{ + return data.length >= 11 && (cast(const(char)[])data[0 .. 11]) == "-----BEGIN "; +} + +Array!ubyte decode_pem(const(ubyte)[] data) +{ + auto text = cast(const(char)[])data; + + // Find end of first line (header) + size_t start = 0; + while (start < text.length && text[start] != '\n') + ++start; + if (start < text.length) + ++start; // skip \n + + // Find "-----END" marker + size_t end = start; + while (end + 5 < text.length) + { + if (text[end .. end + 5] == "-----") + break; + ++end; + } + + if (end <= start) + return Array!ubyte(); + + // Strip whitespace and decode base64 + // First pass: count non-whitespace characters + size_t b64_len = 0; + for (size_t i = start; i < end; ++i) + { + if (text[i] != '\r' && text[i] != '\n' && text[i] != ' ') + ++b64_len; + } + + // Second pass: copy to contiguous buffer and decode + auto b64_buf = Array!char(Alloc, b64_len); + size_t j = 0; + for (size_t i = start; i < end; ++i) + { + if (text[i] != '\r' && text[i] != '\n' && text[i] != ' ') + b64_buf.ptr[j++] = text[i]; + } + + // Decode + auto result = Array!ubyte(Alloc, base64_decode_length(b64_len)); + ptrdiff_t decoded_len = base64_decode(b64_buf[], result[]); + if (decoded_len < 0) + return Array!ubyte(); + + result.resize(decoded_len); + return result; +} + +version (Windows) +{ + import core.sys.windows.bcrypt; + import core.sys.windows.ntdef : NTSTATUS; + import core.sys.windows.wincrypt; + import core.sys.windows.windef; + import core.sys.windows.winbase; + + pragma(lib, "Advapi32"); + pragma(lib, "Bcrypt"); + pragma(lib, "Crypt32"); + + // Constants not in D runtime + enum LPCSTR CERT_STORE_PROV_MEMORY = cast(LPCSTR)2; + enum DWORD CERT_STORE_ADD_REPLACE_EXISTING = 3; + enum DWORD CERT_X500_NAME_STR = 3; + enum DWORD CERT_KEY_PROV_INFO_PROP_ID = 2; + enum LPCSTR PKCS_RSA_PRIVATE_KEY = cast(LPCSTR)43; + enum LPCSTR PKCS_PRIVATE_KEY_INFO = cast(LPCSTR)44; + + // Structs not in D runtime + struct CRYPT_KEY_PROV_INFO + { + LPWSTR pwszContainerName; + LPWSTR pwszProvName; + DWORD dwProvType; + DWORD dwFlags; + DWORD cProvParam; + void* rgProvParam; // CRYPT_KEY_PROV_PARAM* + DWORD dwKeySpec; + } + + // CSR-related structs and constants + struct CERT_REQUEST_INFO + { + DWORD dwVersion; + CERT_NAME_BLOB Subject; + CERT_PUBLIC_KEY_INFO SubjectPublicKeyInfo; + DWORD cAttribute; + void* rgAttribute; // CRYPT_ATTRIBUTE* + } + + enum CERT_REQUEST_V1 = 0; + enum LPCSTR X509_CERT_REQUEST_TO_BE_SIGNED = cast(LPCSTR)4; + enum szOID_RSA_SHA256RSA = "1.2.840.113549.1.1.11"; + + // Functions not in D runtime + extern (Windows) @nogc nothrow + { + BOOL CryptExportPublicKeyInfo( + HCRYPTPROV hCryptProv, + DWORD dwKeySpec, + DWORD dwCertEncodingType, + CERT_PUBLIC_KEY_INFO* pInfo, + DWORD* pcbInfo + ); + + BOOL CryptSignAndEncodeCertificate( + HCRYPTPROV hCryptProv, + DWORD dwKeySpec, + DWORD dwCertEncodingType, + LPCSTR lpszStructType, + const(void)* pvStructInfo, + CRYPT_ALGORITHM_IDENTIFIER* pSignatureAlgorithm, + const(void)* pvHashAuxInfo, + BYTE* pbEncoded, + DWORD* pcbEncoded + ); + PCCERT_CONTEXT CertCreateSelfSignCertificate( + HCRYPTPROV hCryptProvOrNCryptKey, + PCERT_NAME_BLOB pSubjectIssuerBlob, + DWORD dwFlags, + CRYPT_KEY_PROV_INFO* pKeyProvParam, + CRYPT_ALGORITHM_IDENTIFIER* pSignatureAlgorithm, + SYSTEMTIME* pStartTime, + SYSTEMTIME* pEndTime, + void* pExtensions // PCERT_EXTENSIONS + ); + + BOOL CertStrToNameA( + DWORD dwCertEncodingType, + LPCSTR pszX500, + DWORD dwStrType, + void* pvReserved, + BYTE* pbEncoded, + DWORD* pcbEncoded, + LPCSTR* ppszError + ); + + BOOL CertAddEncodedCertificateToStore( + HCERTSTORE hCertStore, + DWORD dwCertEncodingType, + const(BYTE)* pbCertEncoded, + DWORD cbCertEncoded, + DWORD dwAddDisposition, + PCCERT_CONTEXT* ppCertContext + ); + + BOOL CertSetCertificateContextProperty( + PCCERT_CONTEXT pCertContext, + DWORD dwPropId, + DWORD dwFlags, + const(void)* pvData + ); + + BOOL CryptDecodeObjectEx( + DWORD dwCertEncodingType, + LPCSTR lpszStructType, + const(BYTE)* pbEncoded, + DWORD cbEncoded, + DWORD dwFlags, + void* pDecodePara, // PCRYPT_DECODE_PARA + void* pvStructInfo, + DWORD* pcbStructInfo + ); + + BOOL CryptEncodeObjectEx( + DWORD dwCertEncodingType, + LPCSTR lpszStructType, + const(void)* pvStructInfo, + DWORD dwFlags, + void* pEncodePara, // PCRYPT_ENCODE_PARA + void* pvEncoded, + DWORD* pcbEncoded + ); + } +} diff --git a/src/urt/digest/sha.d b/src/urt/digest/sha.d index 671a6fa..186ede5 100644 --- a/src/urt/digest/sha.d +++ b/src/urt/digest/sha.d @@ -88,7 +88,7 @@ ubyte[Context.DigestLen] sha_finalise(Context)(ref Context ctx) // Pad whatever data is left in the buffer. ctx.data[i++] = 0x80; - if (ctx.datalen > 56) + if (ctx.datalen >= 56) { ctx.data[i .. 64] = 0x00; Context.transform(ctx, ctx.data); diff --git a/src/urt/encoding.d b/src/urt/encoding.d index 9f76aa0..c748e7a 100644 --- a/src/urt/encoding.d +++ b/src/urt/encoding.d @@ -8,17 +8,23 @@ enum HexDecode(string str) = () { ubyte[hex_decode_length(str.length)] r; enum URLDecode(string str) = () { char[url_decode_length(str)] r; size_t len = url_decode(str, r[]); assert(len == r.sizeof, "Not a URL encoded string: " ~ str); return r; }(); -size_t base64_encode_length(size_t source_length) pure - => (source_length + 2) / 3 * 4; +size_t base64_encode_length(bool url = false)(size_t source_length) pure +{ + static if (url) + return (source_length * 4 + 2) / 3; // no padding + else + return (source_length + 2) / 3 * 4; +} -size_t base64_encode_length(const void[] data) pure - => base64_encode_length(data.length); +size_t base64_encode_length(bool url = false)(const void[] data) pure + => base64_encode_length!url(data.length); -ptrdiff_t base64_encode(const void[] data, char[] result) pure +ptrdiff_t base64_encode(bool url = false)(const void[] data, char[] result) pure { + immutable(char)* table = url ? &base64url[0] : &base64[0]; auto src = cast(const(ubyte)[])data; size_t len = data.length; - size_t out_len = base64_encode_length(len); + size_t out_len = base64_encode_length!url(len); if (result.length < out_len) return -1; @@ -31,77 +37,111 @@ ptrdiff_t base64_encode(const void[] data, char[] result) pure ubyte b1 = src[i++]; ubyte b2 = src[i++]; - result[j++] = base64[b0 >> 2]; - result[j++] = base64[((b0 & 0x03) << 4) | (b1 >> 4)]; - result[j++] = base64[((b1 & 0x0F) << 2) | (b2 >> 6)]; - result[j++] = base64[b2 & 0x3F]; + result[j++] = table[b0 >> 2]; + result[j++] = table[((b0 & 0x03) << 4) | (b1 >> 4)]; + result[j++] = table[((b1 & 0x0F) << 2) | (b2 >> 6)]; + result[j++] = table[b2 & 0x3F]; } if (i < len) { ubyte b0 = src[i++]; - result[j++] = base64[b0 >> 2]; + result[j++] = table[b0 >> 2]; if (i < len) { ubyte b1 = src[i]; - result[j++] = base64[((b0 & 0x03) << 4) | (b1 >> 4)]; - result[j++] = base64[((b1 & 0x0F) << 2)]; + result[j++] = table[((b0 & 0x03) << 4) | (b1 >> 4)]; + result[j++] = table[((b1 & 0x0F) << 2)]; } else { - result[j++] = base64[((b0 & 0x03) << 4)]; - result[j++] = '='; + result[j++] = table[((b0 & 0x03) << 4)]; + static if (!url) + result[j++] = '='; } - result[j] = '='; + static if (!url) + result[j] = '='; } return out_len; } -size_t base64_decode_length(size_t source_length) pure - => source_length / 4 * 3; +size_t base64_decode_length(bool url = false)(size_t source_length) pure +{ + static if (url) + { + size_t remainder = source_length % 4; + return source_length / 4 * 3 + (remainder > 0 ? remainder - 1 : 0); + } + else + return source_length / 4 * 3; +} -size_t base64_decode_length(const char[] data) pure - => base64_decode_length(data.length); +size_t base64_decode_length(bool url = false)(const char[] data) pure + => base64_decode_length!url(data.length); -ptrdiff_t base64_decode(const char[] data, void[] result) pure +ptrdiff_t base64_decode(bool url = false)(const char[] data, void[] result) pure { + static if (url) + { + enum uint offset = 45; + enum uint map_size = 78; + alias map = base64url_map; + } + else + { + enum uint offset = 43; + enum uint map_size = 80; + alias map = base64_map; + } + size_t len = data.length; auto dest = cast(ubyte[])result; - size_t out_len = base64_decode_length(len); - if (data[len - 1] == '=') - out_len--; - if (data[len - 2] == '=') - out_len--; + size_t out_len; + + static if (url) + { + size_t remainder = len % 4; + if (remainder == 1) + return -1; + out_len = len / 4 * 3 + (remainder > 0 ? remainder - 1 : 0); + } + else + { + out_len = len / 4 * 3; + if (len >= 1 && data[len - 1] == '=') + --out_len; + if (len >= 2 && data[len - 2] == '=') + --out_len; + } if (result.length < out_len) return -1; + static if (url) + size_t full_len = len / 4 * 4; + else + size_t full_len = len; + size_t i = 0; size_t j = 0; - while (i < len) + while (i < full_len) { - if (i > len - 4) + if (i > full_len - 4) return -1; // TODO: this could be faster by using more memory, store a full 256-byte table and no comparisons... - uint b0 = data[i++] - 43; - uint b1 = data[i++] - 43; - uint b2 = data[i++] - 43; - uint b3 = data[i++] - 43; - if (b0 >= 80) - return -1; - if (b1 >= 80) - return -1; - if (b2 >= 80) - return -1; - if (b3 >= 80) + uint b0 = data[i++] - offset; + uint b1 = data[i++] - offset; + uint b2 = data[i++] - offset; + uint b3 = data[i++] - offset; + if (b0 >= map_size || b1 >= map_size || b2 >= map_size || b3 >= map_size) return -1; - b0 = base64_map[b0]; - b1 = base64_map[b1]; - b2 = base64_map[b2]; - b3 = base64_map[b3]; + b0 = map[b0]; + b1 = map[b1]; + b2 = map[b2]; + b3 = map[b3]; dest[j++] = cast(ubyte)((b0 << 2) | (b1 >> 4)); if (b2 != 64) @@ -110,9 +150,48 @@ ptrdiff_t base64_decode(const char[] data, void[] result) pure dest[j++] = cast(ubyte)((b2 << 6) | b3); } + static if (url) + { + if (i < len) + { + uint b0 = data[i++] - offset; + uint b1 = data[i++] - offset; + if (b0 >= map_size || b1 >= map_size) + return -1; + b0 = map[b0]; + b1 = map[b1]; + dest[j++] = cast(ubyte)((b0 << 2) | (b1 >> 4)); + + if (i < len) + { + uint b2 = data[i] - offset; + if (b2 >= map_size) + return -1; + b2 = map[b2]; + dest[j++] = cast(ubyte)((b1 << 4) | (b2 >> 2)); + } + } + } + return out_len; } +size_t base64url_encode_length(size_t source_length) pure +=> base64_encode_length!true(source_length); + +size_t base64url_encode_length(const void[] data) pure +=> base64_encode_length!true(data); + +alias base64url_encode = base64_encode!true; + +size_t base64url_decode_length(size_t source_length) pure + => base64_decode_length!true(source_length); + +size_t base64url_decode_length(const char[] data) pure + => base64_decode_length!true(data); + +alias base64url_decode = base64_decode!true; + unittest { immutable ubyte[12] data = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C]; @@ -141,6 +220,75 @@ unittest len = base64_decode(encoded, decoded); assert(len == 10); assert(data[0..10] == decoded[0..10]); + + // base64url: different alphabet (+/ → -_) and no padding + // [0..3] all-62, [3..6] all-63, [6..9] mixed 62/63 + immutable ubyte[9] urldata = [0xFB, 0xEF, 0xBE, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xBF]; + + len = base64_encode(urldata[0..3], encoded[0..4]); + assert(len == 4); + assert(encoded[0..4] == "++++"); + len = base64url_encode(urldata[0..3], encoded[0..4]); + assert(len == 4); + assert(encoded[0..4] == "----"); + + len = base64_encode(urldata[3..6], encoded[0..4]); + assert(len == 4); + assert(encoded[0..4] == "////"); + len = base64url_encode(urldata[3..6], encoded[0..4]); + assert(len == 4); + assert(encoded[0..4] == "____"); + + len = base64_encode(urldata[6..9], encoded[0..4]); + assert(len == 4); + assert(encoded[0..4] == "+/+/"); + len = base64url_encode(urldata[6..9], encoded[0..4]); + assert(len == 4); + assert(encoded[0..4] == "-_-_"); + + // decode roundtrips + len = base64_decode("++++", decoded[0..3]); + assert(len == 3); + assert(decoded[0..3] == urldata[0..3]); + len = base64url_decode("----", decoded[0..3]); + assert(len == 3); + assert(decoded[0..3] == urldata[0..3]); + len = base64_decode("+/+/", decoded[0..3]); + assert(len == 3); + assert(decoded[0..3] == urldata[6..9]); + len = base64url_decode("-_-_", decoded[0..3]); + assert(len == 3); + assert(decoded[0..3] == urldata[6..9]); + + // padding vs no-padding: 1 byte → /w== vs _w + len = base64_encode(urldata[3..4], encoded[0..4]); + assert(len == 4); + assert(encoded[0..4] == "/w=="); + len = base64_decode(encoded[0..4], decoded[0..1]); + assert(len == 1); + assert(decoded[0] == 0xFF); + + len = base64url_encode(urldata[3..4], encoded[0..2]); + assert(len == 2); + assert(encoded[0..2] == "_w"); + len = base64url_decode(encoded[0..2], decoded[0..1]); + assert(len == 1); + assert(decoded[0] == 0xFF); + + // padding vs no-padding: 2 bytes → ++8= vs --8 + len = base64_encode(urldata[0..2], encoded[0..4]); + assert(len == 4); + assert(encoded[0..4] == "++8="); + len = base64_decode(encoded[0..4], decoded[0..2]); + assert(len == 2); + assert(decoded[0..2] == urldata[0..2]); + + len = base64url_encode(urldata[0..2], encoded[0..3]); + assert(len == 3); + assert(encoded[0..3] == "--8"); + len = base64url_decode(encoded[0..3], decoded[0..2]); + assert(len == 2); + assert(decoded[0..2] == urldata[0..2]); } size_t hex_encode_length(size_t sourceLength) pure @@ -331,10 +479,19 @@ unittest private: __gshared immutable char[64] base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -__gshared immutable ubyte[80] base64_map = [ 62, 0, 0, 0, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 64, 0, 0, - 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, - 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, +__gshared immutable ubyte[80] base64_map = [ 62, 0, 0, 0, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 64, 0, 0, + 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, + 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 +]; + +__gshared immutable char[64] base64url = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +__gshared immutable ubyte[78] base64url_map = [ 62, 0, 0, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63, + 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 ]; From bddf980a2b2bd91bcfc3e8228d1e1f41490c1820 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 11 Mar 2026 22:13:22 +1000 Subject: [PATCH 098/138] Posix SysTime from DateTime function. --- src/urt/package.d | 4 +- src/urt/time.d | 104 +++++++++++++++++++++++++++------------------- 2 files changed, 64 insertions(+), 44 deletions(-) diff --git a/src/urt/package.d b/src/urt/package.d index 6e57129..4c4959e 100644 --- a/src/urt/package.d +++ b/src/urt/package.d @@ -21,8 +21,8 @@ private: pragma(crt_constructor) void crt_bootup() { - import urt.time : initClock; - initClock(); + import urt.time : init_clock; + init_clock(); import urt.rand; init_rand(); diff --git a/src/urt/time.d b/src/urt/time.d index e970b75..0abf92a 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -67,9 +67,9 @@ pure nothrow @nogc: static if (is(T == Time!c, Clock c) && c != clock) { static if (clock == Clock.Monotonic && c == Clock.SystemTime) - return SysTime(ticks + ticksSinceBoot); + return SysTime(ticks + ticks_since_boot); else - return MonoTime(ticks - ticksSinceBoot); + return MonoTime(ticks - ticks_since_boot); } else static assert(false, "constraint out of sync"); @@ -88,9 +88,9 @@ pure nothrow @nogc: static if (clock != c) { static if (clock == Clock.Monotonic) - t1 += ticksSinceBoot; + t1 += ticks_since_boot; else - t2 += ticksSinceBoot; + t2 += ticks_since_boot; } return Duration(t1 - t2); } @@ -168,7 +168,7 @@ pure nothrow @nogc: => ticks != 0; T opCast(T)() const if (is_some_float!T) - => cast(T)ticks / cast(T)ticksPerSecond; + => cast(T)ticks / cast(T)ticks_per_second; bool opEquals(Duration b) const => ticks == b.ticks; @@ -191,21 +191,21 @@ pure nothrow @nogc: long as(string base)() const { static if (base == "nsecs") - return ticks*nsecMultiplier; + return ticks*nsec_multiplier; else static if (base == "usecs") - return ticks*nsecMultiplier / 1_000; + return ticks*nsec_multiplier / 1_000; else static if (base == "msecs") - return ticks*nsecMultiplier / 1_000_000; + return ticks*nsec_multiplier / 1_000_000; else static if (base == "seconds") - return ticks*nsecMultiplier / 1_000_000_000; + return ticks*nsec_multiplier / 1_000_000_000; else static if (base == "minutes") - return ticks*nsecMultiplier / 60_000_000_000; + return ticks*nsec_multiplier / 60_000_000_000; else static if (base == "hours") - return ticks*nsecMultiplier / 3_600_000_000_000; + return ticks*nsec_multiplier / 3_600_000_000_000; else static if (base == "days") - return ticks*nsecMultiplier / 86_400_000_000_000; + return ticks*nsec_multiplier / 86_400_000_000_000; else static if (base == "weeks") - return ticks*nsecMultiplier / 604_800_000_000_000; + return ticks*nsec_multiplier / 604_800_000_000_000; else static assert(false, "Invalid base"); } @@ -303,7 +303,7 @@ pure nothrow @nogc: if (last_unit == 8) return -1; - ticks = total_nsecs / nsecMultiplier; + ticks = total_nsecs / nsec_multiplier; return offset; } @@ -679,21 +679,21 @@ pure nothrow @nogc: Duration dur(string base)(long value) pure { static if (base == "nsecs") - return Duration(value / nsecMultiplier); + return Duration(value / nsec_multiplier); else static if (base == "usecs") - return Duration(value*1_000 / nsecMultiplier); + return Duration(value*1_000 / nsec_multiplier); else static if (base == "msecs") - return Duration(value*1_000_000 / nsecMultiplier); + return Duration(value*1_000_000 / nsec_multiplier); else static if (base == "seconds") - return Duration(value*1_000_000_000 / nsecMultiplier); + return Duration(value*1_000_000_000 / nsec_multiplier); else static if (base == "minutes") - return Duration(value*60_000_000_000 / nsecMultiplier); + return Duration(value*60_000_000_000 / nsec_multiplier); else static if (base == "hours") - return Duration(value*3_600_000_000_000 / nsecMultiplier); + return Duration(value*3_600_000_000_000 / nsec_multiplier); else static if (base == "days") - return Duration(value*86_400_000_000_000 / nsecMultiplier); + return Duration(value*86_400_000_000_000 / nsec_multiplier); else static if (base == "weeks") - return Duration(value*604_800_000_000_000 / nsecMultiplier); + return Duration(value*604_800_000_000_000 / nsec_multiplier); else static assert(false, "Invalid base"); } @@ -746,10 +746,11 @@ SysTime getSysTime() SysTime getSysTime(DateTime time) pure { version (Windows) - return dateTimeToFileTime(time); + return datetime_to_filetime(time); else version (Posix) { - assert(false, "TODO"); + timespec ts = datetime_to_realtime(time); + return SysTime(ts.tv_sec * 1_000_000_000 + ts.tv_nsec); } else static assert(false, "TODO"); @@ -758,12 +759,12 @@ SysTime getSysTime(DateTime time) pure DateTime getDateTime() { version (Windows) - return fileTimeToDateTime(getSysTime()); + return filetime_to_datetime(getSysTime()); else version (Posix) { timespec ts; clock_gettime(CLOCK_REALTIME, &ts); - return realtimeToDateTime(ts); + return realtime_to_datetime(ts); } else static assert(false, "TODO"); @@ -772,13 +773,13 @@ DateTime getDateTime() DateTime getDateTime(SysTime time) pure { version (Windows) - return fileTimeToDateTime(time); + return filetime_to_datetime(time); else version (Posix) { timespec ts; ts.tv_sec = cast(time_t)(time.ticks / 1_000_000_000); ts.tv_nsec = cast(uint)(time.ticks % 1_000_000_000); - return realtimeToDateTime(ts); + return realtime_to_datetime(ts); } else static assert(false, "TODO"); @@ -825,18 +826,18 @@ __gshared immutable uint[9] digit_multipliers = [ 100_000_000, 10_000_000, 1_000 version (Windows) { - immutable uint ticksPerSecond; - immutable uint nsecMultiplier; - immutable ulong ticksSinceBoot; + immutable uint ticks_per_second; + immutable uint nsec_multiplier; + immutable ulong ticks_since_boot; } else version (Posix) { - enum uint ticksPerSecond = 1_000_000_000; - enum uint nsecMultiplier = 1; - immutable ulong ticksSinceBoot; + enum uint ticks_per_second = 1_000_000_000; + enum uint nsec_multiplier = 1; + immutable ulong ticks_since_boot; } -package(urt) void initClock() +package(urt) void init_clock() { cast()startTime = getTime(); @@ -847,8 +848,8 @@ package(urt) void initClock() LARGE_INTEGER freq; QueryPerformanceFrequency(&freq); - cast()ticksPerSecond = cast(uint)freq.QuadPart; - cast()nsecMultiplier = 1_000_000_000 / ticksPerSecond; + cast()ticks_per_second = cast(uint)freq.QuadPart; + cast()nsec_multiplier = 1_000_000_000 / ticks_per_second; // we want the ftime for QPC 0; which should be the boot time // we'll repeat this 100 times and take the minimum, and we should be within probably nanoseconds of the correct value @@ -860,7 +861,7 @@ package(urt) void initClock() GetSystemTimePreciseAsFileTime(cast(FILETIME*)&ftime); bootTime = min(bootTime, ftime - qpc.QuadPart); } - cast()ticksSinceBoot = bootTime; + cast()ticks_since_boot = bootTime; } else version (Posix) { @@ -875,7 +876,7 @@ package(urt) void initClock() clock_gettime(CLOCK_REALTIME, &rt); bootTime = min(bootTime, rt.tv_sec*1_000_000_000 + rt.tv_nsec - mt.tv_sec*1_000_000_000 - mt.tv_nsec); } - cast()ticksSinceBoot = bootTime; + cast()ticks_since_boot = bootTime; } else static assert(false, "TODO"); @@ -1010,7 +1011,7 @@ unittest version (Windows) { - DateTime fileTimeToDateTime(SysTime ftime) pure + DateTime filetime_to_datetime(SysTime ftime) pure { version (BigEndian) static assert(false, "Only works in little endian!"); @@ -1034,7 +1035,7 @@ version (Windows) return dt; } - SysTime dateTimeToFileTime(DateTime dt) pure + SysTime datetime_to_filetime(ref DateTime dt) pure { version (BigEndian) static assert(false, "Only works in little endian!"); @@ -1062,7 +1063,7 @@ version (Windows) } else version (Posix) { - DateTime realtimeToDateTime(timespec ts) pure + DateTime realtime_to_datetime(timespec ts) pure { tm t; alias PureHACK = extern(C) tm* function(time_t* timer, tm* buf) pure nothrow @nogc; @@ -1080,4 +1081,23 @@ else version (Posix) return dt; } + + timespec datetime_to_realtime(ref DateTime time) pure + { + tm t; + t.tm_year = time.year - 1900; + t.tm_mon = cast(int)time.month - 1; + t.tm_mday = time.day; + t.tm_hour = time.hour; + t.tm_min = time.minute; + t.tm_sec = time.second; + + alias PureHACK = extern(C) time_t function(tm* timer) pure nothrow @nogc; + time_t sec = (cast(PureHACK)&mktime)(&t); + + timespec ts; + ts.tv_sec = sec; + ts.tv_nsec = time.ns; + return ts; + } } From 0cc7b1061aa14d094b9f773c12b9b35509cdbbf6 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sat, 7 Mar 2026 15:10:50 +1000 Subject: [PATCH 099/138] Migrate away from Windows CAPI and remove that lib dependency. The tooling we need to avoid it is needed for mbedtls/handrolled anyway. --- src/urt/crypto/der.d | 245 ++++++++++ src/urt/crypto/pem.d | 72 +++ src/urt/crypto/pki.d | 1013 +++++++++++++++++++++--------------------- 3 files changed, 816 insertions(+), 514 deletions(-) create mode 100644 src/urt/crypto/der.d create mode 100644 src/urt/crypto/pem.d diff --git a/src/urt/crypto/der.d b/src/urt/crypto/der.d new file mode 100644 index 0000000..0ec035a --- /dev/null +++ b/src/urt/crypto/der.d @@ -0,0 +1,245 @@ +module urt.crypto.der; + +import urt.time : DateTime; + +nothrow @nogc: + + +// pre-encoded OID content bytes +static immutable ubyte[7] oid_ec_public_key = [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01]; // 1.2.840.10045.2.1 +static immutable ubyte[8] oid_prime256v1 = [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07]; // 1.2.840.10045.3.1.7 +static immutable ubyte[8] oid_sha256_ecdsa = [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02]; // 1.2.840.10045.4.3.2 +static immutable ubyte[3] oid_common_name = [0x55, 0x04, 0x03]; // 2.5.4.3 + + +// Primitives... + +uint der_length_size(size_t len) pure +{ + if (len < 0x80) return 1; + if (len < 0x100) return 2; + return 3; +} + +// write tag + length header for known content size +ptrdiff_t der_header(ubyte[] buf, ubyte tag, size_t content_len) +{ + size_t n = 1 + der_length_size(content_len); + if (!buf.ptr) + return n; + if (buf.length < n) + return -1; + size_t pos = 0; + buf[pos++] = tag; + pos += put_length(buf[pos .. $], content_len); + return pos; +} + +// write tag + length + content +ptrdiff_t der_tlv(ubyte[] buf, ubyte tag, const(ubyte)[] content) +{ + size_t n = 1 + der_length_size(content.length) + content.length; + if (!buf.ptr) + return n; + if (buf.length < n) + return -1; + size_t pos = 0; + buf[pos++] = tag; + pos += put_length(buf[pos .. $], content.length); + buf[pos .. pos + content.length] = content[]; + return n; +} + +// INTEGER with leading-zero stripping and sign padding +ptrdiff_t der_integer(ubyte[] buf, const(ubyte)[] value) +{ + while (value.length > 1 && value[0] == 0) + value = value[1 .. $]; + bool pad = (value[0] & 0x80) != 0; + size_t content_len = (pad ? 1 : 0) + value.length; + size_t n = 1 + der_length_size(content_len) + content_len; + if (!buf.ptr) + return n; + if (buf.length < n) + return -1; + size_t pos = 0; + buf[pos++] = 0x02; + pos += put_length(buf[pos .. $], content_len); + if (pad) + buf[pos++] = 0; + buf[pos .. pos + value.length] = value[]; + return n; +} + +ptrdiff_t der_integer_small(ubyte[] buf, uint value) +{ + if (value == 0) + { + if (!buf.ptr) + return 3; + if (buf.length < 3) + return -1; + buf[0] = 0x02; + buf[1] = 0x01; + buf[2] = 0x00; + return 3; + } + ubyte[4] be = void; + uint n = 0; + for (uint v = value; v > 0; v >>= 8) + ++n; + for (uint i = 0; i < n; ++i) + be[i] = cast(ubyte)(value >> ((n - 1 - i) * 8)); + return der_integer(buf, be[0 .. n]); +} + + +// Composite structures... + +// SEQUENCE { INTEGER r, INTEGER s } from raw 64-byte ECDSA P-256 signature +ptrdiff_t der_ecdsa_sig(ubyte[] buf, const(ubyte)[] raw_sig) +{ + ptrdiff_t r_size = der_integer(null, raw_sig[0 .. 32]); + ptrdiff_t s_size = der_integer(null, raw_sig[32 .. 64]); + size_t content = r_size + s_size; + size_t total = 1 + der_length_size(content) + content; + if (!buf.ptr) + return total; + if (buf.length < total) + return -1; + size_t pos = 0; + buf[pos++] = 0x30; + pos += put_length(buf[pos .. $], content); + pos += der_integer(buf[pos .. $], raw_sig[0 .. 32]); + pos += der_integer(buf[pos .. $], raw_sig[32 .. 64]); + return total; +} + +ptrdiff_t der_utctime(ubyte[] buf, DateTime dt) +{ + enum size_t total = 15; // tag(1) + length(1) + content(13) + if (!buf.ptr) + return total; + if (buf.length < total) + return -1; + buf[0] = 0x17; + buf[1] = 13; + ubyte yr = cast(ubyte)(dt.year % 100); + ubyte mo = cast(ubyte)dt.month; + buf[2] = cast(ubyte)('0' + yr / 10); + buf[3] = cast(ubyte)('0' + yr % 10); + buf[4] = cast(ubyte)('0' + mo / 10); + buf[5] = cast(ubyte)('0' + mo % 10); + buf[6] = cast(ubyte)('0' + dt.day / 10); + buf[7] = cast(ubyte)('0' + dt.day % 10); + buf[8] = cast(ubyte)('0' + dt.hour / 10); + buf[9] = cast(ubyte)('0' + dt.hour % 10); + buf[10] = cast(ubyte)('0' + dt.minute / 10); + buf[11] = cast(ubyte)('0' + dt.minute % 10); + buf[12] = cast(ubyte)('0' + dt.second / 10); + buf[13] = cast(ubyte)('0' + dt.second % 10); + buf[14] = 'Z'; + return total; +} + +// X.500 Name with a single CN attribute: SEQUENCE { SET { SEQUENCE { OID, UTF8String } } } +ptrdiff_t der_name_cn(ubyte[] buf, const(char)[] cn) +{ + size_t oid_tlv = 1 + 1 + oid_common_name.length; + size_t utf8_tlv = 1 + der_length_size(cn.length) + cn.length; + size_t atv_content = oid_tlv + utf8_tlv; + size_t atv = 1 + der_length_size(atv_content) + atv_content; + size_t rdn = 1 + der_length_size(atv) + atv; + size_t total = 1 + der_length_size(rdn) + rdn; + if (!buf.ptr) + return total; + if (buf.length < total) + return -1; + size_t pos = 0; + buf[pos++] = 0x30; + pos += put_length(buf[pos .. $], rdn); + buf[pos++] = 0x31; + pos += put_length(buf[pos .. $], atv); + buf[pos++] = 0x30; + pos += put_length(buf[pos .. $], atv_content); + pos += der_tlv(buf[pos .. $], 0x06, oid_common_name[]); + pos += der_tlv(buf[pos .. $], 0x0c, cast(const(ubyte)[])cn); + return total; +} + +// SubjectPublicKeyInfo for EC P-256 uncompressed point +ptrdiff_t der_ec_pubkey_info(ubyte[] buf, const(ubyte)[] x, const(ubyte)[] y) +{ + size_t oid1_tlv = 1 + 1 + oid_ec_public_key.length; + size_t oid2_tlv = 1 + 1 + oid_prime256v1.length; + size_t alg_content = oid1_tlv + oid2_tlv; + size_t alg = 1 + der_length_size(alg_content) + alg_content; + + size_t bs_content = 1 + 1 + x.length + y.length; // unused_bits + 0x04 + x + y + size_t bs = 1 + der_length_size(bs_content) + bs_content; + + size_t spki_content = alg + bs; + size_t total = 1 + der_length_size(spki_content) + spki_content; + if (!buf.ptr) + return total; + if (buf.length < total) + return -1; + + size_t pos = 0; + buf[pos++] = 0x30; + pos += put_length(buf[pos .. $], spki_content); + + buf[pos++] = 0x30; + pos += put_length(buf[pos .. $], alg_content); + pos += der_tlv(buf[pos .. $], 0x06, oid_ec_public_key[]); + pos += der_tlv(buf[pos .. $], 0x06, oid_prime256v1[]); + + buf[pos++] = 0x03; + pos += put_length(buf[pos .. $], bs_content); + buf[pos++] = 0x00; + buf[pos++] = 0x04; + buf[pos .. pos + x.length] = x[]; + pos += x.length; + buf[pos .. pos + y.length] = y[]; + pos += y.length; + + return total; +} + +// AlgorithmIdentifier for sha256WithECDSA +ptrdiff_t der_sig_alg(ubyte[] buf) +{ + size_t oid_tlv = 1 + 1 + oid_sha256_ecdsa.length; + size_t total = 1 + der_length_size(oid_tlv) + oid_tlv; + if (!buf.ptr) + return total; + if (buf.length < total) + return -1; + size_t pos = 0; + buf[pos++] = 0x30; + pos += put_length(buf[pos .. $], oid_tlv); + pos += der_tlv(buf[pos .. $], 0x06, oid_sha256_ecdsa[]); + return total; +} + + +private: + +uint put_length(ubyte[] buf, size_t len) +{ + if (len < 0x80) + { + buf[0] = cast(ubyte)len; + return 1; + } + if (len < 0x100) + { + buf[0] = 0x81; + buf[1] = cast(ubyte)len; + return 2; + } + buf[0] = 0x82; + buf[1] = cast(ubyte)(len >> 8); + buf[2] = cast(ubyte)(len & 0xff); + return 3; +} diff --git a/src/urt/crypto/pem.d b/src/urt/crypto/pem.d new file mode 100644 index 0000000..1cc32e7 --- /dev/null +++ b/src/urt/crypto/pem.d @@ -0,0 +1,72 @@ +module urt.crypto.pem; + +import urt.array; +import urt.encoding; +import urt.mem; + +nothrow @nogc: + + +bool is_pem(const(char)[] data) + => data.length >= 11 && data[0 .. 11] == "-----BEGIN "; + +Array!char encode_pem(const(ubyte)[] der, const(char)[] label) +{ + Array!char result = "-----BEGIN "; + result ~= label; + result ~= "-----\n"; + + size_t enc_len = base64_encode_length(der.length); + if (enc_len > 256) + assert(false, "PEM encode: DER input too large for stack buffer"); + char[256] b64_buf = void; + base64_encode(der, b64_buf[0 .. enc_len]); + + size_t pos = 0; + while (pos < enc_len) + { + size_t line_len = enc_len - pos; + if (line_len > 64) + line_len = 64; + result ~= b64_buf[pos .. pos + line_len]; + result ~= "\n"; + pos += line_len; + } + + result ~= "-----END "; + result ~= label; + result ~= "-----\n"; + return result; +} + +Array!ubyte decode_pem(const(char)[] data) +{ + import urt.string; + + size_t start = data.findFirst('\n'); + if (start == data.length) + return Array!ubyte(); + data = data[start .. $].trimFront; + + size_t end = data.findFirst("-----END"); + if (end == data.length) + return Array!ubyte(); + data = data[0 .. end].trimBack; + + if (data.length == 0) + return Array!ubyte(); + + // strip whitespace from base64 content + auto b64 = Array!char(Reserve, data.length); + for (size_t i = 0; i < data.length; ++i) + if (!data[i].is_whitespace) + b64 ~= data[i]; + + auto result = Array!ubyte(Alloc, base64_decode_length(b64.length)); + ptrdiff_t decoded_len = base64_decode(b64[], result[]); + if (decoded_len < 0) + return Array!ubyte(); + + result.resize(decoded_len); + return result; +} diff --git a/src/urt/crypto/pki.d b/src/urt/crypto/pki.d index b5bc061..1413b9d 100644 --- a/src/urt/crypto/pki.d +++ b/src/urt/crypto/pki.d @@ -1,7 +1,9 @@ module urt.crypto.pki; import urt.array; -import urt.encoding; +import urt.crypto.der; +import urt.crypto.pem; +import urt.digest.sha : SHA256Context, sha_init, sha_update, sha_finalise; import urt.mem; import urt.result; import urt.string; @@ -9,33 +11,20 @@ import urt.time; nothrow @nogc: -//version = DebugPKI; - -enum KeyType -{ - rsa2048, - ecdsa_p256, -} struct KeyPair { nothrow @nogc: version (Windows) { - // CAPI handles (RSA) - HCRYPTPROV hprov; - HCRYPTKEY hkey; - // CNG handles (ECDSA) BCRYPT_ALG_HANDLE halg; BCRYPT_KEY_HANDLE hcng; } - KeyType type; - bool valid() const pure { version (Windows) - return hprov != 0 || hcng !is null; + return hcng !is null; else return false; } @@ -48,6 +37,7 @@ nothrow @nogc: { PCCERT_CONTEXT context; HCERTSTORE store; + NCRYPT_KEY_HANDLE hncrypt; // persisted NCrypt key for SChannel (set by associate_key) } bool valid() const pure @@ -60,44 +50,32 @@ nothrow @nogc: } -Result generate_keypair(ref KeyPair kp, KeyType type) +Result generate_keypair(out KeyPair kp) { version (Windows) { - DWORD prov_type; - ALG_ID alg; - DWORD flags; - - final switch (type) - { - case KeyType.rsa2048: - prov_type = PROV_RSA_AES; - alg = AT_KEYEXCHANGE; - flags = (2048 << 16) | CRYPT_EXPORTABLE; - break; - - case KeyType.ecdsa_p256: - return generate_ecdsa_p256(kp); - } + NTSTATUS status = BCryptOpenAlgorithmProvider(&kp.halg, BCRYPT_ECDSA_P256_ALGORITHM.ptr, null, 0); + if (status != 0) + return Result(cast(uint)status); - // Use a named container (not CRYPT_VERIFYCONTEXT) so private key is - // accessible for signing operations like CSR generation. - if (!CryptAcquireContextA(&kp.hprov, "openwatt".ptr, null, prov_type, CRYPT_NEWKEYSET)) + status = BCryptGenerateKeyPair(kp.halg, &kp.hcng, 256, 0); + if (status != 0) { - // Container already exists, open it - if (!CryptAcquireContextA(&kp.hprov, "openwatt".ptr, null, prov_type, 0)) - return getlasterror_result(); + BCryptCloseAlgorithmProvider(kp.halg, 0); + kp.halg = null; + return Result(cast(uint)status); } - if (!CryptGenKey(kp.hprov, alg, flags, &kp.hkey)) + status = BCryptFinalizeKeyPair(kp.hcng, 0); + if (status != 0) { - auto r = getlasterror_result(); - CryptReleaseContext(kp.hprov, 0); - kp.hprov = 0; - return r; + BCryptDestroyKey(kp.hcng); + kp.hcng = null; + BCryptCloseAlgorithmProvider(kp.halg, 0); + kp.halg = null; + return Result(cast(uint)status); } - kp.type = KeyType.rsa2048; return Result.success; } else version (Posix) @@ -110,85 +88,210 @@ void free_keypair(ref KeyPair kp) { version (Windows) { - if (kp.hkey != 0) - CryptDestroyKey(kp.hkey); - if (kp.hprov != 0) - CryptReleaseContext(kp.hprov, 0); if (kp.hcng !is null) BCryptDestroyKey(kp.hcng); if (kp.halg !is null) BCryptCloseAlgorithmProvider(kp.halg, 0); - kp.hkey = 0; - kp.hprov = 0; kp.hcng = null; kp.halg = null; } } -Result create_self_signed(ref CertRef cert, ref KeyPair key, const(char)[] cn, uint validity_days = 365) +Result create_self_signed(ref KeyPair key, out CertRef cert, const(char)[] cn, const(char)[] hostname = null, uint validity_days = 365) { version (Windows) - { - if (!key.valid) - return InternalResult.invalid_parameter; + return create_self_signed_win32(key, cert, cn, hostname, validity_days); + else + return create_self_signed_portable(key, cert, cn, hostname, validity_days); +} - char[256] cn_buf = void; - if (cn.length + 3 >= cn_buf.length) - return InternalResult.buffer_too_small; - cn_buf[0 .. 3] = "CN="; - cn_buf[3 .. 3 + cn.length] = cn[]; - cn_buf[3 + cn.length] = 0; +private Result create_self_signed_win32(ref KeyPair key, out CertRef cert, const(char)[] cn, const(char)[] hostname, uint validity_days = 365) +{ + version (Windows) + { + // create a persisted NCrypt ECDSA P-256 key (SChannel requires persisted keys) + NCRYPT_PROV_HANDLE hprov; + SECURITY_STATUS ss = NCryptOpenStorageProvider(&hprov, MS_KEY_STORAGE_PROVIDER.ptr, 0); + if (ss != 0) + return Result(cast(uint)ss); + + wchar[48] name_buf = void; + cast(void)generate_key_name(name_buf[]); + + NCRYPT_KEY_HANDLE hncrypt; + ss = NCryptCreatePersistedKey(hprov, &hncrypt, BCRYPT_ECDSA_P256_ALGORITHM.ptr, name_buf.ptr, 0, NCRYPT_OVERWRITE_KEY_FLAG); + NCryptFreeObject(hprov); + if (ss != 0) + return Result(cast(uint)ss); + + ss = NCryptFinalizeKey(hncrypt, 0); + if (ss != 0) + { + NCryptFreeObject(hncrypt); + return Result(cast(uint)ss); + } - ubyte[256] name_buf = void; - DWORD name_size = name_buf.sizeof; - if (!CertStrToNameA(X509_ASN_ENCODING, cn_buf.ptr, CERT_X500_NAME_STR, null, name_buf.ptr, &name_size, null)) - return getlasterror_result(); + // encode subject name as DER + ubyte[128] name_der = void; + ptrdiff_t der_len = der_name_cn(name_der[], cn); + if (der_len <= 0) + { + NCryptDeleteKey(hncrypt, 0); + return InternalResult.data_error; + } CERT_NAME_BLOB subject_blob; - subject_blob.cbData = name_size; - subject_blob.pbData = name_buf.ptr; - - CRYPT_KEY_PROV_INFO key_prov_info; - key_prov_info.pwszContainerName = null; - key_prov_info.pwszProvName = null; - key_prov_info.dwProvType = PROV_RSA_AES; - key_prov_info.dwKeySpec = AT_KEYEXCHANGE; - - SYSTEMTIME start_time = void, end_time = void; - GetSystemTime(&start_time); - end_time = start_time; - - FILETIME ft_start = void; - SystemTimeToFileTime(&start_time, &ft_start); - ulong ticks = *cast(ulong*)&ft_start; - ticks += cast(ulong)validity_days * 24 * 60 * 60 * 10_000_000; // 100ns ticks per day - FILETIME ft_end = *cast(FILETIME*)&ticks; - FileTimeToSystemTime(&ft_end, &end_time); - - PCCERT_CONTEXT ctx = CertCreateSelfSignCertificate( - key.hprov, - &subject_blob, - 0, - &key_prov_info, - null, - &start_time, - &end_time, - null - ); + subject_blob.cbData = cast(DWORD)der_len; + subject_blob.pbData = name_der.ptr; - if (ctx is null) - return getlasterror_result(); + // build SAN extension (Chrome requires SAN) + ubyte[512] san_der = void; + size_t pos = 2; // skip SEQUENCE header, fill in later + + void add_dns_name(scope const(char)[] name) + { + san_der[pos++] = 0x82; // dNSName [2] implicit + san_der[pos++] = cast(ubyte)name.length; + san_der[pos .. pos + name.length] = cast(const(ubyte)[])name[]; + pos += name.length; + } + + void add_ip(scope const(ubyte)[] addr) + { + san_der[pos++] = 0x87; // iPAddress [7] implicit + san_der[pos++] = cast(ubyte)addr.length; + san_der[pos .. pos + addr.length] = addr[]; + pos += addr.length; + } + + // CN as dNSName + add_dns_name(cn); + // localhost + loopback + add_dns_name("localhost"); + add_ip([127, 0, 0, 1]); + add_ip([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); // ::1 + + // hostname.local (mDNS) + if (hostname.length > 0 && hostname.length + 6 < 128) + { + san_der[pos++] = 0x82; + san_der[pos++] = cast(ubyte)(hostname.length + 6); + san_der[pos .. pos + hostname.length] = cast(const(ubyte)[])hostname[]; + pos += hostname.length; + san_der[pos .. pos + 6] = cast(const(ubyte)[])".local"; + pos += 6; + } + + // TODO: add iPAddress SAN entries for all IP addresses the server is bound to. + // ...needs the server's bound addresses passed in or enumerated here! + + // SEQUENCE wrapper + san_der[0] = 0x30; + san_der[1] = cast(ubyte)(pos - 2); + DWORD san_len = cast(DWORD)pos; + + CERT_EXTENSION san_ext; + san_ext.pszObjId = cast(LPSTR)"2.5.29.17".ptr; // szOID_SUBJECT_ALT_NAME2 + san_ext.fCritical = FALSE; + san_ext.Value.cbData = san_len; + san_ext.Value.pbData = san_der.ptr; + + CERT_EXTENSIONS exts; + exts.cExtension = 1; + exts.rgExtension = &san_ext; + + // CertCreateSelfSignCertificate handles key association correctly for SChannel + auto pctx = CertCreateSelfSignCertificate(hncrypt, &subject_blob, 0, null, null, null, null, &exts); + if (pctx is null) + { + auto r = getlasterror_result(); + NCryptDeleteKey(hncrypt, 0); + return r; + } + + cert.context = pctx; + cert.store = null; + cert.hncrypt = hncrypt; - cert.context = ctx; return Result.success; } - else version (Posix) - assert(false, "TODO: mbedtls"); else - static assert(0, "Not implemented"); + assert(false); } -Result load_certificate(ref CertRef cert, const(ubyte)[] cert_data) +private Result create_self_signed_portable(ref KeyPair key, out CertRef cert, const(char)[] cn, const(char)[] hostname, uint validity_days = 365) +{ + if (!key.valid) + return InternalResult.invalid_parameter; + + Array!ubyte pub_x, pub_y; + auto r = export_public_key_raw(key, pub_x, pub_y); + if (!r) + return r; + + auto now = getSysTime(); + auto not_before = getDateTime(now); + auto not_after = getDateTime(now + dur!"days"(validity_days)); + + // compute TBSCertificate field sizes + ptrdiff_t ver_inner = der_integer_small(null, 2); + ptrdiff_t ver = der_header(null, 0xa0, ver_inner) + ver_inner; + ptrdiff_t serial = der_integer_small(null, 1); + ptrdiff_t sig_alg_size = der_sig_alg(null); + ptrdiff_t issuer = der_name_cn(null, cn); + ptrdiff_t val_inner = der_utctime(null, not_before) + der_utctime(null, not_after); + ptrdiff_t validity = der_header(null, 0x30, val_inner) + val_inner; + ptrdiff_t subject = der_name_cn(null, cn); + ptrdiff_t pubkey = der_ec_pubkey_info(null, pub_x[], pub_y[]); + size_t tbs_content = ver + serial + sig_alg_size + issuer + validity + subject + pubkey; + size_t tbs_total = der_header(null, 0x30, tbs_content) + tbs_content; + + // write TBSCertificate + ubyte[512] tbs_buf = void; + size_t pos = 0; + pos += der_header(tbs_buf[pos .. $], 0x30, tbs_content); + pos += der_header(tbs_buf[pos .. $], 0xa0, ver_inner); + pos += der_integer_small(tbs_buf[pos .. $], 2); + pos += der_integer_small(tbs_buf[pos .. $], 1); + pos += der_sig_alg(tbs_buf[pos .. $]); + pos += der_name_cn(tbs_buf[pos .. $], cn); + pos += der_header(tbs_buf[pos .. $], 0x30, val_inner); + pos += der_utctime(tbs_buf[pos .. $], not_before); + pos += der_utctime(tbs_buf[pos .. $], not_after); + pos += der_name_cn(tbs_buf[pos .. $], cn); + pos += der_ec_pubkey_info(tbs_buf[pos .. $], pub_x[], pub_y[]); + + // hash and sign + SHA256Context sha; + sha_init(sha); + sha_update(sha, tbs_buf[0 .. tbs_total]); + ubyte[32] hash = sha_finalise(sha); + + Array!ubyte sig; + r = sign_hash(key, hash[], sig); + if (!r) + return r; + + // Certificate: SEQUENCE { tbs, sigAlgId, BIT STRING { ecdsa_sig } } + ptrdiff_t sig_der = der_ecdsa_sig(null, sig[]); + size_t bs_content = 1 + sig_der; // unused_bits + ecdsa_sig + size_t cert_content = tbs_total + sig_alg_size + 1 + der_length_size(bs_content) + bs_content; + size_t cert_total = der_header(null, 0x30, cert_content) + cert_content; + + ubyte[640] cert_buf = void; + pos = 0; + pos += der_header(cert_buf[pos .. $], 0x30, cert_content); + cert_buf[pos .. pos + tbs_total] = tbs_buf[0 .. tbs_total]; + pos += tbs_total; + pos += der_sig_alg(cert_buf[pos .. $]); + pos += der_header(cert_buf[pos .. $], 0x03, bs_content); + cert_buf[pos++] = 0x00; // unused bits + pos += der_ecdsa_sig(cert_buf[pos .. $], sig[]); + + return cert_buf[0 .. cert_total].load_certificate(cert); +} + +Result load_certificate(const(ubyte)[] cert_data, out CertRef cert) { version (Windows) { @@ -198,28 +301,20 @@ Result load_certificate(ref CertRef cert, const(ubyte)[] cert_data) const(ubyte)[] der = cert_data; Array!ubyte decoded; - if (is_pem(cert_data)) + if (is_pem(cast(const(char)[])cert_data)) { - decoded = decode_pem(cert_data); + decoded = decode_pem(cast(const(char)[])cert_data); if (decoded.length == 0) return InternalResult.data_error; der = decoded[]; } - cert.store = CertOpenStore( - CERT_STORE_PROV_MEMORY, - 0, 0, 0, null - ); + cert.store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, null); if (cert.store is null) return getlasterror_result(); - if (!CertAddEncodedCertificateToStore( - cert.store, - X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - der.ptr, - cast(DWORD)der.length, - CERT_STORE_ADD_REPLACE_EXISTING, - cast(PCCERT_CONTEXT*)&cert.context)) + if (!CertAddEncodedCertificateToStore(cert.store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + der.ptr, cast(DWORD)der.length, CERT_STORE_ADD_REPLACE_EXISTING, cast(PCCERT_CONTEXT*)&cert.context)) { auto r = getlasterror_result(); CertCloseStore(cert.store, 0); @@ -235,102 +330,133 @@ Result load_certificate(ref CertRef cert, const(ubyte)[] cert_data) static assert(0, "Not implemented"); } -Result load_private_key(ref KeyPair kp, const(ubyte)[] key_data) +Result associate_key(ref CertRef cert, ref KeyPair key) { version (Windows) { - if (key_data.length == 0) + if (!cert.valid || !key.valid) return InternalResult.invalid_parameter; - const(ubyte)[] der = key_data; - Array!ubyte decoded; + // already associated (e.g., by CertCreateSelfSignCertificate) + if (cert.hncrypt !is null) + return Result.success; - if (is_pem(key_data)) + // SChannel requires the private key to be a named, persisted NCrypt key. + // Ephemeral BCrypt/NCrypt keys cause SEC_E_NO_CREDENTIALS (0x8009030E). + // Strategy: BCrypt blob → NCrypt temp (for PKCS8 export) → PKCS8 re-import + // with key name → persisted key → CERT_KEY_PROV_INFO_PROP_ID so SChannel + // can locate the key by provider + name. + + NCRYPT_PROV_HANDLE hprov; + SECURITY_STATUS ss = NCryptOpenStorageProvider(&hprov, MS_KEY_STORAGE_PROVIDER.ptr, 0); + if (ss != 0) + return Result(cast(uint)ss); + + // export BCrypt key as blob + ULONG blob_size = 0; + NTSTATUS status = BCryptExportKey(key.hcng, null, BCRYPT_ECCPRIVATE_BLOB.ptr, null, 0, &blob_size, 0); + if (status != 0) { - decoded = decode_pem(key_data); - if (decoded.length == 0) - return InternalResult.data_error; - der = decoded[]; + NCryptFreeObject(hprov); + return Result(cast(uint)status); } - DWORD blob_size = 0; - // try PKCS#1 first, fall back to PKCS#8 - if (!CryptDecodeObjectEx( - X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - PKCS_RSA_PRIVATE_KEY, - der.ptr, - cast(DWORD)der.length, - 0, - null, - null, - &blob_size)) + auto blob = Array!ubyte(Alloc, blob_size); + status = BCryptExportKey(key.hcng, null, BCRYPT_ECCPRIVATE_BLOB.ptr, blob.ptr, blob_size, &blob_size, 0); + if (status != 0) { - // Try PKCS#8 wrapper - if (!CryptDecodeObjectEx( - X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - PKCS_PRIVATE_KEY_INFO, - der.ptr, - cast(DWORD)der.length, - 0, - null, - null, - &blob_size)) - return getlasterror_result(); + NCryptFreeObject(hprov); + return Result(cast(uint)status); } - auto blob_buf = Array!ubyte(Alloc, blob_size); - if (!CryptDecodeObjectEx( - X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - PKCS_RSA_PRIVATE_KEY, - der.ptr, - cast(DWORD)der.length, - 0, - null, - blob_buf.ptr, - &blob_size)) - return getlasterror_result(); + // import BCrypt blob into NCrypt (unfinalized so we can set export policy) + NCRYPT_KEY_HANDLE htemp; + ss = NCryptImportKey(hprov, null, BCRYPT_ECCPRIVATE_BLOB.ptr, null, &htemp, blob.ptr, blob_size, NCRYPT_DO_NOT_FINALIZE_FLAG); + if (ss != 0) + { + NCryptFreeObject(hprov); + return Result(cast(uint)ss); + } - if (!CryptAcquireContextA(&kp.hprov, "openwatt".ptr, null, PROV_RSA_AES, CRYPT_NEWKEYSET)) + // allow plaintext export so we can re-export as PKCS8 + DWORD export_policy = NCRYPT_ALLOW_EXPORT_FLAG | NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG; + ss = NCryptSetProperty(htemp, NCRYPT_EXPORT_POLICY_PROPERTY.ptr, cast(ubyte*)&export_policy, DWORD.sizeof, 0); + if (ss != 0) { - if (!CryptAcquireContextA(&kp.hprov, "openwatt".ptr, null, PROV_RSA_AES, 0)) - return getlasterror_result(); + NCryptFreeObject(htemp); + NCryptFreeObject(hprov); + return Result(cast(uint)ss); } - if (!CryptImportKey(kp.hprov, blob_buf.ptr, blob_size, 0, CRYPT_EXPORTABLE, &kp.hkey)) + ss = NCryptFinalizeKey(htemp, 0); + if (ss != 0) { - auto r = getlasterror_result(); - CryptReleaseContext(kp.hprov, 0); - kp.hprov = 0; - return r; + NCryptFreeObject(htemp); + NCryptFreeObject(hprov); + return Result(cast(uint)ss); } - return Result.success; - } - else version (Posix) - assert(false, "TODO: mbedtls"); - else - static assert(0, "Not implemented"); -} + // export as PKCS8 — only PKCS8 supports named key import for persistence + ULONG pkcs8_size = 0; + ss = NCryptExportKey(htemp, null, NCRYPT_PKCS8_PRIVATE_KEY_BLOB.ptr, null, null, 0, &pkcs8_size, 0); + if (ss != 0) + { + NCryptFreeObject(htemp); + NCryptFreeObject(hprov); + return Result(cast(uint)ss); + } -Result associate_key(ref CertRef cert, ref KeyPair key) -{ - version (Windows) - { - if (!cert.valid || !key.valid) - return InternalResult.invalid_parameter; + auto pkcs8 = Array!ubyte(Alloc, pkcs8_size); + ss = NCryptExportKey(htemp, null, NCRYPT_PKCS8_PRIVATE_KEY_BLOB.ptr, null, pkcs8.ptr, pkcs8_size, &pkcs8_size, 0); + NCryptFreeObject(htemp); + if (ss != 0) + { + NCryptFreeObject(hprov); + return Result(cast(uint)ss); + } - CRYPT_KEY_PROV_INFO key_prov_info; - key_prov_info.pwszContainerName = null; - key_prov_info.pwszProvName = null; - key_prov_info.dwProvType = PROV_RSA_AES; - key_prov_info.dwKeySpec = AT_KEYEXCHANGE; - - if (!CertSetCertificateContextProperty( - cert.context, - CERT_KEY_PROV_INFO_PROP_ID, - 0, - &key_prov_info)) - return getlasterror_result(); + // re-import PKCS8 with key name to create a persisted key + wchar[48] name_buf = void; + size_t name_len = generate_key_name(name_buf[]); + + NCryptBuffer nbuf; + nbuf.cbBuffer = cast(uint)((name_len + 1) * 2); // include null terminator, in bytes + nbuf.BufferType = NCRYPTBUFFER_PKCS_KEY_NAME; + nbuf.pvBuffer = cast(void*)name_buf.ptr; + + NCryptBufferDesc params; + params.ulVersion = 0; // NCRYPTBUFFER_VERSION + params.cBuffers = 1; + params.pBuffers = &nbuf; + + NCRYPT_KEY_HANDLE hkey; + ss = NCryptImportKey(hprov, null, NCRYPT_PKCS8_PRIVATE_KEY_BLOB.ptr, ¶ms, &hkey, pkcs8.ptr, pkcs8_size, NCRYPT_OVERWRITE_KEY_FLAG); + NCryptFreeObject(hprov); + if (ss != 0) + return Result(cast(uint)ss); + + // clean up any previously associated NCrypt key + if (cert.hncrypt !is null) + NCryptDeleteKey(cert.hncrypt, 0); + cert.hncrypt = hkey; + + // tell SChannel where to find the key: provider name + key name (CNG mode) + CRYPT_KEY_PROV_INFO prov_info; + prov_info.pwszContainerName = name_buf.ptr; + prov_info.pwszProvName = cast(wchar*)MS_KEY_STORAGE_PROVIDER.ptr; + prov_info.dwProvType = 0; // CNG key storage provider + prov_info.dwFlags = 0; + prov_info.cProvParam = 0; + prov_info.rgProvParam = null; + prov_info.dwKeySpec = 0; + + if (!CertSetCertificateContextProperty(cert.context, CERT_KEY_PROV_INFO_PROP_ID, 0, &prov_info)) + { + auto r = getlasterror_result(); + NCryptDeleteKey(cert.hncrypt, 0); + cert.hncrypt = null; + return r; + } return Result.success; } @@ -344,10 +470,13 @@ void free_cert(ref CertRef cert) { version (Windows) { + if (cert.hncrypt !is null) + NCryptDeleteKey(cert.hncrypt, 0); if (cert.context !is null) CertFreeCertificateContext(cert.context); if (cert.store !is null) CertCloseStore(cert.store, 0); + cert.hncrypt = null; cert.context = null; cert.store = null; } @@ -365,16 +494,16 @@ SysTime cert_expiry(ref const CertRef cert) return SysTime(); } -void* native_cert_context(ref CertRef cert) +inout(void)* native_cert_context(ref inout CertRef cert) { version (Windows) - return cast(void*)cert.context; + return cast(inout(void)*)cert.context; else return null; } -Result sign_hash(ref KeyPair kp, const(ubyte)[] hash, ref Array!ubyte signature) +Result sign_hash(ref KeyPair kp, const(ubyte)[] hash, out Array!ubyte signature) { version (Windows) { @@ -382,16 +511,12 @@ Result sign_hash(ref KeyPair kp, const(ubyte)[] hash, ref Array!ubyte signature) return InternalResult.invalid_parameter; ULONG sig_size = 0; - NTSTATUS status = BCryptSignHash(kp.hcng, null, - cast(PUCHAR)hash.ptr, cast(ULONG)hash.length, - null, 0, &sig_size, 0); + NTSTATUS status = BCryptSignHash(kp.hcng, null, cast(PUCHAR)hash.ptr, cast(ULONG)hash.length, null, 0, &sig_size, 0); if (status != 0) return Result(cast(uint)status); signature = Array!ubyte(Alloc, sig_size); - status = BCryptSignHash(kp.hcng, null, - cast(PUCHAR)hash.ptr, cast(ULONG)hash.length, - signature.ptr, sig_size, &sig_size, 0); + status = BCryptSignHash(kp.hcng, null, cast(PUCHAR)hash.ptr, cast(ULONG)hash.length, signature.ptr, sig_size, &sig_size, 0); if (status != 0) { signature = Array!ubyte(); @@ -407,7 +532,7 @@ Result sign_hash(ref KeyPair kp, const(ubyte)[] hash, ref Array!ubyte signature) static assert(0, "Not implemented"); } -Result export_public_key_raw(ref KeyPair kp, ref Array!ubyte x, ref Array!ubyte y) +Result export_public_key_raw(ref KeyPair kp, out Array!ubyte x, out Array!ubyte y) { version (Windows) { @@ -415,14 +540,12 @@ Result export_public_key_raw(ref KeyPair kp, ref Array!ubyte x, ref Array!ubyte return InternalResult.invalid_parameter; ULONG blob_size = 0; - NTSTATUS status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPUBLIC_BLOB.ptr, - null, 0, &blob_size, 0); + NTSTATUS status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPUBLIC_BLOB.ptr, null, 0, &blob_size, 0); if (status != 0) return Result(cast(uint)status); auto blob = Array!ubyte(Alloc, blob_size); - status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPUBLIC_BLOB.ptr, - blob.ptr, blob_size, &blob_size, 0); + status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPUBLIC_BLOB.ptr, blob.ptr, blob_size, &blob_size, 0); if (status != 0) return Result(cast(uint)status); @@ -447,152 +570,62 @@ Result export_public_key_raw(ref KeyPair kp, ref Array!ubyte x, ref Array!ubyte Array!ubyte generate_csr(ref KeyPair kp, const(char)[] cn) { - version (Windows) - { - if (!kp.valid) - return Array!ubyte(); - - char[256] cn_buf = void; - if (cn.length + 3 >= cn_buf.length) - return Array!ubyte(); - cn_buf[0 .. 3] = "CN="; - cn_buf[3 .. 3 + cn.length] = cn[]; - cn_buf[3 + cn.length] = 0; - - ubyte[256] name_buf = void; - DWORD name_size = name_buf.sizeof; - if (!CertStrToNameA(X509_ASN_ENCODING, cn_buf.ptr, CERT_X500_NAME_STR, null, name_buf.ptr, &name_size, null)) - return Array!ubyte(); - - CERT_NAME_BLOB subject_blob; - subject_blob.cbData = name_size; - subject_blob.pbData = name_buf.ptr; - - CERT_REQUEST_INFO req_info; - req_info.dwVersion = CERT_REQUEST_V1; - req_info.Subject = subject_blob; - - DWORD pub_info_size = 0; - if (!CryptExportPublicKeyInfo(kp.hprov, AT_KEYEXCHANGE, X509_ASN_ENCODING, null, &pub_info_size)) - return Array!ubyte(); - auto pub_info_buf = Array!ubyte(Alloc, pub_info_size); - if (!CryptExportPublicKeyInfo(kp.hprov, AT_KEYEXCHANGE, X509_ASN_ENCODING, - cast(CERT_PUBLIC_KEY_INFO*)pub_info_buf.ptr, &pub_info_size)) - return Array!ubyte(); - req_info.SubjectPublicKeyInfo = *cast(CERT_PUBLIC_KEY_INFO*)pub_info_buf.ptr; - - CRYPT_ALGORITHM_IDENTIFIER sig_alg; - sig_alg.pszObjId = cast(LPSTR)szOID_RSA_SHA256RSA.ptr; - - DWORD csr_size = 0; - if (!CryptSignAndEncodeCertificate(kp.hprov, AT_KEYEXCHANGE, - X509_ASN_ENCODING, X509_CERT_REQUEST_TO_BE_SIGNED, - &req_info, &sig_alg, null, null, &csr_size)) - return Array!ubyte(); - - auto csr = Array!ubyte(Alloc, csr_size); - if (!CryptSignAndEncodeCertificate(kp.hprov, AT_KEYEXCHANGE, - X509_ASN_ENCODING, X509_CERT_REQUEST_TO_BE_SIGNED, - &req_info, &sig_alg, null, csr.ptr, &csr_size)) - return Array!ubyte(); - - csr.resize(csr_size); - return csr; - } - else version (Posix) - assert(false, "TODO: mbedtls"); - else - static assert(0, "Not implemented"); -} - - -Result export_private_key(ref KeyPair kp, ref Array!ubyte der_out) -{ - version (Windows) - { - if (kp.hkey == 0) - return InternalResult.invalid_parameter; - - DWORD blob_size = 0; - if (!CryptExportKey(kp.hkey, 0, PRIVATEKEYBLOB, 0, null, &blob_size)) - return getlasterror_result(); - - auto blob = Array!ubyte(Alloc, blob_size); - if (!CryptExportKey(kp.hkey, 0, PRIVATEKEYBLOB, 0, blob.ptr, &blob_size)) - return getlasterror_result(); - - // PRIVATEKEYBLOB → PKCS#1 DER - DWORD der_size = 0; - if (!CryptEncodeObjectEx( - X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - PKCS_RSA_PRIVATE_KEY, - blob.ptr, - 0, - null, - null, - &der_size)) - return getlasterror_result(); - - der_out = Array!ubyte(Alloc, der_size); - if (!CryptEncodeObjectEx( - X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - PKCS_RSA_PRIVATE_KEY, - blob.ptr, - 0, - null, - der_out.ptr, - &der_size)) - { - auto r = getlasterror_result(); - der_out = Array!ubyte(); - return r; - } - - der_out.resize(der_size); - return Result.success; - } - else version (Posix) - assert(false, "TODO: mbedtls"); - else - static assert(0, "Not implemented"); -} - -Array!ubyte encode_pem(const(ubyte)[] der, const(char)[] label) -{ - import urt.encoding : base64_encode, base64_encode_length; - - Array!ubyte result; + if (!kp.valid) + return Array!ubyte(); - // Header - result.concat(cast(const(ubyte)[])"-----BEGIN "); - result.concat(cast(const(ubyte)[])label); - result.concat(cast(const(ubyte)[])"-----\n"); + Array!ubyte pub_x, pub_y; + if (!export_public_key_raw(kp, pub_x, pub_y)) + return Array!ubyte(); - // Base64 body in 64-char lines - size_t enc_len = base64_encode_length(der.length); - auto b64 = Array!char(Alloc, enc_len); - base64_encode(der, b64[0 .. enc_len]); + // compute CertificationRequestInfo field sizes + ptrdiff_t ver = der_integer_small(null, 0); + ptrdiff_t subject = der_name_cn(null, cn); + ptrdiff_t pubkey = der_ec_pubkey_info(null, pub_x[], pub_y[]); + ptrdiff_t attrs = der_header(null, 0xa0, 0); // empty attributes [0] + size_t info_content = ver + subject + pubkey + attrs; + size_t info_total = der_header(null, 0x30, info_content) + info_content; + // write CertificationRequestInfo + ubyte[512] info_buf = void; size_t pos = 0; - while (pos < enc_len) - { - size_t line_len = enc_len - pos; - if (line_len > 64) - line_len = 64; - result.concat(cast(const(ubyte)[])b64[pos .. pos + line_len]); - result.concat(cast(const(ubyte)[])"\n"); - pos += line_len; - } - - // Footer - result.concat(cast(const(ubyte)[])"-----END "); - result.concat(cast(const(ubyte)[])label); - result.concat(cast(const(ubyte)[])"-----\n"); + pos += der_header(info_buf[pos .. $], 0x30, info_content); + pos += der_integer_small(info_buf[pos .. $], 0); + pos += der_name_cn(info_buf[pos .. $], cn); + pos += der_ec_pubkey_info(info_buf[pos .. $], pub_x[], pub_y[]); + pos += der_header(info_buf[pos .. $], 0xa0, 0); + + // hash and sign + SHA256Context sha; + sha_init(sha); + sha_update(sha, info_buf[0 .. info_total]); + ubyte[32] hash = sha_finalise(sha); + + Array!ubyte sig; + if (!sign_hash(kp, hash[], sig)) + return Array!ubyte(); - return result; + // CertificationRequest: SEQUENCE { info, sigAlgId, BIT STRING { ecdsa_sig } } + ptrdiff_t sig_alg_size = der_sig_alg(null); + ptrdiff_t sig_der = der_ecdsa_sig(null, sig[]); + size_t bs_content = 1 + sig_der; + size_t csr_content = info_total + sig_alg_size + 1 + der_length_size(bs_content) + bs_content; + size_t csr_total = der_header(null, 0x30, csr_content) + csr_content; + + auto csr = Array!ubyte(Alloc, csr_total); + pos = 0; + pos += der_header(csr[pos .. $], 0x30, csr_content); + csr.ptr[pos .. pos + info_total] = info_buf[0 .. info_total]; + pos += info_total; + pos += der_sig_alg(csr[pos .. $]); + pos += der_header(csr[pos .. $], 0x03, bs_content); + csr.ptr[pos++] = 0x00; + pos += der_ecdsa_sig(csr[pos .. $], sig[]); + + return csr; } -Result export_ecdsa_private_key(ref KeyPair kp, ref Array!ubyte blob_out) + +Result export_private_key(ref KeyPair kp, out Array!ubyte key_out) { version (Windows) { @@ -600,21 +633,19 @@ Result export_ecdsa_private_key(ref KeyPair kp, ref Array!ubyte blob_out) return InternalResult.invalid_parameter; ULONG blob_size = 0; - NTSTATUS status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPRIVATE_BLOB.ptr, - null, 0, &blob_size, 0); + NTSTATUS status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPRIVATE_BLOB.ptr, null, 0, &blob_size, 0); if (status != 0) return Result(cast(uint)status); - blob_out = Array!ubyte(Alloc, blob_size); - status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPRIVATE_BLOB.ptr, - blob_out.ptr, blob_size, &blob_size, 0); + key_out = Array!ubyte(Alloc, blob_size); + status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPRIVATE_BLOB.ptr, key_out.ptr, blob_size, &blob_size, 0); if (status != 0) { - blob_out.clear(); + key_out.clear(); return Result(cast(uint)status); } - blob_out.resize(blob_size); + key_out.resize(blob_size); return Result.success; } else version (Posix) @@ -623,44 +654,26 @@ Result export_ecdsa_private_key(ref KeyPair kp, ref Array!ubyte blob_out) static assert(0, "Not implemented"); } -Result import_ecdsa_private_key(ref KeyPair kp, const(ubyte)[] blob_data) +Result import_private_key(const(ubyte)[] key_data, out KeyPair kp) { version (Windows) { - NTSTATUS status = BCryptOpenAlgorithmProvider(&kp.halg, BCRYPT_ECDSA_P256_ALGORITHM.ptr, null, 0); - if (status != 0) - return Result(cast(uint)status); + const(ubyte)[] der = key_data; + Array!ubyte decoded; - status = BCryptImportKeyPair(kp.halg, null, BCRYPT_ECCPRIVATE_BLOB.ptr, - &kp.hcng, cast(ubyte*)blob_data.ptr, cast(ULONG)blob_data.length, 0); - if (status != 0) + if (is_pem(cast(const(char)[])key_data)) { - BCryptCloseAlgorithmProvider(kp.halg, 0); - kp.halg = null; - return Result(cast(uint)status); + decoded = decode_pem(cast(const(char)[])key_data); + if (decoded.length == 0) + return InternalResult.data_error; + der = decoded[]; } - kp.type = KeyType.ecdsa_p256; - return Result.success; - } - else version (Posix) - assert(false, "TODO: mbedtls"); - else - static assert(0, "Not implemented"); -} - - -private: - -Result generate_ecdsa_p256(ref KeyPair kp) -{ - version (Windows) - { NTSTATUS status = BCryptOpenAlgorithmProvider(&kp.halg, BCRYPT_ECDSA_P256_ALGORITHM.ptr, null, 0); if (status != 0) return Result(cast(uint)status); - status = BCryptGenerateKeyPair(kp.halg, &kp.hcng, 256, 0); + status = BCryptImportKeyPair(kp.halg, null, BCRYPT_ECCPRIVATE_BLOB.ptr, &kp.hcng, cast(ubyte*)der.ptr, cast(ULONG)der.length, 0); if (status != 0) { BCryptCloseAlgorithmProvider(kp.halg, 0); @@ -668,17 +681,6 @@ Result generate_ecdsa_p256(ref KeyPair kp) return Result(cast(uint)status); } - status = BCryptFinalizeKeyPair(kp.hcng, 0); - if (status != 0) - { - BCryptDestroyKey(kp.hcng); - kp.hcng = null; - BCryptCloseAlgorithmProvider(kp.halg, 0); - kp.halg = null; - return Result(cast(uint)status); - } - - kp.type = KeyType.ecdsa_p256; return Result.success; } else version (Posix) @@ -687,60 +689,20 @@ Result generate_ecdsa_p256(ref KeyPair kp) static assert(0, "Not implemented"); } -bool is_pem(const(ubyte)[] data) -{ - return data.length >= 11 && (cast(const(char)[])data[0 .. 11]) == "-----BEGIN "; -} - -Array!ubyte decode_pem(const(ubyte)[] data) -{ - auto text = cast(const(char)[])data; - - // Find end of first line (header) - size_t start = 0; - while (start < text.length && text[start] != '\n') - ++start; - if (start < text.length) - ++start; // skip \n - - // Find "-----END" marker - size_t end = start; - while (end + 5 < text.length) - { - if (text[end .. end + 5] == "-----") - break; - ++end; - } - - if (end <= start) - return Array!ubyte(); - // Strip whitespace and decode base64 - // First pass: count non-whitespace characters - size_t b64_len = 0; - for (size_t i = start; i < end; ++i) - { - if (text[i] != '\r' && text[i] != '\n' && text[i] != ' ') - ++b64_len; - } +private: - // Second pass: copy to contiguous buffer and decode - auto b64_buf = Array!char(Alloc, b64_len); - size_t j = 0; - for (size_t i = start; i < end; ++i) +version (Windows) +{ + size_t generate_key_name(wchar[] buf) { - if (text[i] != '\r' && text[i] != '\n' && text[i] != ' ') - b64_buf.ptr[j++] = text[i]; + import urt.mem.temp : tconcat; + import urt.string.uni : uni_convert; + __gshared uint counter = 0; + size_t len = uni_convert(tconcat("openwatt_key_", counter++), buf); + buf[len] = 0; + return len; } - - // Decode - auto result = Array!ubyte(Alloc, base64_decode_length(b64_len)); - ptrdiff_t decoded_len = base64_decode(b64_buf[], result[]); - if (decoded_len < 0) - return Array!ubyte(); - - result.resize(decoded_len); - return result; } version (Windows) @@ -749,89 +711,63 @@ version (Windows) import core.sys.windows.ntdef : NTSTATUS; import core.sys.windows.wincrypt; import core.sys.windows.windef; - import core.sys.windows.winbase; - pragma(lib, "Advapi32"); pragma(lib, "Bcrypt"); pragma(lib, "Crypt32"); + pragma(lib, "Ncrypt"); + + alias SECURITY_STATUS = int; + alias NCRYPT_PROV_HANDLE = void*; + alias NCRYPT_KEY_HANDLE = void*; + + enum NCRYPT_OVERWRITE_KEY_FLAG = 0x00000080; + enum NCRYPT_DO_NOT_FINALIZE_FLAG = 0x00000400; + enum NCRYPT_ALLOW_EXPORT_FLAG = 0x00000001; + enum NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG = 0x00000002; + enum DWORD CERT_KEY_PROV_INFO_PROP_ID = 2; + enum DWORD NCRYPTBUFFER_PKCS_KEY_NAME = 45; - // Constants not in D runtime enum LPCSTR CERT_STORE_PROV_MEMORY = cast(LPCSTR)2; enum DWORD CERT_STORE_ADD_REPLACE_EXISTING = 3; - enum DWORD CERT_X500_NAME_STR = 3; - enum DWORD CERT_KEY_PROV_INFO_PROP_ID = 2; - enum LPCSTR PKCS_RSA_PRIVATE_KEY = cast(LPCSTR)43; - enum LPCSTR PKCS_PRIVATE_KEY_INFO = cast(LPCSTR)44; - // Structs not in D runtime + struct CERT_EXTENSIONS + { + DWORD cExtension; + PCERT_EXTENSION rgExtension; + } + + immutable wchar[] MS_KEY_STORAGE_PROVIDER = "Microsoft Software Key Storage Provider\0"w; + immutable wchar[] NCRYPT_PKCS8_PRIVATE_KEY_BLOB = "PKCS8_PRIVATEKEY\0"w; + immutable wchar[] NCRYPT_EXPORT_POLICY_PROPERTY = "Export Policy\0"w; + + struct CRYPT_KEY_PROV_INFO { - LPWSTR pwszContainerName; - LPWSTR pwszProvName; + wchar* pwszContainerName; + wchar* pwszProvName; DWORD dwProvType; DWORD dwFlags; DWORD cProvParam; - void* rgProvParam; // CRYPT_KEY_PROV_PARAM* + void* rgProvParam; DWORD dwKeySpec; } - // CSR-related structs and constants - struct CERT_REQUEST_INFO + struct NCryptBuffer { - DWORD dwVersion; - CERT_NAME_BLOB Subject; - CERT_PUBLIC_KEY_INFO SubjectPublicKeyInfo; - DWORD cAttribute; - void* rgAttribute; // CRYPT_ATTRIBUTE* + ULONG cbBuffer; + ULONG BufferType; + void* pvBuffer; } - enum CERT_REQUEST_V1 = 0; - enum LPCSTR X509_CERT_REQUEST_TO_BE_SIGNED = cast(LPCSTR)4; - enum szOID_RSA_SHA256RSA = "1.2.840.113549.1.1.11"; + struct NCryptBufferDesc + { + ULONG ulVersion; + ULONG cBuffers; + NCryptBuffer* pBuffers; + } - // Functions not in D runtime extern (Windows) @nogc nothrow { - BOOL CryptExportPublicKeyInfo( - HCRYPTPROV hCryptProv, - DWORD dwKeySpec, - DWORD dwCertEncodingType, - CERT_PUBLIC_KEY_INFO* pInfo, - DWORD* pcbInfo - ); - - BOOL CryptSignAndEncodeCertificate( - HCRYPTPROV hCryptProv, - DWORD dwKeySpec, - DWORD dwCertEncodingType, - LPCSTR lpszStructType, - const(void)* pvStructInfo, - CRYPT_ALGORITHM_IDENTIFIER* pSignatureAlgorithm, - const(void)* pvHashAuxInfo, - BYTE* pbEncoded, - DWORD* pcbEncoded - ); - PCCERT_CONTEXT CertCreateSelfSignCertificate( - HCRYPTPROV hCryptProvOrNCryptKey, - PCERT_NAME_BLOB pSubjectIssuerBlob, - DWORD dwFlags, - CRYPT_KEY_PROV_INFO* pKeyProvParam, - CRYPT_ALGORITHM_IDENTIFIER* pSignatureAlgorithm, - SYSTEMTIME* pStartTime, - SYSTEMTIME* pEndTime, - void* pExtensions // PCERT_EXTENSIONS - ); - - BOOL CertStrToNameA( - DWORD dwCertEncodingType, - LPCSTR pszX500, - DWORD dwStrType, - void* pvReserved, - BYTE* pbEncoded, - DWORD* pcbEncoded, - LPCSTR* ppszError - ); - BOOL CertAddEncodedCertificateToStore( HCERTSTORE hCertStore, DWORD dwCertEncodingType, @@ -848,25 +784,74 @@ version (Windows) const(void)* pvData ); - BOOL CryptDecodeObjectEx( - DWORD dwCertEncodingType, - LPCSTR lpszStructType, - const(BYTE)* pbEncoded, - DWORD cbEncoded, - DWORD dwFlags, - void* pDecodePara, // PCRYPT_DECODE_PARA - void* pvStructInfo, - DWORD* pcbStructInfo + SECURITY_STATUS NCryptOpenStorageProvider( + NCRYPT_PROV_HANDLE* phProvider, + const(wchar)* pszProviderName, + DWORD dwFlags ); - BOOL CryptEncodeObjectEx( - DWORD dwCertEncodingType, - LPCSTR lpszStructType, - const(void)* pvStructInfo, + SECURITY_STATUS NCryptImportKey( + NCRYPT_PROV_HANDLE hProvider, + NCRYPT_KEY_HANDLE hImportKey, + const(wchar)* pszBlobType, + void* pParameterList, // NCryptBufferDesc* + NCRYPT_KEY_HANDLE* phKey, + BYTE* pbData, + DWORD cbData, + DWORD dwFlags + ); + + SECURITY_STATUS NCryptFreeObject( + NCRYPT_PROV_HANDLE hObject + ); + + SECURITY_STATUS NCryptSetProperty( + NCRYPT_KEY_HANDLE hObject, + const(wchar)* pszProperty, + ubyte* pbInput, + DWORD cbInput, + DWORD dwFlags + ); + + SECURITY_STATUS NCryptFinalizeKey( + NCRYPT_KEY_HANDLE hKey, + DWORD dwFlags + ); + + SECURITY_STATUS NCryptExportKey( + NCRYPT_KEY_HANDLE hKey, + NCRYPT_KEY_HANDLE hExportKey, + const(wchar)* pszBlobType, + void* pParameterList, + BYTE* pbOutput, + DWORD cbOutput, + DWORD* pcbResult, + DWORD dwFlags + ); + + SECURITY_STATUS NCryptCreatePersistedKey( + NCRYPT_PROV_HANDLE hProvider, + NCRYPT_KEY_HANDLE* phKey, + const(wchar)* pszAlgId, + const(wchar)* pszKeyName, + DWORD dwLegacyKeySpec, + DWORD dwFlags + ); + + SECURITY_STATUS NCryptDeleteKey( + NCRYPT_KEY_HANDLE hKey, + DWORD dwFlags + ); + + PCCERT_CONTEXT CertCreateSelfSignCertificate( + NCRYPT_KEY_HANDLE hCryptProvOrNCryptKey, + CERT_NAME_BLOB* pSubjectIssuerBlob, DWORD dwFlags, - void* pEncodePara, // PCRYPT_ENCODE_PARA - void* pvEncoded, - DWORD* pcbEncoded + void* pKeyProvInfo, // PCRYPT_KEY_PROV_INFO + void* pSignatureAlgorithm, // PCRYPT_ALGORITHM_IDENTIFIER + void* pStartTime, // PSYSTEMTIME + void* pEndTime, // PSYSTEMTIME + void* pExtensions // PCERT_EXTENSIONS ); } } From 2881ebe968e65949a5d9a104fd0327308e575ed1 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 12 Mar 2026 00:02:24 +1000 Subject: [PATCH 100/138] Implement mbedtls version of the crypto utilities. --- .github/workflows/ci.yml | 6 +- Makefile | 1 + src/urt/crypto/pki.d | 560 ++++++++++++++++++++++++++++++------- src/urt/internal/mbedtls.c | 114 ++++++++ src/urt/internal/mbedtls.d | 203 ++++++++++++++ src/urt/mem/package.d | 4 + 6 files changed, 781 insertions(+), 107 deletions(-) create mode 100644 src/urt/internal/mbedtls.c create mode 100644 src/urt/internal/mbedtls.d diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9e8ec1..9ab0698 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,10 +41,14 @@ jobs: with: compiler: ${{ matrix.compiler == 'dmd' && 'dmd-master' || matrix.compiler }} + # Linux needs mbedtls for TLS/crypto support + - name: Install mbedtls (Linux) + if: ${{ matrix.os == 'ubuntu' }} + run: sudo apt-get update && sudo apt-get install -y libmbedtls-dev # 32-bit linux needs libs to link and run tests... - name: Install 32-bit Toolchains (Linux) if: ${{ matrix.os == 'ubuntu' && matrix.platform == 'x86' }} - run: sudo apt-get update && sudo apt-get install -y gcc-multilib + run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install -y gcc-multilib libmbedtls-dev:i386 - name: Install 32-bit ARM Toolchains (Linux) if: ${{ matrix.os == 'ubuntu' && matrix.platform == 'arm' }} run: sudo dpkg --add-architecture armhf && sudo apt-get update && sudo apt-get install -y libstdc++6:armhf diff --git a/Makefile b/Makefile index e74677b..c48f93b 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ DEPFILE := $(OBJDIR)/$(TARGETNAME).d DFLAGS := $(DFLAGS) -preview=bitfields -preview=rvaluerefparam -preview=nosharedaccess -preview=in SOURCES := $(shell find "$(SRCDIR)" -type f -name '*.d') +SOURCES := $(SOURCES) $(SRCDIR)/urt/internal/mbedtls.c # Set target file based on build type and OS ifeq ($(BUILD_TYPE),exe) diff --git a/src/urt/crypto/pki.d b/src/urt/crypto/pki.d index 1413b9d..470a9c3 100644 --- a/src/urt/crypto/pki.d +++ b/src/urt/crypto/pki.d @@ -20,13 +20,19 @@ nothrow @nogc: BCRYPT_ALG_HANDLE halg; BCRYPT_KEY_HANDLE hcng; } + else version (Posix) + { + mbedtls_pk_context pk; + } + else + static assert(false, "TODO"); bool valid() const pure { version (Windows) return hcng !is null; - else - return false; + else version (Posix) + return pk.pk_info !is null; } } @@ -39,11 +45,17 @@ nothrow @nogc: HCERTSTORE store; NCRYPT_KEY_HANDLE hncrypt; // persisted NCrypt key for SChannel (set by associate_key) } + else version (Posix) + { + mbedtls_x509_crt* crt; // heap-allocated via urt_x509_crt_new + } bool valid() const pure { version (Windows) return context !is null; + else version (Posix) + return crt !is null; else return false; } @@ -79,7 +91,18 @@ Result generate_keypair(out KeyPair kp) return Result.success; } else version (Posix) - assert(false, "TODO: mbedtls"); + { + mbedtls_pk_init(&kp.pk); + int ret = urt_pk_gen_ec_p256_key(&kp.pk, &mbedtls_ctr_drbg_random, get_rng()); + if (ret != 0) + { + mbedtls_pk_free(&kp.pk); + kp.pk = mbedtls_pk_context.init; + return Result(cast(uint)ret); + } + + return Result.success; + } else static assert(0, "Not implemented"); } @@ -95,6 +118,11 @@ void free_keypair(ref KeyPair kp) kp.hcng = null; kp.halg = null; } + else version (Posix) + { + mbedtls_pk_free(&kp.pk); + kp.pk = mbedtls_pk_context.init; + } } Result create_self_signed(ref KeyPair key, out CertRef cert, const(char)[] cn, const(char)[] hostname = null, uint validity_days = 365) @@ -325,7 +353,37 @@ Result load_certificate(const(ubyte)[] cert_data, out CertRef cert) return Result.success; } else version (Posix) - assert(false, "TODO: mbedtls"); + { + if (cert_data.length == 0) + return InternalResult.invalid_parameter; + + cert.crt = urt_x509_crt_new(); + if (cert.crt is null) + return InternalResult.invalid_parameter; + + // mbedtls_x509_crt_parse handles both PEM and DER. + // for PEM, the buffer must be null-terminated. + int ret; + if (is_pem(cast(const(char)[])cert_data)) + { + // add null terminator for mbedtls PEM parser + auto pem_buf = Array!ubyte(Alloc, cert_data.length + 1); + pem_buf.ptr[0 .. cert_data.length] = cert_data[]; + pem_buf.ptr[cert_data.length] = 0; + ret = mbedtls_x509_crt_parse(cert.crt, pem_buf.ptr, pem_buf.length); + } + else + ret = mbedtls_x509_crt_parse_der(cert.crt, cert_data.ptr, cert_data.length); + + if (ret != 0) + { + urt_x509_crt_delete(cert.crt); + cert.crt = null; + return Result(cast(uint)ret); + } + + return Result.success; + } else static assert(0, "Not implemented"); } @@ -461,7 +519,13 @@ Result associate_key(ref CertRef cert, ref KeyPair key) return Result.success; } else version (Posix) - assert(false, "TODO: mbedtls"); + { + // no-op: mbedtls doesn't require key-to-cert binding in a system store. + // the cert and key are passed separately to mbedtls_ssl_conf_own_cert. + if (!cert.valid || !key.valid) + return InternalResult.invalid_parameter; + return Result.success; + } else static assert(0, "Not implemented"); } @@ -480,6 +544,12 @@ void free_cert(ref CertRef cert) cert.context = null; cert.store = null; } + else version (Posix) + { + if (cert.crt !is null) + urt_x509_crt_delete(cert.crt); + cert.crt = null; + } } SysTime cert_expiry(ref const CertRef cert) @@ -490,6 +560,11 @@ SysTime cert_expiry(ref const CertRef cert) return SysTime(); return SysTime(*cast(ulong*)&cert.context.pCertInfo.NotAfter); } + else version (Posix) + { + // TODO: parse cert.crt.valid_to (mbedtls_x509_time) and convert to SysTime + return SysTime(); + } else return SysTime(); } @@ -498,6 +573,8 @@ inout(void)* native_cert_context(ref inout CertRef cert) { version (Windows) return cast(inout(void)*)cert.context; + else version (Posix) + return cast(inout(void)*)cert.crt; else return null; } @@ -527,7 +604,30 @@ Result sign_hash(ref KeyPair kp, const(ubyte)[] hash, out Array!ubyte signature) return Result.success; } else version (Posix) - assert(false, "TODO: mbedtls"); + { + if (!kp.valid) + return InternalResult.invalid_parameter; + + // mbedtls_pk_sign produces DER-encoded ECDSA signature. + // we need raw R||S format (64 bytes for P-256) to match the Windows contract. + ubyte[256] sig_buf = void; + size_t sig_len = 0; + int ret = urt_pk_sign(&kp.pk, + hash.ptr, hash.length, sig_buf.ptr, sig_buf.length, &sig_len, + &mbedtls_ctr_drbg_random, get_rng()); + if (ret != 0) + return Result(cast(uint)ret); + + // parse DER SEQUENCE { INTEGER r, INTEGER s } → raw R||S (32 bytes each) + signature = Array!ubyte(Alloc, 64); + if (!der_sig_to_raw(sig_buf[0 .. sig_len], signature.ptr[0 .. 64])) + { + signature = Array!ubyte(); + return InternalResult.data_error; + } + + return Result.success; + } else static assert(0, "Not implemented"); } @@ -562,7 +662,26 @@ Result export_public_key_raw(ref KeyPair kp, out Array!ubyte x, out Array!ubyte return Result.success; } else version (Posix) - assert(false, "TODO: mbedtls"); + { + if (!kp.valid) + return InternalResult.invalid_parameter; + + // export uncompressed point: 0x04 || X || Y + ubyte[65] pt_buf = void; // 1 + 32 + 32 for P-256 + size_t olen = 0; + int ret = urt_pk_export_pubkey_xy(&kp.pk, pt_buf.ptr, pt_buf.length, &olen); + if (ret != 0) + return Result(cast(uint)ret); + + if (olen != 65) // 0x04 + 32 + 32 + return InternalResult.data_error; + + x = Array!ubyte(Alloc, 32); + y = Array!ubyte(Alloc, 32); + x.ptr[0 .. 32] = pt_buf[1 .. 33]; + y.ptr[0 .. 32] = pt_buf[33 .. 65]; + return Result.success; + } else static assert(0, "Not implemented"); } @@ -632,24 +751,44 @@ Result export_private_key(ref KeyPair kp, out Array!ubyte key_out) if (kp.hcng is null) return InternalResult.invalid_parameter; + // Export BCrypt blob: BCRYPT_ECCKEY_BLOB { magic, cbKey=32 } X[32] Y[32] d[32] ULONG blob_size = 0; NTSTATUS status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPRIVATE_BLOB.ptr, null, 0, &blob_size, 0); if (status != 0) return Result(cast(uint)status); - key_out = Array!ubyte(Alloc, blob_size); - status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPRIVATE_BLOB.ptr, key_out.ptr, blob_size, &blob_size, 0); + auto blob = Array!ubyte(Alloc, blob_size); + status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPRIVATE_BLOB.ptr, blob.ptr, blob_size, &blob_size, 0); if (status != 0) - { - key_out.clear(); return Result(cast(uint)status); - } - key_out.resize(blob_size); - return Result.success; + if (blob_size < 8 + 32 * 3) + return InternalResult.data_error; + + // Build SEC 1 ECPrivateKey DER from the BCrypt blob components + return build_ec_sec1_der(blob[72 .. 104][0..32], blob[8 .. 40][0..32], blob[40 .. 72][0..32], key_out); } else version (Posix) - assert(false, "TODO: mbedtls"); + { + if (!kp.valid) + return InternalResult.invalid_parameter; + + // Extract raw d, X, Y from mbedtls pk context + ubyte[32] d = void; + ubyte[65] xy_buf = void; + size_t d_len = 0, xy_len = 0; + + int ret = urt_pk_export_privkey_d(&kp.pk, d.ptr, d.length, &d_len); + if (ret != 0) + return Result(cast(uint)ret); + + ret = urt_pk_export_pubkey_xy(&kp.pk, xy_buf.ptr, xy_buf.length, &xy_len); + if (ret != 0) + return Result(cast(uint)ret); + + // xy_buf is 0x04 || X[32] || Y[32] + return build_ec_sec1_der(d, xy_buf[1 .. 33], xy_buf[33 .. 65], key_out); + } else static assert(0, "Not implemented"); } @@ -669,40 +808,208 @@ Result import_private_key(const(ubyte)[] key_data, out KeyPair kp) der = decoded[]; } - NTSTATUS status = BCryptOpenAlgorithmProvider(&kp.halg, BCRYPT_ECDSA_P256_ALGORITHM.ptr, null, 0); - if (status != 0) - return Result(cast(uint)status); + ubyte[32] d = void, x = void, y = void; + if (!parse_ec_sec1_der(der, d, x, y)) + return InternalResult.data_error; - status = BCryptImportKeyPair(kp.halg, null, BCRYPT_ECCPRIVATE_BLOB.ptr, &kp.hcng, cast(ubyte*)der.ptr, cast(ULONG)der.length, 0); - if (status != 0) + // Build BCRYPT_ECCKEY_BLOB: { magic(4), cbKey(4)=32 } X[32] Y[32] d[32] + ubyte[104] blob = void; + (cast(uint[])blob[0 .. 8])[0] = 0x34534345; // BCRYPT_ECDSA_PRIVATE_P256_MAGIC + (cast(uint[])blob[0 .. 8])[1] = 32; // cbKey + blob[8 .. 40] = x[]; + blob[40 .. 72] = y[]; + blob[72 .. 104] = d[]; + + NTSTATUS status = BCryptOpenAlgorithmProvider(&kp.halg, BCRYPT_ECDSA_P256_ALGORITHM.ptr, null, 0); + if (status == 0) { + status = BCryptImportKeyPair(kp.halg, null, BCRYPT_ECCPRIVATE_BLOB.ptr, &kp.hcng, blob.ptr, 104, 0); + if (status == 0) + return Result.success; BCryptCloseAlgorithmProvider(kp.halg, 0); kp.halg = null; - return Result(cast(uint)status); + } + return Result(cast(uint)status); + } + else version (Posix) + { + const(ubyte)[] der = key_data; + Array!ubyte decoded; + + if (is_pem(cast(const(char)[])key_data)) + { + decoded = decode_pem(cast(const(char)[])key_data); + if (decoded.length == 0) + return InternalResult.data_error; + der = decoded[]; + } + + ubyte[32] d = void, x = void, y = void; + if (!parse_ec_sec1_der(der, d, x, y)) + return InternalResult.data_error; + + // Build uncompressed point: 0x04 || X || Y + ubyte[65] xy = void; + xy[0] = 0x04; + xy[1 .. 33] = x[]; + xy[33 .. 65] = y[]; + + mbedtls_pk_init(&kp.pk); + int ret = urt_pk_import_ec_p256_key(&kp.pk, d.ptr, d.length, xy.ptr, xy.length); + if (ret != 0) + { + mbedtls_pk_free(&kp.pk); + kp.pk = mbedtls_pk_context.init; + return Result(cast(uint)ret); } return Result.success; } - else version (Posix) - assert(false, "TODO: mbedtls"); else static assert(0, "Not implemented"); } - private: -version (Windows) +// Build SEC 1 ECPrivateKey DER (RFC 5915) for P-256 from raw components. +// Both platforms use this format for portable key storage. +// +// ECPrivateKey ::= SEQUENCE { +// INTEGER 1, +// OCTET STRING d[32], +// [0] { OID secp256r1 }, +// [1] { BIT STRING 04 || X || Y } +// } +Result build_ec_sec1_der(ref const ubyte[32] d, ref const ubyte[32] x, ref const ubyte[32] y, out Array!ubyte key_out) { - size_t generate_key_name(wchar[] buf) + // compute sizes (pass null to measure) + size_t ver_size = der_integer_small(null, 1); // INTEGER 1 + size_t d_size = der_tlv(null, 0x04, d[]); // OCTET STRING d + size_t oid_size = der_tlv(null, 0x06, oid_prime256v1[]); // OID secp256r1 + size_t params_size = der_header(null, 0xa0, oid_size) + oid_size; // [0] { OID } + + enum size_t pt_len = 1 + 32 + 32; // 04 || X || Y + enum size_t bs_content = 1 + pt_len; // unused_bits + point + size_t bs_size = der_header(null, 0x03, bs_content) + bs_content; // BIT STRING + size_t pubkey_size = der_header(null, 0xa1, bs_size) + bs_size; // [1] { BIT STRING } + + size_t content = ver_size + d_size + params_size + pubkey_size; + size_t total = der_header(null, 0x30, content) + content; // outer SEQUENCE + + key_out = Array!ubyte(Alloc, total); + ubyte[] buf = key_out[]; + size_t pos = 0; + + pos += der_header(buf[pos .. $], 0x30, content); + pos += der_integer_small(buf[pos .. $], 1); + pos += der_tlv(buf[pos .. $], 0x04, d[]); + + // [0] EXPLICIT { OID secp256r1 } + pos += der_header(buf[pos .. $], 0xa0, oid_size); + pos += der_tlv(buf[pos .. $], 0x06, oid_prime256v1[]); + + // [1] EXPLICIT { BIT STRING { 04 || X || Y } } + pos += der_header(buf[pos .. $], 0xa1, bs_size); + pos += der_header(buf[pos .. $], 0x03, bs_content); + buf[pos++] = 0x00; // unused bits + buf[pos++] = 0x04; // uncompressed point + buf[pos .. pos + 32] = x[]; + pos += 32; + buf[pos .. pos + 32] = y[]; + pos += 32; + + assert(pos == total); + return Result.success; +} + +// Parse SEC 1 ECPrivateKey DER (RFC 5915) for P-256, extracting raw d, X, Y. +bool parse_ec_sec1_der(const(ubyte)[] der, ref ubyte[32] d, ref ubyte[32] x, ref ubyte[32] y) +{ + if (der.length < 2 || der[0] != 0x30) + return false; + + size_t pos = 1; + size_t seq_len; + if (!read_der_length(der, pos, seq_len)) + return false; + if (pos + seq_len > der.length) + return false; + size_t seq_end = pos + seq_len; + + // INTEGER 1 (version) + if (pos + 3 > seq_end || der[pos] != 0x02 || der[pos + 1] != 0x01 || der[pos + 2] != 0x01) + return false; + pos += 3; + + // OCTET STRING (private key d) + if (pos >= seq_end || der[pos] != 0x04) + return false; + ++pos; + size_t d_len; + if (!read_der_length(der, pos, d_len)) + return false; + if (d_len == 0 || d_len > 32 || pos + d_len > seq_end) + return false; + d[] = 0; + d[32 - d_len .. 32] = der[pos .. pos + d_len]; // right-align + pos += d_len; + + // optional [0] parameters — skip + if (pos < seq_end && der[pos] == 0xa0) { - import urt.mem.temp : tconcat; - import urt.string.uni : uni_convert; - __gshared uint counter = 0; - size_t len = uni_convert(tconcat("openwatt_key_", counter++), buf); - buf[len] = 0; - return len; + ++pos; + size_t param_len; + if (!read_der_length(der, pos, param_len)) + return false; + pos += param_len; + } + + // optional [1] public key + if (pos < seq_end && der[pos] == 0xa1) + { + ++pos; + size_t ctx_len; + if (!read_der_length(der, pos, ctx_len)) + return false; + size_t ctx_end = pos + ctx_len; + // BIT STRING + if (pos >= ctx_end || der[pos] != 0x03) + return false; + ++pos; + size_t bs_len; + if (!read_der_length(der, pos, bs_len)) + return false; + // unused bits (0) + 04 + X + Y = 66 bytes + if (bs_len != 66 || pos + bs_len > ctx_end) + return false; + if (der[pos] != 0x00 || der[pos + 1] != 0x04) + return false; + x[] = der[pos + 2 .. pos + 34]; + y[] = der[pos + 34 .. pos + 66]; + return true; } + + // No public key in the SEC 1 structure — can't reconstruct without EC math + return false; +} + +bool read_der_length(const(ubyte)[] der, ref size_t pos, out size_t len) +{ + if (pos >= der.length) + return false; + ubyte b = der[pos++]; + if (b < 0x80) + { + len = b; + return true; + } + uint n = b & 0x7f; + if (n == 0 || n > 2 || pos + n > der.length) + return false; + len = 0; + for (uint i = 0; i < n; ++i) + len = (len << 8) | der[pos++]; + return true; } version (Windows) @@ -766,92 +1073,133 @@ version (Windows) NCryptBuffer* pBuffers; } - extern (Windows) @nogc nothrow + extern(Windows) @nogc nothrow { - BOOL CertAddEncodedCertificateToStore( - HCERTSTORE hCertStore, - DWORD dwCertEncodingType, - const(BYTE)* pbCertEncoded, - DWORD cbCertEncoded, - DWORD dwAddDisposition, - PCCERT_CONTEXT* ppCertContext - ); + BOOL CertAddEncodedCertificateToStore(HCERTSTORE hCertStore, DWORD dwCertEncodingType, const(BYTE)* pbCertEncoded, DWORD cbCertEncoded, DWORD dwAddDisposition, PCCERT_CONTEXT* ppCertContext); + BOOL CertSetCertificateContextProperty(PCCERT_CONTEXT pCertContext, DWORD dwPropId, DWORD dwFlags, const(void)* pvData); + SECURITY_STATUS NCryptOpenStorageProvider(NCRYPT_PROV_HANDLE* phProvider, const(wchar)* pszProviderName, DWORD dwFlags); + SECURITY_STATUS NCryptImportKey(NCRYPT_PROV_HANDLE hProvider, NCRYPT_KEY_HANDLE hImportKey, const(wchar)* pszBlobType, void* pParameterList, NCRYPT_KEY_HANDLE* phKey, BYTE* pbData, DWORD cbData, DWORD dwFlags); + SECURITY_STATUS NCryptFreeObject(NCRYPT_PROV_HANDLE hObject); + SECURITY_STATUS NCryptSetProperty(NCRYPT_KEY_HANDLE hObject, const(wchar)* pszProperty, ubyte* pbInput, DWORD cbInput, DWORD dwFlags); + SECURITY_STATUS NCryptFinalizeKey(NCRYPT_KEY_HANDLE hKey, DWORD dwFlags); + SECURITY_STATUS NCryptExportKey(NCRYPT_KEY_HANDLE hKey, NCRYPT_KEY_HANDLE hExportKey, const(wchar)* pszBlobType, void* pParameterList, BYTE* pbOutput, DWORD cbOutput, DWORD* pcbResult, DWORD dwFlags); + SECURITY_STATUS NCryptCreatePersistedKey(NCRYPT_PROV_HANDLE hProvider, NCRYPT_KEY_HANDLE* phKey, const(wchar)* pszAlgId, const(wchar)* pszKeyName, DWORD dwLegacyKeySpec, DWORD dwFlags); + SECURITY_STATUS NCryptDeleteKey(NCRYPT_KEY_HANDLE hKey, DWORD dwFlags); - BOOL CertSetCertificateContextProperty( - PCCERT_CONTEXT pCertContext, - DWORD dwPropId, + PCCERT_CONTEXT CertCreateSelfSignCertificate( + NCRYPT_KEY_HANDLE hCryptProvOrNCryptKey, + CERT_NAME_BLOB* pSubjectIssuerBlob, DWORD dwFlags, - const(void)* pvData + void* pKeyProvInfo, // PCRYPT_KEY_PROV_INFO + void* pSignatureAlgorithm, // PCRYPT_ALGORITHM_IDENTIFIER + void* pStartTime, // PSYSTEMTIME + void* pEndTime, // PSYSTEMTIME + void* pExtensions // PCERT_EXTENSIONS ); + } - SECURITY_STATUS NCryptOpenStorageProvider( - NCRYPT_PROV_HANDLE* phProvider, - const(wchar)* pszProviderName, - DWORD dwFlags - ); + size_t generate_key_name(wchar[] buf) + { + import urt.mem.temp : tconcat; + import urt.string.uni : uni_convert; + __gshared uint counter = 0; + size_t len = uni_convert(tconcat("openwatt_key_", counter++), buf); + buf[len] = 0; + return len; + } +} - SECURITY_STATUS NCryptImportKey( - NCRYPT_PROV_HANDLE hProvider, - NCRYPT_KEY_HANDLE hImportKey, - const(wchar)* pszBlobType, - void* pParameterList, // NCryptBufferDesc* - NCRYPT_KEY_HANDLE* phKey, - BYTE* pbData, - DWORD cbData, - DWORD dwFlags - ); +version (Posix) +{ + import urt.internal.mbedtls; - SECURITY_STATUS NCryptFreeObject( - NCRYPT_PROV_HANDLE hObject - ); + // lazily-initialized global CSPRNG for mbedtls operations + mbedtls_ctr_drbg_context* get_rng() + { + __gshared mbedtls_ctr_drbg_context* rng; + __gshared mbedtls_entropy_context* entropy; + __gshared bool initialized; - SECURITY_STATUS NCryptSetProperty( - NCRYPT_KEY_HANDLE hObject, - const(wchar)* pszProperty, - ubyte* pbInput, - DWORD cbInput, - DWORD dwFlags - ); + if (initialized) + return rng; - SECURITY_STATUS NCryptFinalizeKey( - NCRYPT_KEY_HANDLE hKey, - DWORD dwFlags - ); + entropy = urt_entropy_new(); + if (entropy is null) + return null; - SECURITY_STATUS NCryptExportKey( - NCRYPT_KEY_HANDLE hKey, - NCRYPT_KEY_HANDLE hExportKey, - const(wchar)* pszBlobType, - void* pParameterList, - BYTE* pbOutput, - DWORD cbOutput, - DWORD* pcbResult, - DWORD dwFlags - ); + rng = urt_ctr_drbg_new(); + if (rng is null) + { + urt_entropy_delete(entropy); + entropy = null; + return null; + } - SECURITY_STATUS NCryptCreatePersistedKey( - NCRYPT_PROV_HANDLE hProvider, - NCRYPT_KEY_HANDLE* phKey, - const(wchar)* pszAlgId, - const(wchar)* pszKeyName, - DWORD dwLegacyKeySpec, - DWORD dwFlags - ); + int ret = mbedtls_ctr_drbg_seed(rng, &mbedtls_entropy_func, cast(void*)entropy, null, 0); + if (ret != 0) + { + urt_ctr_drbg_delete(rng); + urt_entropy_delete(entropy); + rng = null; + entropy = null; + return null; + } - SECURITY_STATUS NCryptDeleteKey( - NCRYPT_KEY_HANDLE hKey, - DWORD dwFlags - ); + initialized = true; + return rng; + } - PCCERT_CONTEXT CertCreateSelfSignCertificate( - NCRYPT_KEY_HANDLE hCryptProvOrNCryptKey, - CERT_NAME_BLOB* pSubjectIssuerBlob, - DWORD dwFlags, - void* pKeyProvInfo, // PCRYPT_KEY_PROV_INFO - void* pSignatureAlgorithm, // PCRYPT_ALGORITHM_IDENTIFIER - void* pStartTime, // PSYSTEMTIME - void* pEndTime, // PSYSTEMTIME - void* pExtensions // PCERT_EXTENSIONS - ); + // convert DER-encoded ECDSA signature SEQUENCE { INTEGER r, INTEGER s } + // to raw R||S format (32 bytes each for P-256) + bool der_sig_to_raw(const(ubyte)[] der, ubyte[] raw) + { + if (raw.length < 64) + return false; + + raw[0 .. 64] = 0; + + size_t pos = 0; + + // SEQUENCE + if (pos >= der.length || der[pos++] != 0x30) + return false; + if (pos >= der.length) + return false; + + // sequence length (skip) + if (der[pos] & 0x80) + pos += 1 + (der[pos] & 0x7f); + else + ++pos; + + // parse two INTEGERs into raw[0..32] and raw[32..64] + foreach (i; 0 .. 2) + { + if (pos >= der.length || der[pos++] != 0x02) + return false; + if (pos >= der.length) + return false; + + size_t len = der[pos++]; + if (pos + len > der.length) + return false; + + auto integer = der[pos .. pos + len]; + pos += len; + + // strip leading zero padding (DER INTEGERs are signed, so positive values + // with high bit set get a 0x00 prefix) + while (integer.length > 32 && integer[0] == 0) + integer = integer[1 .. $]; + + if (integer.length > 32) + return false; + + // right-align into 32-byte field + size_t offset = 32 - integer.length; + raw[i * 32 + offset .. i * 32 + offset + integer.length] = integer[]; + } + + return true; } } diff --git a/src/urt/internal/mbedtls.c b/src/urt/internal/mbedtls.c new file mode 100644 index 0000000..35d185d --- /dev/null +++ b/src/urt/internal/mbedtls.c @@ -0,0 +1,114 @@ +// C wrappers for mbedtls — sizeof() for opaque types, and wrappers for +// functions that access internal struct layouts D cannot safely replicate. + +#if !defined(_WIN32) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +size_t urt_sizeof_entropy(void) { return sizeof(mbedtls_entropy_context); } +size_t urt_sizeof_ctr_drbg(void) { return sizeof(mbedtls_ctr_drbg_context); } +size_t urt_sizeof_x509_crt(void) { return sizeof(mbedtls_x509_crt); } +size_t urt_sizeof_ssl_context(void) { return sizeof(mbedtls_ssl_context); } +size_t urt_sizeof_ssl_config(void) { return sizeof(mbedtls_ssl_config); } + +// Generate an ECDSA P-256 key into a pk context. +// Handles pk_setup + ecp_gen_key internally so D never needs to access +// mbedtls_ecp_keypair or mbedtls_ecp_group, whose layouts are version-dependent. +int urt_pk_gen_ec_p256_key(mbedtls_pk_context *pk, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng) +{ + int ret = mbedtls_pk_setup(pk, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)); + if (ret != 0) + return ret; + // mbedtls_pk_ec() is deprecated in 3.1+ but present in all 2.x and 3.x versions. + return mbedtls_ecp_gen_key(MBEDTLS_ECP_DP_SECP256R1, mbedtls_pk_ec(*pk), f_rng, p_rng); +} + +// Export the public key as an uncompressed EC point (0x04 || X || Y). +// Returns 0 on success with *olen set to the number of bytes written. +// Uses mbedtls_pk_write_pubkey_der to avoid any direct ECP struct member access — +// the SubjectPublicKeyInfo for P-256 always ends with the 65-byte uncompressed point. +int urt_pk_export_pubkey_xy(mbedtls_pk_context *pk, unsigned char *buf, size_t buflen, size_t *olen) +{ + unsigned char der[256]; + int len = mbedtls_pk_write_pubkey_der(pk, der, sizeof(der)); + if (len < 0) + return len; + // SubjectPublicKeyInfo for P-256 ends with BIT STRING { 04 || X || Y } (65 bytes). + if (len < 65 || buflen < 65) + return MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL; + memcpy(buf, der + sizeof(der) - 65, 65); + *olen = 65; + return 0; +} + +// Wrappers for functions whose signatures changed between mbedtls 2.x and 3.x. +// mbedtls 3.x added sig_size parameter to mbedtls_pk_sign (inserted in the middle), +// and f_rng/p_rng to mbedtls_pk_parse_key (appended at end). +// Calling from D with the wrong signature corrupts arguments on the stack. + +// Always signs with SHA-256. The md_type_t enum values changed between +// mbedtls 2.x and 3.x, so we resolve the correct value here in C. +int urt_pk_sign(mbedtls_pk_context *ctx, const unsigned char *hash, size_t hash_len, unsigned char *sig, size_t sig_size, size_t *sig_len, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng) +{ +#if MBEDTLS_VERSION_MAJOR >= 3 + return mbedtls_pk_sign(ctx, MBEDTLS_MD_SHA256, hash, hash_len, sig, sig_size, sig_len, f_rng, p_rng); +#else + (void)sig_size; + return mbedtls_pk_sign(ctx, MBEDTLS_MD_SHA256, hash, hash_len, sig, sig_len, f_rng, p_rng); +#endif +} + +// Import raw EC P-256 key components (d, X, Y) into a pk context. +// Sets up the pk context, group, private key, and public key point. +int urt_pk_import_ec_p256_key(mbedtls_pk_context *pk, const unsigned char *d, size_t d_len, const unsigned char *xy, size_t xy_len) +{ + int ret = mbedtls_pk_setup(pk, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)); + if (ret != 0) + return ret; + + mbedtls_ecp_keypair *ec = mbedtls_pk_ec(*pk); + +#if MBEDTLS_VERSION_MAJOR >= 3 + ret = mbedtls_ecp_group_load(&ec->private_grp, MBEDTLS_ECP_DP_SECP256R1); + if (ret != 0) return ret; + ret = mbedtls_mpi_read_binary(&ec->private_d, d, d_len); + if (ret != 0) return ret; + // xy is 0x04 || X || Y (65 bytes) + ret = mbedtls_ecp_point_read_binary(&ec->private_grp, &ec->private_Q, xy, xy_len); +#else + ret = mbedtls_ecp_group_load(&ec->grp, MBEDTLS_ECP_DP_SECP256R1); + if (ret != 0) return ret; + ret = mbedtls_mpi_read_binary(&ec->d, d, d_len); + if (ret != 0) return ret; + ret = mbedtls_ecp_point_read_binary(&ec->grp, &ec->Q, xy, xy_len); +#endif + return ret; +} + +// Export the raw private key scalar d (32 bytes for P-256). +// Accesses version-dependent struct member (d vs private_d). +int urt_pk_export_privkey_d(mbedtls_pk_context *pk, unsigned char *buf, size_t buflen, size_t *olen) +{ + mbedtls_ecp_keypair *ec = mbedtls_pk_ec(*pk); +#if MBEDTLS_VERSION_MAJOR >= 3 + mbedtls_mpi *d = &ec->private_d; +#else + mbedtls_mpi *d = &ec->d; +#endif + size_t len = mbedtls_mpi_size(d); + if (buflen < len) + return MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL; + *olen = len; + return mbedtls_mpi_write_binary(d, buf, len); +} + +#endif diff --git a/src/urt/internal/mbedtls.d b/src/urt/internal/mbedtls.d new file mode 100644 index 0000000..5b3b869 --- /dev/null +++ b/src/urt/internal/mbedtls.d @@ -0,0 +1,203 @@ +// minimal D bindings for mbedtls — only what pki.d and tls.d need +module urt.internal.mbedtls; + +version (Posix): + +pragma(lib, "mbedtls"); +pragma(lib, "mbedx509"); +pragma(lib, "mbedcrypto"); + +nothrow @nogc: +extern(C): + +// --- PK (public key abstraction) --- + +struct mbedtls_pk_info_t; // opaque + +struct mbedtls_pk_context +{ + const(mbedtls_pk_info_t)* pk_info; + void* pk_ctx; +} + +// mbedtls_pk_type_t and mbedtls_md_type_t enum values changed between 2.x +// and 3.x, so D must not hardcode them. All operations that need these enums +// (pk_setup, pk_sign, etc.) are wrapped in urt/internal/mbedtls.c where the +// correct header values are resolved at compile time. + +void mbedtls_pk_init(mbedtls_pk_context* ctx); +void mbedtls_pk_free(mbedtls_pk_context* ctx); + +// --- wrappers from urt/internal/mbedtls.c --- +// These handle operations whose signatures changed between mbedtls 2.x and 3.x, +// as well as operations that access internal struct layouts. + +int urt_pk_gen_ec_p256_key(mbedtls_pk_context* pk, int function(void*, ubyte*, size_t) nothrow @nogc f_rng, void* p_rng); +int urt_pk_export_pubkey_xy(mbedtls_pk_context* pk, ubyte* buf, size_t buflen, size_t* olen); +int urt_pk_sign(mbedtls_pk_context* ctx, const(ubyte)* hash, size_t hash_len, ubyte* sig, size_t sig_size, size_t* sig_len, int function(void*, ubyte*, size_t) nothrow @nogc f_rng, void* p_rng); +int urt_pk_import_ec_p256_key(mbedtls_pk_context* pk, const(ubyte)* d, size_t d_len, const(ubyte)* xy, size_t xy_len); +int urt_pk_export_privkey_d(mbedtls_pk_context* pk, ubyte* buf, size_t buflen, size_t* olen); + + +// --- X.509 certificate --- + +// mbedtls_x509_crt is complex (~200+ bytes). We only use it through pointers +// allocated via urt/internal/mbedtls.c (urt_mbedtls_x509_crt_new/delete). +struct mbedtls_x509_crt; + +void mbedtls_x509_crt_init(mbedtls_x509_crt* crt); +void mbedtls_x509_crt_free(mbedtls_x509_crt* crt); +int mbedtls_x509_crt_parse_der(mbedtls_x509_crt* chain, const(ubyte)* buf, size_t buflen); +int mbedtls_x509_crt_parse(mbedtls_x509_crt* chain, const(ubyte)* buf, size_t buflen); + + +// --- Entropy & CTR-DRBG (random number generation) --- + +// These are large structs. Allocated via urt/internal/mbedtls.c (urt_mbedtls_entropy_new/delete, urt_mbedtls_ctr_drbg_new/delete). +struct mbedtls_entropy_context; +struct mbedtls_ctr_drbg_context; + +void mbedtls_entropy_init(mbedtls_entropy_context* ctx); +void mbedtls_entropy_free(mbedtls_entropy_context* ctx); +int mbedtls_entropy_func(void* data, ubyte* output, size_t len); + +void mbedtls_ctr_drbg_init(mbedtls_ctr_drbg_context* ctx); +void mbedtls_ctr_drbg_free(mbedtls_ctr_drbg_context* ctx); +int mbedtls_ctr_drbg_seed(mbedtls_ctr_drbg_context* ctx, int function(void*, ubyte*, size_t) nothrow @nogc f_entropy, void* p_entropy, const(ubyte)* custom, size_t len); +int mbedtls_ctr_drbg_random(void* p_rng, ubyte* output, size_t output_len); + + +// --- SSL/TLS --- + +struct mbedtls_ssl_context; +struct mbedtls_ssl_config; + +enum MBEDTLS_SSL_IS_CLIENT = 0; +enum MBEDTLS_SSL_IS_SERVER = 1; +enum MBEDTLS_SSL_TRANSPORT_STREAM = 0; +enum MBEDTLS_SSL_PRESET_DEFAULT = 0; + +enum MBEDTLS_ERR_SSL_WANT_READ = -0x6900; +enum MBEDTLS_ERR_SSL_WANT_WRITE = -0x6880; +enum MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY = -0x7880; + +void mbedtls_ssl_init(mbedtls_ssl_context* ssl); +void mbedtls_ssl_free(mbedtls_ssl_context* ssl); +int mbedtls_ssl_setup(mbedtls_ssl_context* ssl, const(mbedtls_ssl_config)* conf); +void mbedtls_ssl_set_bio(mbedtls_ssl_context* ssl, void* p_bio, int function(void*, const(ubyte)*, size_t) nothrow @nogc f_send, int function(void*, ubyte*, size_t) nothrow @nogc f_recv, void* f_recv_timeout); +int mbedtls_ssl_handshake(mbedtls_ssl_context* ssl); +int mbedtls_ssl_read(mbedtls_ssl_context* ssl, ubyte* buf, size_t len); +int mbedtls_ssl_write(mbedtls_ssl_context* ssl, const(ubyte)* buf, size_t len); +int mbedtls_ssl_close_notify(mbedtls_ssl_context* ssl); +int mbedtls_ssl_set_hostname(mbedtls_ssl_context* ssl, const(char)* hostname); + +void mbedtls_ssl_config_init(mbedtls_ssl_config* conf); +void mbedtls_ssl_config_free(mbedtls_ssl_config* conf); +int mbedtls_ssl_config_defaults(mbedtls_ssl_config* conf, int endpoint, int transport, int preset); +void mbedtls_ssl_conf_rng(mbedtls_ssl_config* conf, int function(void*, ubyte*, size_t) nothrow @nogc f_rng, void* p_rng); +void mbedtls_ssl_conf_ca_chain(mbedtls_ssl_config* conf, mbedtls_x509_crt* ca_chain, void* ca_crl); +int mbedtls_ssl_conf_own_cert(mbedtls_ssl_config* conf, mbedtls_x509_crt* own_cert, mbedtls_pk_context* pk_key); +void mbedtls_ssl_conf_authmode(mbedtls_ssl_config* conf, int authmode); + +alias mbedtls_ssl_conf_sni_cb = int function(void* p_info, mbedtls_ssl_context* ssl, const(ubyte)* name, size_t name_len) nothrow @nogc; +void mbedtls_ssl_conf_sni(mbedtls_ssl_config* conf, mbedtls_ssl_conf_sni_cb f_sni, void* p_sni); + +enum MBEDTLS_SSL_VERIFY_NONE = 0; +enum MBEDTLS_SSL_VERIFY_OPTIONAL = 1; +enum MBEDTLS_SSL_VERIFY_REQUIRED = 2; + + +// --- sizeof() from urt/internal/mbedtls.c (opaque types too complex to replicate) --- + +size_t urt_sizeof_entropy(); +size_t urt_sizeof_ctr_drbg(); +size_t urt_sizeof_x509_crt(); +size_t urt_sizeof_ssl_context(); +size_t urt_sizeof_ssl_config(); + +import urt.mem; + +mbedtls_entropy_context* urt_entropy_new() +{ + auto ctx = cast(mbedtls_entropy_context*)calloc(1, urt_sizeof_entropy()); + if (ctx) + mbedtls_entropy_init(ctx); + return ctx; +} + +void urt_entropy_delete(mbedtls_entropy_context* ctx) +{ + if (ctx) + { + mbedtls_entropy_free(ctx); + free(ctx); + } +} + +mbedtls_ctr_drbg_context* urt_ctr_drbg_new() +{ + auto ctx = cast(mbedtls_ctr_drbg_context*)calloc(1, urt_sizeof_ctr_drbg()); + if (ctx) + mbedtls_ctr_drbg_init(ctx); + return ctx; +} + +void urt_ctr_drbg_delete(mbedtls_ctr_drbg_context* ctx) +{ + if (ctx) + { + mbedtls_ctr_drbg_free(ctx); + free(ctx); + } +} + +mbedtls_x509_crt* urt_x509_crt_new() +{ + auto ctx = cast(mbedtls_x509_crt*)calloc(1, urt_sizeof_x509_crt()); + if (ctx) + mbedtls_x509_crt_init(ctx); + return ctx; +} + +void urt_x509_crt_delete(mbedtls_x509_crt* crt) +{ + if (crt) + { + mbedtls_x509_crt_free(crt); + free(crt); + } +} + +mbedtls_ssl_context* urt_ssl_new() +{ + auto ctx = cast(mbedtls_ssl_context*)calloc(1, urt_sizeof_ssl_context()); + if (ctx) + mbedtls_ssl_init(ctx); + return ctx; +} + +void urt_ssl_delete(mbedtls_ssl_context* ssl) +{ + if (ssl) + { + mbedtls_ssl_free(ssl); + free(ssl); + } +} + +mbedtls_ssl_config* urt_ssl_config_new() +{ + auto ctx = cast(mbedtls_ssl_config*)calloc(1, urt_sizeof_ssl_config()); + if (ctx) + mbedtls_ssl_config_init(ctx); + return ctx; +} + +void urt_ssl_config_delete(mbedtls_ssl_config* conf) +{ + if (conf) + { + mbedtls_ssl_config_free(conf); + free(conf); + } +} diff --git a/src/urt/mem/package.d b/src/urt/mem/package.d index 9a903c8..dfea791 100644 --- a/src/urt/mem/package.d +++ b/src/urt/mem/package.d @@ -11,6 +11,10 @@ extern(C) nothrow @nogc: void* alloca(size_t size); + void* calloc(size_t num, size_t size); + void* malloc(size_t size); + void free(void *ptr); + void* memcpy(void* dest, const void* src, size_t n) pure; void* memmove(void* dest, const void* src, size_t n) pure; void* memset(void* s, int c, size_t n) pure; From 8f619b9031f0e884c576b5e25c34f9caec4a61a3 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Mon, 2 Mar 2026 03:54:51 +1000 Subject: [PATCH 101/138] Stripped druntime for real! --- src/object.d | 1755 +++++++++ src/std/math.d | 8 + src/urt/atomic.d | 107 + src/urt/conv.d | 4 +- src/urt/dbg.d | 43 - src/urt/exception.d | 58 + src/urt/fibre.d | 2 +- src/urt/file.d | 35 +- src/urt/internal/aa.d | 1048 ++++++ src/urt/internal/bitop.d | 283 ++ src/urt/internal/exception.d | 3600 ++++++++++++++++++ src/urt/internal/lifetime.d | 2 +- src/urt/internal/os.c | 8 + src/urt/internal/stdc.c | 19 + src/urt/internal/sys/windows/basetsd.d | 141 + src/urt/internal/sys/windows/basetyps.d | 27 + src/urt/internal/sys/windows/ntdef.d | 85 + src/urt/internal/sys/windows/ntsecapi.d | 796 ++++ src/urt/internal/sys/windows/ntsecpkg.d | 445 +++ src/urt/internal/sys/windows/package.d | 13 + src/urt/internal/sys/windows/schannel.d | 106 + src/urt/internal/sys/windows/sdkddkver.d | 155 + src/urt/internal/sys/windows/security.d | 119 + src/urt/internal/sys/windows/sspi.d | 381 ++ src/urt/internal/sys/windows/subauth.d | 275 ++ src/urt/internal/sys/windows/w32api.d | 138 + src/urt/internal/sys/windows/winbase.d | 2941 +++++++++++++++ src/urt/internal/sys/windows/wincon.d | 316 ++ src/urt/internal/sys/windows/wincrypt.d | 902 +++++ src/urt/internal/sys/windows/windef.d | 151 + src/urt/internal/sys/windows/winerror.d | 2312 ++++++++++++ src/urt/internal/sys/windows/winnt.d | 4321 ++++++++++++++++++++++ src/urt/internal/sys/windows/winsock2.d | 766 ++++ src/urt/internal/sys/windows/winuser.d | 144 + src/urt/internal/traits.d | 1227 ++++++ src/urt/io.d | 84 +- src/urt/lifetime.d | 30 +- src/urt/math.d | 64 +- src/urt/mem/alloc.d | 10 +- src/urt/mem/allocator.d | 5 +- src/urt/mem/package.d | 185 +- src/urt/mem/string.d | 15 +- src/urt/package.d | 290 +- src/urt/result.d | 11 +- src/urt/socket.d | 11 +- src/urt/string/string.d | 4 +- src/urt/system.d | 6 +- src/urt/time.d | 4 +- src/urt/util.d | 119 +- 49 files changed, 23415 insertions(+), 156 deletions(-) create mode 100644 src/object.d create mode 100644 src/std/math.d create mode 100644 src/urt/atomic.d create mode 100644 src/urt/exception.d create mode 100644 src/urt/internal/aa.d create mode 100644 src/urt/internal/bitop.d create mode 100644 src/urt/internal/exception.d create mode 100644 src/urt/internal/stdc.c create mode 100644 src/urt/internal/sys/windows/basetsd.d create mode 100644 src/urt/internal/sys/windows/basetyps.d create mode 100644 src/urt/internal/sys/windows/ntdef.d create mode 100644 src/urt/internal/sys/windows/ntsecapi.d create mode 100644 src/urt/internal/sys/windows/ntsecpkg.d create mode 100644 src/urt/internal/sys/windows/package.d create mode 100644 src/urt/internal/sys/windows/schannel.d create mode 100644 src/urt/internal/sys/windows/sdkddkver.d create mode 100644 src/urt/internal/sys/windows/security.d create mode 100644 src/urt/internal/sys/windows/sspi.d create mode 100644 src/urt/internal/sys/windows/subauth.d create mode 100644 src/urt/internal/sys/windows/w32api.d create mode 100644 src/urt/internal/sys/windows/winbase.d create mode 100644 src/urt/internal/sys/windows/wincon.d create mode 100644 src/urt/internal/sys/windows/wincrypt.d create mode 100644 src/urt/internal/sys/windows/windef.d create mode 100644 src/urt/internal/sys/windows/winerror.d create mode 100644 src/urt/internal/sys/windows/winnt.d create mode 100644 src/urt/internal/sys/windows/winsock2.d create mode 100644 src/urt/internal/sys/windows/winuser.d create mode 100644 src/urt/internal/traits.d diff --git a/src/object.d b/src/object.d new file mode 100644 index 0000000..e739fa9 --- /dev/null +++ b/src/object.d @@ -0,0 +1,1755 @@ +// Minimal object.d — replaces druntime's implicit root module. +// +// This file is the auditing frontier: every symbol added here is a +// symbol the compiler or linker demanded. Keep it as small as possible. +module object; + +static assert(__VERSION__ >= 2112, + "uRT requires DMD frontend 2.112+ (DMD ≥2.112, LDC ≥1.42). " ~ + "Older frontends use TypeInfo-based AA hooks incompatible with uRT's template-based AAs."); + +// ────────────────────────────────────────────────────────────────────── +// Platform-dependent ABI flags (must match druntime's detection) +// ────────────────────────────────────────────────────────────────────── + +version (X86_64) +{ + version (DigitalMars) version = WithArgTypes; + else version (Windows) {} // Win64 ABI doesn't need argTypes + else version = WithArgTypes; +} +else version (AArch64) +{ + version (OSX) {} + else version (iOS) {} + else version (TVOS) {} + else version (WatchOS) {} + else version = WithArgTypes; +} + + +// ────────────────────────────────────────────────────────────────────── +// Fundamental type aliases (compiler hardcodes references to these) +// ────────────────────────────────────────────────────────────────────── + +alias size_t = typeof(int.sizeof); +alias ptrdiff_t = typeof(cast(void*) 0 - cast(void*) 0); +alias nullptr_t = typeof(null); +alias noreturn = typeof(*null); + +version (Windows) + alias wchar wchar_t; +else version (Posix) + alias dchar wchar_t; +else version (WASI) + alias dchar wchar_t; + +alias string = immutable(char)[]; +alias wstring = immutable(wchar)[]; +alias dstring = immutable(dchar)[]; + +alias hash_t = size_t; + +// Required by __importc_builtins.di for lazy module references in ImportC. +template imported(string name) +{ + mixin("import imported = " ~ name ~ ";"); +} + +// ────────────────────────────────────────────────────────────────────── +// Primitive tools +// ────────────────────────────────────────────────────────────────────── + +// TODO: move the functions here so object doesn't import these modules... +public import urt.lifetime : move, forward; +public import urt.meta : Alias, AliasSeq; +public import urt.util : min, max, swap; + +//alias Alias(alias a) = a; +//alias Alias(T) = T; +// +//alias AliasSeq(TList...) = TList; +// +//T min(T)(T a, T b) pure nothrow @nogc @safe +// => b < a ? b : a; +// +//T max(T)(T a, T b) pure nothrow @nogc @safe +// => b > a ? b : a; +// +//ref T swap(T)(ref T a, return ref T b) +//{ +// import urt.lifetime : move, moveEmplace; +// +// T t = a.move; +// b.move(a); +// t.move(b); +// return b; +//} +// +//T swap(T)(ref T a, T b) +//{ +// import urt.lifetime : move, moveEmplace; +// +// auto t = a.move; +// b.move(a); +// return t.move; +//} + + +// ────────────────────────────────────────────────────────────────────── +// Object — root of the class hierarchy +// ────────────────────────────────────────────────────────────────────── + +class Object +{ +@nogc: + size_t toHash() @trusted nothrow + { + size_t addr = cast(size_t) cast(void*) this; + return addr ^ (addr >>> 4); + } + + bool opEquals(Object rhs) + => this is rhs; + + int opCmp(Object rhs) + => 0; + + ptrdiff_t toString(char[] buffer) const nothrow + => try_copy_string(buffer, "Object"); +} + +// Free-function opEquals for class types — the compiler lowers `a == b` +// on class objects to a call to this function. +bool opEquals(LHS, RHS)(LHS lhs, RHS rhs) + if ((is(LHS : const Object) || is(LHS : const shared Object)) && + (is(RHS : const Object) || is(RHS : const shared Object))) +{ + static if (__traits(compiles, lhs.opEquals(rhs)) && __traits(compiles, rhs.opEquals(lhs))) + { + if (lhs is rhs) + return true; + if (lhs is null || rhs is null) + return false; + if (!lhs.opEquals(rhs)) + return false; + if (typeid(lhs) is typeid(rhs) || !__ctfe && typeid(lhs).opEquals(typeid(rhs))) + return true; + return rhs.opEquals(lhs); + } + else + return .opEquals!(Object, Object)(*cast(Object*) &lhs, *cast(Object*) &rhs); +} + +// ────────────────────────────────────────────────────────────────────── +// TypeInfo — compiler generates references for typeid, AAs, etc. +// ────────────────────────────────────────────────────────────────────── + +class TypeInfo +{ +@nogc: + size_t getHash(scope const void* p) @trusted nothrow const + => 0; + + bool equals(scope const void* p1, scope const void* p2) @trusted const + => p1 == p2; + + int compare(scope const void* p1, scope const void* p2) @trusted const + => 0; + + @property size_t tsize() nothrow pure const @safe @nogc + => 0; + + const(TypeInfo) next() nothrow pure const @nogc + => null; + + size_t[] offTi() nothrow const + => null; + + @property uint flags() nothrow pure const @safe @nogc + => 0; + + @property size_t talign() nothrow pure const @safe @nogc + => tsize; + + const(void)[] initializer() nothrow pure const @safe @nogc + => null; + + @property immutable(void)* rtInfo() nothrow pure const @safe @nogc + => null; + + version (WithArgTypes) + { + int argTypes(out TypeInfo arg1, out TypeInfo arg2) @safe nothrow + { + arg1 = null; + arg2 = null; + return 0; + } + } + + override size_t toHash() @trusted nothrow const + => 0; + + override bool opEquals(Object rhs) + => false; + + override int opCmp(Object rhs) + => 0; + + override ptrdiff_t toString(char[] buffer) const nothrow + => try_copy_string(buffer, "TypeInfo"); +} + +struct Interface +{ + TypeInfo_Class classinfo; + void*[] vtbl; + size_t offset; +} + +struct OffsetTypeInfo +{ + size_t offset; + TypeInfo ti; +} + +class TypeInfo_Class : TypeInfo +{ +@nogc: + byte[] m_init; + string name; + void*[] vtbl; + Interface[] interfaces; + TypeInfo_Class base; + void* destructor; + void function(Object) nothrow @nogc classInvariant; + + enum ClassFlags : ushort + { + isCOMclass = 0x1, + noPointers = 0x2, + hasOffTi = 0x4, + hasCtor = 0x8, + hasGetMembers = 0x10, + hasTypeInfo = 0x20, + isAbstract = 0x40, + isCPPclass = 0x80, + hasDtor = 0x100, + hasNameSig = 0x200, + } + ClassFlags m_flags; + ushort depth; + void* deallocator; + OffsetTypeInfo[] m_offTi; + void function(Object) @nogc defaultConstructor; + + immutable(void)* m_RTInfo; + override @property immutable(void)* rtInfo() nothrow pure const @safe { return m_RTInfo; } + + uint[4] nameSig; + + override @property size_t tsize() nothrow pure const @safe + { + return (void*).sizeof; + } +} + +// TypeInfo_Struct — compiler generates static instances for every struct type. +// Field layout must exactly match what the compiler emits. +class TypeInfo_Struct : TypeInfo +{ +@nogc: + override ptrdiff_t toString(char[] buffer) const nothrow + => try_copy_string(buffer, name); + + override size_t toHash() @trusted nothrow const + => hashOf(mangledName); + + override bool opEquals(Object o) + { + if (this is o) return true; + auto s = cast(const TypeInfo_Struct) o; + return s && this.mangledName == s.mangledName; + } + + override size_t getHash(scope const void* p) @trusted pure nothrow const + { + if (xtoHash) + return (*xtoHash)(p); + return 0; + } + + override bool equals(scope const void* p1, scope const void* p2) @trusted pure nothrow const + { + if (!p1 || !p2) return false; + if (xopEquals) + { + const dg = _member_func(p1, xopEquals); + return dg.xopEquals(p2); + } + return p1 == p2; + } + + override int compare(scope const void* p1, scope const void* p2) @trusted pure nothrow const + { + if (p1 == p2) return 0; + if (!p1) return -1; + if (!p2) return 1; + if (xopCmp) + { + const dg = _member_func(p1, xopCmp); + return dg.xopCmp(p2); + } + return 0; + } + + override @property size_t tsize() nothrow pure const + => initializer().length; + + override const(void)[] initializer() nothrow pure const @safe @nogc + => m_init; + + override @property uint flags() nothrow pure const + => m_flags; + + override @property size_t talign() nothrow pure const + => m_align; + + string mangledName; + + @property string name() nothrow const @trusted + => mangledName; // no demangling — avoids pulling in core.demangle + + void[] m_init; + + @safe pure nothrow + { + size_t function(in void*) @nogc xtoHash; + bool function(in void*, in void*) @nogc xopEquals; + int function(in void*, in void*) @nogc xopCmp; + ptrdiff_t function(in void*, const(char)[]) @nogc xtoString; + + enum StructFlags : uint + { + hasPointers = 0x1, + isDynamicType = 0x2, + } + StructFlags m_flags; + } + union + { + void function(void*) @nogc xdtor; + void function(void*, const TypeInfo_Struct) @nogc xdtorti; + } + void function(void*) xpostblit; + + uint m_align; + + version (WithArgTypes) + { + override int argTypes(out TypeInfo arg1, out TypeInfo arg2) + { + arg1 = m_arg1; + arg2 = m_arg2; + return 0; + } + + TypeInfo m_arg1; + TypeInfo m_arg2; + } + + override @property immutable(void)* rtInfo() nothrow pure const @safe @nogc + => m_RTInfo; + + immutable(void)* m_RTInfo; +} + +class TypeInfo_Enum : TypeInfo +{ +@nogc: + override size_t getHash(scope const void* p) const { return base.getHash(p); } + override bool equals(scope const void* p1, scope const void* p2) const { return base.equals(p1, p2); } + override int compare(scope const void* p1, scope const void* p2) const { return base.compare(p1, p2); } + override @property size_t tsize() nothrow pure const { return base.tsize; } + override const(TypeInfo) next() nothrow pure const @nogc { return base.next; } + override @property uint flags() nothrow pure const { return base.flags; } + override @property size_t talign() nothrow pure const { return base.talign; } + override @property immutable(void)* rtInfo() nothrow pure const @safe @nogc { return base.rtInfo; } + + override const(void)[] initializer() nothrow pure const @nogc + => m_init.length ? m_init : base.initializer(); + + version (WithArgTypes) + { + override int argTypes(out TypeInfo arg1, out TypeInfo arg2) + => base.argTypes(arg1, arg2); + } + + TypeInfo base; + string name; + void[] m_init; +} + +class TypeInfo_Pointer : TypeInfo +{ +@nogc: + override @property size_t tsize() nothrow pure const + => (void*).sizeof; + + override const(TypeInfo) next() nothrow pure const @nogc + => m_next; + + TypeInfo m_next; +} + +class TypeInfo_Array : TypeInfo +{ +@nogc: + override @property size_t tsize() nothrow pure const + => (void[]).sizeof; + + override const(TypeInfo) next() nothrow pure const @nogc + => value; + + TypeInfo value; +} + +// Built-in array TypeInfo subclasses — the compiler generates references to +// these for common array types. Empty subclasses are sufficient; the +// compiler fills in the `value` field. +class TypeInfo_Ah : TypeInfo_Array {} // ubyte[] +class TypeInfo_Ag : TypeInfo_Array {} // byte[] +class TypeInfo_Aa : TypeInfo_Array {} // char[] +class TypeInfo_Aaya : TypeInfo_Array {} // string[] +class TypeInfo_At : TypeInfo_Array {} // ushort[] +class TypeInfo_As : TypeInfo_Array {} // short[] +class TypeInfo_Au : TypeInfo_Array {} // wchar[] +class TypeInfo_Ak : TypeInfo_Array {} // uint[] +class TypeInfo_Ai : TypeInfo_Array {} // int[] +class TypeInfo_Aw : TypeInfo_Array {} // dchar[] +class TypeInfo_Am : TypeInfo_Array {} // ulong[] +class TypeInfo_Al : TypeInfo_Array {} // long[] +class TypeInfo_Af : TypeInfo_Array {} // float[] +class TypeInfo_Ad : TypeInfo_Array {} // double[] +class TypeInfo_Av : TypeInfo_Array {} // void[] + +class TypeInfo_StaticArray : TypeInfo +{ +@nogc: + override @property size_t tsize() nothrow pure const + => len * value.tsize; + + override const(TypeInfo) next() nothrow pure const @nogc + => value; + + TypeInfo value; + size_t len; +} + +class TypeInfo_Vector : TypeInfo +{ +@nogc: + override @property size_t tsize() nothrow pure const + => base.tsize; + + TypeInfo base; +} + +class TypeInfo_Function : TypeInfo +{ +@nogc: + override @property size_t tsize() nothrow pure const + => 0; + + TypeInfo next; + string deco; +} + +class TypeInfo_Delegate : TypeInfo +{ +@nogc: + override @property size_t tsize() nothrow pure const + { + alias dg = int delegate(); + return dg.sizeof; + } + + TypeInfo next; + string deco; +} + +class TypeInfo_Interface : TypeInfo +{ +@nogc: + override @property size_t tsize() nothrow pure const + => (void*).sizeof; + + TypeInfo_Class info; +} + +class TypeInfo_Tuple : TypeInfo +{ + TypeInfo[] elements; +} + +class TypeInfo_AssociativeArray : TypeInfo +{ +@nogc: + override ptrdiff_t toString(char[] buffer) const nothrow + { + if (!buffer.ptr) + return value.toString(null) + key.toString(null) + 2; + ptrdiff_t l = value.toString(buffer); + if (l < 0) + return l; + if (buffer.length < l + 2) + return -1; + buffer[l] = '['; + ptrdiff_t k = key.toString(buffer[l + 1 .. $]); + if (k < 0) + return k; + l += k + 2; + if (buffer.length < l) + return -1; + buffer[l - 1] = ']'; + return l; + } + + 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(scope const void* p1, scope const void* p2) @trusted const + => xopEquals(p1, p2); + + override hash_t getHash(scope const void* p) nothrow @trusted const + => xtoHash(p); + + override @property size_t tsize() nothrow pure const + => (char[int]).sizeof; + + override const(void)[] initializer() const @trusted + => (cast(void*)null)[0 .. (char[int]).sizeof]; + + override const(TypeInfo) next() nothrow pure const @nogc { return value; } + override @property uint flags() nothrow pure const { return 1; } + + private static import urt.internal.aa; + alias Entry(K, V) = urt.internal.aa.Entry!(K, V); + + TypeInfo value; + TypeInfo key; + TypeInfo entry; + + bool function(scope const void* p1, scope const void* p2) nothrow @safe xopEquals; + hash_t function(scope const void*) nothrow @safe xtoHash; + + alias aaOpEqual(K, V) = urt.internal.aa._aaOpEqual!(K, V); + alias aaGetHash(K, V) = urt.internal.aa._aaGetHash!(K, V); + + override @property size_t talign() nothrow pure const + => (char[int]).alignof; +} + +class TypeInfo_Const : TypeInfo +{ +@nogc: + override size_t getHash(scope const void* p) const { return base.getHash(p); } + override bool equals(scope const void* p1, scope const void* p2) const { return base.equals(p1, p2); } + override int compare(scope const void* p1, scope const void* p2) const { return base.compare(p1, p2); } + override @property size_t tsize() nothrow pure const { return base.tsize; } + override const(TypeInfo) next() nothrow pure const @nogc { return base.next; } + override @property uint flags() nothrow pure const { return base.flags; } + override const(void)[] initializer() nothrow pure const @nogc { return base.initializer(); } + override @property size_t talign() nothrow pure const { return base.talign; } + + TypeInfo base; +} + +class TypeInfo_Invariant : TypeInfo_Const +{ +} + +class TypeInfo_Shared : TypeInfo_Const +{ +} + +class TypeInfo_Inout : TypeInfo_Const +{ +} + +// ────────────────────────────────────────────────────────────────────── +// TypeInfo for built-in types — compiler references these by mangled name +// (e.g. TypeInfo_k for uint). Single-character suffixes follow D's type +// encoding: a=char, b=bool, d=double, f=float, g=byte, h=ubyte, +// i=int, k=uint, l=long, m=ulong, o=dchar, s=short, t=wchar, +// u=ushort, v=void, x=const, y=immutable. +// ────────────────────────────────────────────────────────────────────── + +class TypeInfo_a : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return char.sizeof; } } +class TypeInfo_b : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return bool.sizeof; } } +class TypeInfo_d : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return double.sizeof; } } +class TypeInfo_f : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return float.sizeof; } } +class TypeInfo_g : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return byte.sizeof; } } +class TypeInfo_h : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return ubyte.sizeof; } } +class TypeInfo_i : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return int.sizeof; } } +class TypeInfo_k : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return uint.sizeof; } } +class TypeInfo_l : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return long.sizeof; } } +class TypeInfo_m : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return ulong.sizeof; } } +class TypeInfo_o : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return dchar.sizeof; } } +class TypeInfo_s : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return short.sizeof; } } +class TypeInfo_t : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return wchar.sizeof; } } +class TypeInfo_u : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return ushort.sizeof; } } +class TypeInfo_v : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return 0; } } + +// Helper for TypeInfo_Struct delegate dispatch. +// Uses a union to overlay raw delegate ABI (ptr + funcptr) with typed delegate fields. +private struct _member_func +{ + union + { + struct + { + const void* ptr; + const void* funcptr; + } + + @safe pure nothrow + { + bool delegate(in void*) @nogc xopEquals; + int delegate(in void*) @nogc xopCmp; + } + } +} + +// ────────────────────────────────────────────────────────────────────── +// __ArrayDtor — compiler lowers dynamic array destruction to this +// ────────────────────────────────────────────────────────────────────── + +void __ArrayDtor(T)(scope T[] a) +{ + foreach_reverse (ref T e; a) + e.__xdtor(); +} + +// ────────────────────────────────────────────────────────────────────── +// Compiler hook templates — array & AA literal lowering +// These are only called at runtime; CTFE evaluates literals directly. +// ────────────────────────────────────────────────────────────────────── + +void* _d_arrayliteralTX(T)(size_t length) @trusted pure nothrow +{ + assert(false, "Array literals require druntime"); +} + +alias AssociativeArray(Key, Value) = Value[Key]; + +public import urt.internal.aa : _d_aaIn, _d_aaDel, _d_aaNew, _d_aaEqual, _d_assocarrayliteralTX; +public import urt.internal.aa : _d_aaLen, _d_aaGetY, _d_aaGetRvalueX, _d_aaApply, _d_aaApply2; + +private import urt.internal.aa : makeAA; + +// Lower an AA to a newaa struct for static initialization. +auto _aaAsStruct(K, V)(V[K] aa) @safe +{ + assert(__ctfe); + return makeAA!(K, V)(aa); +} + +Tret _d_arraycatnTX(Tret, Tarr...)(auto ref Tarr froms) @trusted +{ + assert(false, "Array concatenation requires druntime"); +} + + +T _d_newclassT(T)() @trusted + if (is(T == class)) +{ + assert(false, "new class requires druntime"); +/+ + if (__ctfe) + assert(false, "new class not supported at CTFE without druntime"); + + import urt.mem : malloc; + import urt.mem : memcpy; + + enum sz = __traits(classInstanceSize, T); + auto p = malloc(sz); + if (p is null) + assert(false, "out of memory in _d_newclassT"); + + auto initSym = __traits(initSymbol, T); + memcpy(p, initSym.ptr, sz); + return cast(T)cast(void*)p; ++/ +} + +ref Tarr _d_arrayappendcTX(Tarr : T[], T)(return ref scope Tarr px, size_t n) @trusted +{ + assert(false, "Array append requires druntime"); +} + +ref Tarr _d_arrayappendT(Tarr : T[], T)(return ref scope Tarr x, scope Tarr y) @trusted +{ + assert(false, "Array append requires druntime"); +} + +// ────────────────────────────────────────────────────────────────────── +// Compiler hook templates — array operations, construction, etc. +// These are lowered by the compiler for various language constructs. +// ────────────────────────────────────────────────────────────────────── + +T[] _d_newarrayT(T)(size_t length, bool isShared = false) @trusted +{ + assert(false, "new array requires druntime"); +} + +Tarr _d_newarraymTX(Tarr : U[], T, U)(size_t[] dims, bool isShared = false) @trusted +{ + assert(false, "new multi-dim array requires druntime"); +} + +T* _d_newitemT(T)() @trusted +{ + assert(false, "new item requires druntime"); +} + +T _d_newThrowable(T)() @trusted + if (is(T : Throwable)) +{ + assert(false, "new throwable requires druntime"); +} + +int __cmp(T)(scope const T[] lhs, scope const T[] rhs) @trusted +{ + immutable len = lhs.length <= rhs.length ? lhs.length : rhs.length; + foreach (i; 0 .. len) + { + if (lhs.ptr[i] < rhs.ptr[i]) + return -1; + if (lhs.ptr[i] > rhs.ptr[i]) + return 1; + } + return (lhs.length > rhs.length) - (lhs.length < rhs.length); +} + +bool __equals(T1, T2)(scope const T1[] lhs, scope const T2[] rhs) +nothrow @nogc pure @trusted +if (__traits(isScalar, T1) && __traits(isScalar, T2)) +{ + if (lhs.length != rhs.length) + return false; + static if (T1.sizeof == T2.sizeof + && (T1.sizeof >= 4 || __traits(isUnsigned, T1) == __traits(isUnsigned, T2)) + && !__traits(isFloating, T1) && !__traits(isFloating, T2)) + { + if (__ctfe) + { + foreach (i; 0 .. lhs.length) + if (lhs.ptr[i] != rhs.ptr[i]) return false; + return true; + } + else + { + import urt.mem : memcmp; + return !lhs.length || 0 == memcmp(cast(const void*) lhs.ptr, cast(const void*) rhs.ptr, lhs.length * T1.sizeof); + } + } + else + { + foreach (i; 0 .. lhs.length) + if (lhs.ptr[i] != rhs.ptr[i]) return false; + return true; + } +} + +bool __equals(T1, T2)(scope T1[] lhs, scope T2[] rhs) +if (!__traits(isScalar, T1) || !__traits(isScalar, T2)) +{ + if (lhs.length != rhs.length) + return false; + foreach (i; 0 .. lhs.length) + if (lhs[i] != rhs[i]) return false; + return true; +} + +TTo[] __ArrayCast(TFrom, TTo)(return scope TFrom[] from) @nogc pure @trusted +{ + const fromSize = from.length * TFrom.sizeof; + if (fromSize % TTo.sizeof != 0) + assert(false, "Array cast misalignment"); + return (cast(TTo*) from.ptr)[0 .. fromSize / TTo.sizeof]; +} + +Tarr _d_arrayctor(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted +{ + assert(false, "Array ctor requires druntime"); +} + +void _d_arraysetctor(Tarr : T[], T)(scope Tarr p, scope ref T value) @trusted +{ + assert(false, "Array set-ctor requires druntime"); +} + +Tarr _d_arrayassign_l(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted +{ + assert(false, "Array assign requires druntime"); +} + +Tarr _d_arrayassign_r(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted +{ + assert(false, "Array assign requires druntime"); +} + +void _d_arraysetassign(Tarr : T[], T)(return scope Tarr to, scope ref T value) @trusted +{ + assert(false, "Array set-assign requires druntime"); +} + +size_t _d_arraysetlengthT(Tarr : T[], T)(return ref scope Tarr arr, size_t newlength) @trusted +{ + assert(false, "Array setlength requires druntime"); +} + +// LDC wraps the above in a template namespace +template _d_arraysetlengthTImpl(Tarr : T[], T) +{ + size_t _d_arraysetlengthT(return scope ref Tarr arr, size_t newlength) @trusted pure nothrow + { + assert(false, "Array setlength requires druntime"); + } +} + +string _d_assert_fail(A...)(const scope string comp, auto ref const scope A a) +{ + return "assertion failure"; +} + +// ────────────────────────────────────────────────────────────────────── +// Runtime hooks: assertions, array bounds checks +// These are referenced by compiler-generated code even in debug builds. +// ────────────────────────────────────────────────────────────────────── + +extern (C) void _d_assert_msg(string msg, string file, uint line) nothrow @nogc +{ + import urt.exception : assert_handler; + assert_handler()(file, line, msg); +} + +extern (C) void _d_assertp(immutable(char)* file, uint line) nothrow @nogc @trusted +{ + import urt.mem : strlen; + import urt.exception : assert_handler; + auto f = file[0 .. file ? strlen(file) : 0]; + assert_handler()(f, line, null); +} + +extern (C) void _d_assert(string file, uint line) nothrow @nogc +{ + import urt.exception : assert_handler; + assert_handler()(file, line, null); +} + +extern (C) void _d_arraybounds_indexp(string file, uint line, size_t index, size_t length) nothrow @nogc +{ + import urt.exception : assert_handler; + assert_handler()(file, line, "array index out of bounds"); +} + +extern (C) void _d_arraybounds_slicep(string file, uint line, size_t lower, size_t upper, size_t length) nothrow @nogc +{ + import urt.exception : assert_handler; + assert_handler()(file, line, "array slice out of bounds"); +} + +extern (C) void _d_arrayboundsp(string file, uint line) nothrow @nogc +{ + import urt.exception : assert_handler; + assert_handler()(file, line, "array index out of bounds"); +} + +extern (C) void _d_arraybounds(string file, uint line) nothrow @nogc +{ + import urt.exception : assert_handler; + assert_handler()(file, line, "array index out of bounds"); +} + +// Unittest assert hooks — the compiler generates these for assert() inside +// unittest blocks instead of the regular _d_assertp/_d_assert_msg. +extern (C) void _d_unittestp(immutable(char)* file, uint line) nothrow @nogc @trusted +{ + import urt.mem : strlen; + import urt.exception : assert_handler; + auto f = file[0 .. file ? strlen(file) : 0]; + assert_handler()(f, line, "unittest assertion failure"); +} + +extern (C) void _d_unittest_msg(string msg, string file, uint line) nothrow @nogc @trusted +{ + import urt.exception : assert_handler; + assert_handler()(file, line, msg); +} + +extern (C) void _d_unittest(string file, uint line) nothrow @nogc +{ + import urt.exception : assert_handler; + assert_handler()(file, line, "unittest assertion failure"); +} + +// GC allocation hook — compiler lowers `new` to this. In our @nogc world +// it should never be called from production code; provided so unittest +// blocks that accidentally use `new` can at least link. +extern (C) void* _d_allocmemory(size_t sz) nothrow @nogc @trusted +{ + import urt.mem : malloc; + return malloc(sz); +} + +// ────────────────────────────────────────────────────────────────────── +// LDC extern(C) runtime hooks +// LDC's codegen emits calls to old-style extern(C) functions for many +// operations where DMD uses template-based hooks. These stubs satisfy +// the linker. Implementations are provided where feasible; others +// assert(false) and must be fleshed out if the code path is hit. +// ────────────────────────────────────────────────────────────────────── + +version (LDC) +{ + +// --- Bounds checks (LDC variants without 'p' suffix) ------------------- + +extern (C) void _d_arraybounds_index(string file, uint line, size_t index, size_t length) nothrow @nogc +{ + import urt.exception : assert_handler; + if (auto handler = assert_handler) + handler(file, line, "array index out of bounds"); + else + _halt(); +} + +extern (C) void _d_arraybounds_slice(string file, uint line, size_t lower, size_t upper, size_t length) nothrow @nogc +{ + import urt.exception : assert_handler; + if (auto handler = assert_handler) + handler(file, line, "array slice out of bounds"); + else + _halt(); +} + +// --- Array slice copy (LDC emits this for non-elaborate arr[] = other[]) - +// Bounds-checked memcpy: verifies dstlen == srclen, then copies raw bytes. + +extern (C) void _d_array_slice_copy(void* dst, size_t dstlen, void* src, size_t srclen, size_t elemsize) nothrow @nogc @trusted +{ + assert(dstlen == srclen, "array slice lengths don't match for copy"); + import urt.mem : memcpy; + memcpy(dst, src, dstlen * elemsize); +} + +// --- Class allocation and casting (old-style extern C) ----------------- + +extern (C) void* _d_allocclass(TypeInfo_Class ci) nothrow @nogc @trusted +{ + import urt.mem : malloc, memcpy; + auto init = ci.initializer; + auto p = malloc(init.length); + if (p !is null) + memcpy(p, init.ptr, init.length); + return p; +} + +extern (C) Object _d_dynamic_cast(Object o, TypeInfo_Class c) nothrow @nogc @trusted +{ + // Traverse classinfo chain to check if o is-a c + if (o is null) return null; + auto oc = typeid(o); + while (oc !is null) + { + if (oc is c) + return o; + oc = oc.base; + } + return null; +} + +extern (C) void* _d_interface_cast(void* p, TypeInfo_Class c) nothrow @nogc @trusted +{ + // TODO: full interface casting requires traversing the Interface[] table + // in the target object's classinfo to find the right vtable offset. + // For now, return null (cast fails). + return null; +} + +// --- Struct array equality (old-style) --------------------------------- + +extern (C) int _adEq2(void[] a1, void[] a2, TypeInfo ti) nothrow @nogc @trusted +{ + if (a1.length != a2.length) return 0; + import urt.mem : memcmp; + return memcmp(a1.ptr, a2.ptr, a1.length) == 0 ? 1 : 0; +} + +} // version (LDC) + +// --- Old-style array allocation (extern C) ------------------------------ + +extern (C) void[] _d_newarrayT(const TypeInfo ti, size_t length) nothrow @nogc @trusted +{ + import urt.mem : calloc; + auto elemsize = ti.next ? ti.next.tsize : 1; + auto p = calloc(length, elemsize); + return p[0 .. length * elemsize]; +} + +extern (C) void[] _d_newarrayiT(const TypeInfo ti, size_t length) nothrow @nogc @trusted +{ + import urt.mem : calloc; + auto elemsize = ti.next ? ti.next.tsize : 1; + auto p = calloc(length, elemsize); + return p[0 .. length * elemsize]; +} + +extern (C) void[] _d_newarrayU(const TypeInfo ti, size_t length) nothrow @nogc @trusted +{ + import urt.mem : malloc; + auto elemsize = ti.next ? ti.next.tsize : 1; + auto sz = length * elemsize; + auto p = malloc(sz); + if (p is null) return null; + return p[0 .. sz]; +} + +// Unconditional halt — avoids circular dependency with assert. +private void _halt() nothrow @nogc @trusted +{ + version (D_InlineAsm_X86_64) + asm nothrow @nogc { hlt; } + else version (D_InlineAsm_X86) + asm nothrow @nogc { hlt; } + else + *(cast(int*) null) = 0; // fallback: null deref +} + +void __move_post_blt(S)(ref S newLocation, ref S oldLocation) nothrow + if (is(S == struct)) +{ + static if (__traits(hasMember, S, "__xpostblit")) + newLocation.__xpostblit(); +} + +void __ArrayPostblit(T)(T[] a) +{ + foreach (ref e; a) + static if (__traits(hasMember, T, "__xpostblit")) + e.__xpostblit(); +} + +int __switch(T, caseLabels...)(const scope T[] condition) pure nothrow @safe @nogc +{ + foreach (i, s; caseLabels) + { + static if (is(typeof(s) : typeof(condition))) + { + if (condition == s) + return cast(int) i; + } + } + return -1; +} + +void __switch_error()(string file = __FILE__, size_t line = __LINE__) +{ + assert(false, "Final switch error"); +} + +template _d_delstructImpl(T) +{ + void _d_delstruct(ref T p) @trusted + { + p = null; + } +} + +nothrow @nogc @trusted pure extern (C) void _d_delThrowable(scope Throwable) {} + +// ────────────────────────────────────────────────────────────────────── +// _arrayOp — compiler hook for vectorized array slice operations. +// DMD lowers `dest[] = a[] ^ b[]` to `_arrayOp!(T[], T[], T[], "^", "=")(dest, a, b)`. +// Args are in Reverse Polish Notation (RPN). +// ────────────────────────────────────────────────────────────────────── + +template _arrayOp(Args...) +{ + static if (is(Args[0] == E[], E)) + { + alias T = E; + + T[] _arrayOp(T[] res, _Filter!(_is_type, Args[1 .. $]) args) nothrow @nogc @trusted + { + foreach (pos; 0 .. res.length) + mixin(_scalar_exp!(Args[1 .. $]) ~ ";"); + return res; + } + } +} + +// ---- _arrayOp helpers (all private, CTFE-only) ---- + +private template _Filter(alias pred, args...) +{ + static if (args.length == 0) + alias _Filter = AliasSeq!(); + else static if (pred!(args[0])) + alias _Filter = AliasSeq!(args[0], _Filter!(pred, args[1 .. $])); + else + alias _Filter = _Filter!(pred, args[1 .. $]); +} + +private enum _is_type(T) = true; +private enum _is_type(alias a) = false; + +private bool _is_unary_op(string op) pure nothrow @safe @nogc + => op.length > 0 && op[0] == 'u'; + +private bool _is_binary_op(string op) pure nothrow @safe @nogc +{ + if (op == "^^") return true; + if (op.length != 1) return false; + switch (op[0]) + { + case '+', '-', '*', '/', '%', '|', '&', '^': + return true; + default: + return false; + } +} + +private bool _is_binary_assign_op(string op) + => op.length >= 2 && op[$ - 1] == '=' && _is_binary_op(op[0 .. $ - 1]); + +// Convert size_t to string at CTFE. +private string _size_to_str(size_t num) pure @safe +{ + enum digits = "0123456789"; + if (num < 10) return digits[num .. num + 1]; + return _size_to_str(num / 10) ~ digits[num % 10 .. num % 10 + 1]; +} + +// Generate element-wise mixin expression from RPN args (CTFE-evaluated enum). +// Uses a fixed-size stack with depth counter to avoid dynamic array operations +// (which would require _d_arraysetlengthT at semantic analysis time). +private enum _scalar_exp(Args...) = () { + string[Args.length] stack; + size_t depth; + size_t args_idx; + + static if (is(Args[0] == U[], U)) + alias Type = U; + else + alias Type = Args[0]; + + foreach (i, arg; Args) + { + static if (is(arg == E[], E)) + { + stack[depth] = "args[" ~ _size_to_str(args_idx++) ~ "][pos]"; + ++depth; + } + else static if (is(arg)) + { + stack[depth] = "args[" ~ _size_to_str(args_idx++) ~ "]"; + ++depth; + } + else static if (_is_unary_op(arg)) + { + auto op = arg[0] == 'u' ? arg[1 .. $] : arg; + static if (is(Type : int)) + stack[depth - 1] = "cast(typeof(" ~ stack[depth - 1] ~ "))" ~ op ~ "cast(int)(" ~ stack[depth - 1] ~ ")"; + else + stack[depth - 1] = op ~ stack[depth - 1]; + } + else static if (arg == "=") + { + stack[depth - 1] = "res[pos] = cast(T)(" ~ stack[depth - 1] ~ ")"; + } + else static if (_is_binary_assign_op(arg)) + { + stack[depth - 1] = "res[pos] " ~ arg ~ " cast(T)(" ~ stack[depth - 1] ~ ")"; + } + else static if (_is_binary_op(arg)) + { + stack[depth - 2] = "(" ~ stack[depth - 2] ~ " " ~ arg ~ " " ~ stack[depth - 1] ~ ")"; + --depth; + } + } + return stack[0]; +}(); + +// ────────────────────────────────────────────────────────────────────── +// _d_cast — dynamic class cast, walks TypeInfo_Class.base chain +// ────────────────────────────────────────────────────────────────────── + +void* _d_cast(To, From)(From o) @trusted + if (is(From == class) && is(To == class)) +{ + if (o is null) return null; + auto ci = typeid(o); + auto target = typeid(To); + do + { + if (ci is target) return cast(void*) o; + ci = ci.base; + } + while (ci !is null); + return null; +} + +void* _d_cast(To, From)(From o) @trusted + if (is(From == class) && is(To == interface)) +{ + if (o is null) return null; + // Walk interface list — not implemented yet, fall back to null + assert(false, "Interface cast not yet implemented"); +} + +// ────────────────────────────────────────────────────────────────────── +// Throwable / Exception / Error — exception hierarchy +// ────────────────────────────────────────────────────────────────────── + +class Throwable : Object +{ + string msg; + Throwable next; + string file; + size_t line; + + private uint _refcount; + + @nogc @safe pure nothrow this(string msg, Throwable next = null) + { + this.msg = msg; + this.next = next; + } + + @nogc @safe pure nothrow this(string msg, string file, size_t line, Throwable next = null) + { + this.msg = msg; + this.file = file; + this.line = line; + this.next = next; + } + + const(char)[] message() const @nogc @safe pure nothrow + => msg; + + @system @nogc final pure nothrow ref uint refcount() return + => _refcount; + + static Throwable chainTogether(return scope Throwable e1, return scope Throwable e2) nothrow @nogc + { + if (!e1) + return e2; + if (!e2) + return e1; + for (auto e = e1; ; e = e.next) + { + if (!e.next) + { + e.next = e2; + break; + } + } + return e1; + } +} + +class Exception : Throwable +{ + @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) + { + super(msg, file, line, next); + } +} + +class Error : Throwable +{ + Throwable bypassedException; + + @nogc @safe pure nothrow this(string msg, Throwable next = null) + { + super(msg, next); + } + + @nogc @safe pure nothrow this(string msg, string file, size_t line, Throwable next = null) + { + super(msg, file, line, next); + } +} + +// ────────────────────────────────────────────────────────────────────── +// destroy — compiler generates calls for scope guards, etc. +// ────────────────────────────────────────────────────────────────────── + +void destroy(bool initialize = true, T)(ref T obj) if (is(T == struct)) +{ + destruct_recurse(obj); + static if (initialize) + { + static if (__traits(isZeroInit, T)) + (cast(ubyte*)&obj)[0 .. T.sizeof] = 0; + else + { + auto init = __traits(initSymbol, T); + (cast(ubyte*)&obj)[0 .. T.sizeof] = (cast(const ubyte*)init.ptr)[0 .. T.sizeof]; + } + } +} + +// Destruct a struct by calling its __xdtor, but only if it truly belongs +// to this type (Bugzilla 14746). +void destruct_recurse(S)(ref S s) if (is(S == struct)) +{ + static if (__traits(hasMember, S, "__xdtor") && __traits(isSame, S, __traits(parent, s.__xdtor))) + s.__xdtor(); +} + +void destruct_recurse(E, size_t n)(ref E[n] arr) +{ + foreach_reverse (ref elem; arr) + destruct_recurse(elem); +} + +void destroy(bool initialize = true, T)(T obj) if (is(T == class)) +{ + static if (__traits(hasMember, T, "__xdtor")) + obj.__xdtor(); +} + +void destroy(bool initialize = true, T)(ref T obj) if (!is(T == struct) && !is(T == class)) +{ + static if (initialize) + obj = T.init; +} + +// ────────────────────────────────────────────────────────────────────── +// .dup / .idup — array duplication properties +// ────────────────────────────────────────────────────────────────────── + +@property immutable(T)[] idup(T)(T[] a) @trusted +{ + // CTFE-compatible: the interpreter handles ~= natively. + // At runtime this would assert via _d_arrayappendcTX. + immutable(T)[] r; + foreach (ref e; a) + r ~= cast(immutable(T)) e; + return r; +} + +@property T[] dup(T)(const(T)[] a) @trusted +{ + T[] r; + foreach (ref e; a) + r ~= cast(T) e; + return r; +} + +// ────────────────────────────────────────────────────────────────────── +// Compiler-generated struct equality/comparison fallbacks +// ────────────────────────────────────────────────────────────────────── + +bool _xopEquals(in void*, in void*) + => false; + +bool _xopCmp(in void*, in void*) + => false; + +// ────────────────────────────────────────────────────────────────────── +// hashOf — used by AAs and anywhere .toHash is needed +// ────────────────────────────────────────────────────────────────────── + +size_t hashOf(T)(auto ref T val, size_t seed = 0) @trusted pure nothrow @nogc +{ + static if (is(T : const(char)[])) + { + // FNV-1a for strings + size_t h = seed == 0 ? 2166136261 : seed; + foreach (c; cast(const(ubyte)[]) val) + { + h ^= c; + h *= 16777619; + } + return h; + } + else static if (is(T V : V*)) + { + // Pointers — CTFE compatible + if (__ctfe) + { + if (val is null) return seed; + assert(0, "Unable to hash non-null pointer at compile time"); + } + size_t v = cast(size_t) val; + return _fnv(v ^ (v >> 4), seed); + } + else static if (__traits(isIntegral, T)) + { + // Integers — CTFE compatible, no reinterpreting cast + static if (T.sizeof <= size_t.sizeof) + return _fnv(cast(size_t) val, seed); + else + return _fnv(cast(size_t)(val ^ (val >>> (size_t.sizeof * 8))), seed); + } + else static if (__traits(isFloating, T)) + { + // At CTFE we cannot reinterpret float bits; use lossy integer cast + if (__ctfe) + return _fnv(cast(size_t) cast(long) val, seed); + // Runtime: walk the bytes + auto p = cast(const ubyte*)&val; + size_t h = seed == 0 ? 2166136261 : seed; + foreach (i; 0 .. T.sizeof) + { + h ^= p[i]; + h *= 16777619; + } + return h; + } + else static if (is(T == struct)) + { + // Structs — hash each field (CTFE compatible) + size_t h = seed; + foreach (ref field; val.tupleof) + h = hashOf(field, h); + return h; + } + else static if (is(T == enum)) + { + import urt.internal.traits : Unconst; + static if (is(T EType == enum)) + return hashOf(cast(EType) val, seed); + else + return _fnv(0, seed); + } + else + { + return seed; + } +} + +private size_t _fnv(size_t val, size_t seed) nothrow @nogc pure @safe +{ + size_t h = seed == 0 ? 2166136261 : seed; + foreach (i; 0 .. size_t.sizeof) + { + h ^= val & 0xFF; + h *= 16777619; + val >>= 8; + } + return h; +} + +// ────────────────────────────────────────────────────────────────────── +// ModuleInfo — compiler emits one per module with ctor/dtor/unittest info. +// Variable-sized: fields are packed after the header based on flag bits. +// ────────────────────────────────────────────────────────────────────── + +enum +{ + MIctorstart = 0x1, + MIctordone = 0x2, + MIstandalone = 0x4, + MItlsctor = 0x8, + MItlsdtor = 0x10, + MIctor = 0x20, + MIdtor = 0x40, + MIxgetMembers = 0x80, + MIictor = 0x100, + MIunitTest = 0x200, + MIimportedModules = 0x400, + MIlocalClasses = 0x800, + MIname = 0x1000, +} + +struct ModuleInfo +{ + uint _flags; + uint _index; + +const: + private void* addr_of(int flag) return nothrow pure @nogc @trusted + { + void* p = cast(void*)&this + ModuleInfo.sizeof; + + if (flags & MItlsctor) + { + if (flag == MItlsctor) + return p; + p += (void function()).sizeof; + } + if (flags & MItlsdtor) + { + if (flag == MItlsdtor) + return p; + p += (void function()).sizeof; + } + if (flags & MIctor) + { + if (flag == MIctor) + return p; + p += (void function()).sizeof; + } + if (flags & MIdtor) + { + if (flag == MIdtor) + return p; + p += (void function()).sizeof; + } + if (flags & MIxgetMembers) + { + if (flag == MIxgetMembers) + return p; + p += (void*).sizeof; + } + if (flags & MIictor) + { + if (flag == MIictor) + return p; + p += (void function()).sizeof; + } + if (flags & MIunitTest) + { + if (flag == MIunitTest) + return p; + p += (void function()).sizeof; + } + if (flags & MIimportedModules) + { + if (flag == MIimportedModules) + return p; + p += size_t.sizeof + *cast(size_t*)p * (immutable(ModuleInfo)*).sizeof; + } + if (flags & MIlocalClasses) + { + if (flag == MIlocalClasses) + return p; + p += size_t.sizeof + *cast(size_t*)p * (TypeInfo_Class).sizeof; + } + if (true || flags & MIname) + { + if (flag == MIname) + return p; + } + assert(0); + } + + @property uint flags() nothrow pure @nogc + => _flags; + + @property void function() tlsctor() nothrow pure @nogc @trusted + => flags & MItlsctor ? *cast(typeof(return)*)addr_of(MItlsctor) : null; + + @property void function() tlsdtor() nothrow pure @nogc @trusted + => flags & MItlsdtor ? *cast(typeof(return)*)addr_of(MItlsdtor) : null; + + @property void function() ctor() nothrow pure @nogc @trusted + => flags & MIctor ? *cast(typeof(return)*)addr_of(MIctor) : null; + + @property void function() dtor() nothrow pure @nogc @trusted + => flags & MIdtor ? *cast(typeof(return)*)addr_of(MIdtor) : null; + + @property void function() ictor() nothrow pure @nogc @trusted + => flags & MIictor ? *cast(typeof(return)*)addr_of(MIictor) : null; + + @property void function() unitTest() nothrow pure @nogc @trusted + => flags & MIunitTest ? *cast(typeof(return)*)addr_of(MIunitTest) : null; + + @property immutable(ModuleInfo*)[] importedModules() return nothrow pure @nogc @trusted + { + if (flags & MIimportedModules) + { + auto p = cast(size_t*)addr_of(MIimportedModules); + return (cast(immutable(ModuleInfo*)*)(p + 1))[0 .. *p]; + } + return null; + } + + @property string name() nothrow @nogc @trusted + { + if (flags & MIname) + { + auto p = cast(immutable char*)addr_of(MIname); + size_t len = 0; + while (p[len] != 0) ++len; + return p[0 .. len]; + } + return null; + } +} + +// ────────────────────────────────────────────────────────────────────── +// _d_dso_registry — ELF shared-object module registry +// +// On ELF targets the compiler generates .init_array/.fini_array entries +// that call _d_dso_registry with pointers to the module-info section. +// We stash the module range here; uRT's C main() handles constructor +// execution and unittest running via get_module_infos(). +// ────────────────────────────────────────────────────────────────────── + +version (linux) +{ + struct CompilerDSOData + { + size_t _version; + void** _slot; + immutable(ModuleInfo*)* _minfo_beg, _minfo_end; + } + + extern(C) __gshared immutable(ModuleInfo*)* _elf_minfo_beg; + extern(C) __gshared immutable(ModuleInfo*)* _elf_minfo_end; + + extern(C) void _d_dso_registry(CompilerDSOData* data) nothrow @nogc + { + if (data._version < 1) + return; + + if (*data._slot is null) + { + *data._slot = cast(void*)data; + _elf_minfo_beg = data._minfo_beg; + _elf_minfo_end = data._minfo_end; + } + else + { + *data._slot = null; + _elf_minfo_beg = null; + _elf_minfo_end = null; + } + } +} + +// ────────────────────────────────────────────────────────────────────── +// TypeInfo for const/immutable char[] — compiler references by name +// ────────────────────────────────────────────────────────────────────── + +class TypeInfo_Axa : TypeInfo_Array {} // const(char)[] +class TypeInfo_Aya : TypeInfo_Array {} // immutable(char)[] = string + +// ────────────────────────────────────────────────────────────────────── +// _d_invariant — contract invariant hook (matches rt.invariant_ mangling) +// ────────────────────────────────────────────────────────────────────── + +pragma(mangle, "_D2rt10invariant_12_d_invariantFC6ObjectZv") +void _d_invariant_impl(Object o) nothrow @nogc +{ + assert(o !is null); + auto c = typeid(o); + do + { + if (c.classInvariant) + c.classInvariant(o); + c = c.base; + } + while (c); +} + +// ────────────────────────────────────────────────────────────────────── +// Compiler-generated memset intrinsics (struct initialization) +// ────────────────────────────────────────────────────────────────────── + +private struct Bits128 { ulong[2] v; } +private struct Bits80 { ubyte[real.sizeof] v; } +private struct Bits160 { ubyte[2 * real.sizeof] v; } + +extern (C) nothrow @nogc @trusted +{ + short* _memset16(short* p, short value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + int* _memset32(int* p, int value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + long* _memset64(long* p, long value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + Bits80* _memset80(Bits80* p, Bits80 value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + Bits128* _memset128(Bits128* p, Bits128 value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + void[]* _memset128ii(void[]* p, void[] value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + Bits160* _memset160(Bits160* p, Bits160 value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + float* _memsetFloat(float* p, float value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + double* _memsetDouble(double* p, double value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + void* _memsetn(void* p, void* value, int count, size_t sizelem) + { + auto dst = cast(ubyte*) p; + auto src = cast(ubyte*) value; + foreach (_; 0 .. count) + { + foreach (j; 0 .. sizelem) + dst[j] = src[j]; + dst += sizelem; + } + return p; + } +} + +private ptrdiff_t try_copy_string(char[] buffer, const(char)[] src) pure nothrow @nogc +{ + if (buffer.ptr) + { + if (buffer.length < src.length) + return -1; + buffer[0 .. src.length] = src[]; + } + return src.length; +} diff --git a/src/std/math.d b/src/std/math.d new file mode 100644 index 0000000..e6a1a4a --- /dev/null +++ b/src/std/math.d @@ -0,0 +1,8 @@ +/// Minimal std.math stub — provides just the `pow` function that DMD's +/// `^^` operator lowering requires. Nothing else from Phobos is pulled in. +module std.math; + +// DMD lowers `a ^^ b` to `std.math.pow(a, b)`. +public import urt.math : pow; + +// TODO: do we need sqrt for `^^ 0.5`? diff --git a/src/urt/atomic.d b/src/urt/atomic.d new file mode 100644 index 0000000..a93cc45 --- /dev/null +++ b/src/urt/atomic.d @@ -0,0 +1,107 @@ +module urt.atomic; + +// TODO: these are all just stubs, but we can flesh it out as we need it... + +nothrow @nogc @safe: + +enum MemoryOrder +{ + relaxed = 0, + consume = 1, + acquire = 2, + release = 3, + acq_rel = 4, + seq_cst = 5, + seq = 5, +} + + +// DMD lowers to these names... +alias atomicLoad = atomic_load; +alias atomicStore = atomic_store; +alias atomicOp = atomic_op; +alias atomicExchange = atomic_exchange; +alias atomicFetchAdd = atomic_fetch_add; +alias atomicFetchSub = atomic_fetch_sub; + + +T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(ref const T val) pure @trusted +{ + return val; +} + +// Overload for shared +TailShared!T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(auto ref shared const T val) pure @trusted +{ + return *cast(TailShared!T*)&val; +} + +void atomic_store(MemoryOrder ms = MemoryOrder.seq, T, V)(ref shared T val, V newval) pure @trusted + if (__traits(compiles, { *cast(T*)&val = newval; })) +{ + *cast(T*)&val = newval; +} + +TailShared!T atomic_op(string op, T, V1)(ref shared T val, V1 mod) pure @trusted + if (__traits(compiles, mixin("*cast(T*)&val" ~ op ~ "mod"))) +{ + auto ptr = cast(T*)&val; + mixin("*ptr " ~ op ~ " mod;"); + return *cast(TailShared!T*)ptr; +} + +bool cas(T, V1, V2)(shared(T)* here, V1 ifThis, V2 writeThis) pure @trusted +{ + auto ptr = cast(T*)here; + if (*ptr == ifThis) + { + *ptr = writeThis; + return true; + } + return false; +} + +T atomic_exchange(MemoryOrder ms = MemoryOrder.seq, T, V)(shared(T)* here, V exchangeWith) pure @trusted +{ + auto ptr = cast(T*)here; + T old = *ptr; + *ptr = exchangeWith; + return old; +} + +T atomic_fetch_add(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) +{ + auto ptr = cast(T*)&val; + T old = *ptr; + *ptr += mod; + return old; +} + +T atomic_fetch_sub(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) +{ + auto ptr = cast(T*)&val; + T old = *ptr; + *ptr -= mod; + return old; +} + +/// Simplified TailShared — strips shared qualifier for noruntime builds. +template TailShared(U) if (!is(U == shared)) +{ + alias TailShared = .TailShared!(shared U); +} + +template TailShared(S) if (is(S == shared)) +{ + static if (is(S U == shared U)) + { + static if (is(S : U)) + alias TailShared = U; + else + alias TailShared = S; + } + else + static assert(false); +} diff --git a/src/urt/conv.d b/src/urt/conv.d index 320d57e..55755a7 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -339,7 +339,7 @@ double parse_float(const(char)[] str, size_t* bytes_taken = null, uint base = 10 if (__ctfe) return mantissa * double(base)^^e; else - return mantissa * pow(base, e); + return mantissa * pow(double(base), e); } unittest @@ -589,7 +589,7 @@ ptrdiff_t format_float(double value, char[] buffer, const(char)[] format = null) // TODO: this function should be oblitereated and implemented natively... // CRT call can't CTFE, which is a shame - import core.stdc.stdio; + import urt.internal.stdc; import urt.string.format : concat; char[16] fmt = void; diff --git a/src/urt/dbg.d b/src/urt/dbg.d index 189100d..1b0465e 100644 --- a/src/urt/dbg.d +++ b/src/urt/dbg.d @@ -57,46 +57,3 @@ else version (ARM) } else static assert(0, "TODO: Unsupported architecture"); - - -private: - -package(urt) void setup_assert_handler() -{ - import core.exception : assertHandler; - assertHandler = &urt_assert; -} - -void urt_assert(string file, size_t line, string msg) nothrow @nogc -{ - import core.stdc.stdlib : exit; - - debug - { - import core.stdc.stdio; - - if (msg.length == 0) - msg = "Assertion failed"; - - version (Windows) - { - import core.sys.windows.winbase; - char[1024] buffer; - _snprintf(buffer.ptr, buffer.length, "%.*s(%d): %.*s\n", cast(int)file.length, file.ptr, cast(int)line, cast(int)msg.length, msg.ptr); - OutputDebugStringA(buffer.ptr); - - // Windows can have it at stdout aswell? - printf("%.*s(%d): %.*s\n", cast(int)file.length, file.ptr, cast(int)line, cast(int)msg.length, msg.ptr); - } - else - { - // TODO: write to stderr would be better... - printf("%.*s(%d): %.*s\n", cast(int)file.length, file.ptr, cast(int)line, cast(int)msg.length, msg.ptr); - } - - breakpoint(); -// exit(-1); // TODO: what if some systems don't support a software breakpoint? - } - else - exit(-1); -} diff --git a/src/urt/exception.d b/src/urt/exception.d new file mode 100644 index 0000000..84decb4 --- /dev/null +++ b/src/urt/exception.d @@ -0,0 +1,58 @@ +/// Assert handler registration for uRT. +module urt.exception; + +nothrow @nogc: + + +alias AssertHandler = void function(string file, size_t line, string msg) nothrow @nogc; + +AssertHandler assert_handler() @property nothrow @nogc @trusted + => _assert_handler; + +void assert_handler(AssertHandler handler) @property nothrow @nogc @trusted +{ + if (handler is null) + _assert_handler = &urt_assert; + else + _assert_handler = handler; +} + + +private: + +__gshared AssertHandler _assert_handler = &urt_assert; + +void urt_assert(string file, size_t line, string msg) nothrow @nogc +{ + import urt.internal.stdc : exit; + + debug + { + import urt.internal.stdc; + import urt.dbg; + + if (msg.length == 0) + msg = "Assertion failed"; + + version (Windows) + { + import urt.internal.sys.windows.winbase; + char[1024] buffer; + _snprintf(buffer.ptr, buffer.length, "%.*s(%d): %.*s\n", cast(int)file.length, file.ptr, cast(int)line, cast(int)msg.length, msg.ptr); + OutputDebugStringA(buffer.ptr); + + // Windows can have it at stdout aswell? + printf("%.*s(%d): %.*s\n", cast(int)file.length, file.ptr, cast(int)line, cast(int)msg.length, msg.ptr); + } + else + { + // TODO: write to stderr would be better... + printf("%.*s(%d): %.*s\n", cast(int)file.length, file.ptr, cast(int)line, cast(int)msg.length, msg.ptr); + } + + breakpoint(); +// exit(-1); // TODO: what if some systems don't support a software breakpoint? + } + else + exit(-1); +} diff --git a/src/urt/fibre.d b/src/urt/fibre.d index 38a5579..66253fb 100644 --- a/src/urt/fibre.d +++ b/src/urt/fibre.d @@ -344,7 +344,7 @@ alias coentry_t = void function() @nogc; version (UseWindowsFibreAPI) { - import core.sys.windows.winbase; + import urt.internal.sys.windows.winbase; version (X86_64) { diff --git a/src/urt/file.d b/src/urt/file.d index 0b54ae2..b5a232c 100644 --- a/src/urt/file.d +++ b/src/urt/file.d @@ -12,10 +12,10 @@ alias SystemTime = void; version(Windows) { - import core.sys.windows.winbase; - import core.sys.windows.windows; - import core.sys.windows.windef : MAX_PATH; - import core.sys.windows.winnt; + import urt.internal.sys.windows.winbase; + import urt.internal.sys.windows; + import urt.internal.sys.windows.windef : MAX_PATH; + import urt.internal.sys.windows.winnt; import urt.string : twstringz; // TODO: remove this when LDC/GDC are up to date... @@ -26,7 +26,7 @@ version(Windows) } else version (Posix) { - import core.stdc.errno; + import urt.internal.stdc; import core.sys.posix.dirent; import core.sys.posix.fcntl; import core.sys.posix.stdlib; @@ -354,27 +354,22 @@ Result create_directory(const(char)[] path) { version (Windows) { - if (!CreateDirectoryW(path.twstringz, null)) - { - DWORD err = GetLastError(); - if (err == ERROR_ALREADY_EXISTS) - return Result.success; - return getlasterror_result(); - } - return Result.success; + if (CreateDirectoryW(path.twstringz, null)) + return Result.success; + Result r = getlasterror_result(); } else version (Posix) { - if (core.sys.posix.sys.stat.mkdir(tconcat(path, "\0").ptr, 493 /* 0755 */) != 0) - { - if (core.stdc.errno.errno == core.stdc.errno.EEXIST) - return Result.success; - return errno_result(); - } - return Result.success; + if (!core.sys.posix.sys.stat.mkdir(tconcat(path, "\0").ptr, 493 /* 0755 */) != 0) + return Result.success; + Result r = errno_result(); } else static assert(0, "Not implemented"); + + if (r == InternalResult.already_exists) + return Result.success; + return r; } Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags openFlags = FileOpenFlags.None) diff --git a/src/urt/internal/aa.d b/src/urt/internal/aa.d new file mode 100644 index 0000000..e7ae5d6 --- /dev/null +++ b/src/urt/internal/aa.d @@ -0,0 +1,1048 @@ +/** + * template implementation of associative arrays. + * + * Copyright: Copyright Digital Mars 2000 - 2015, Steven Schveighoffer 2022. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Martin Nowak, Steven Schveighoffer, Rainer Schuetze + * + * Source: $(DRUNTIMESRC core/internal/_newaa.d) + * + * derived from rt/aaA.d + */ +module urt.internal.aa; + +/// AA version for debuggers, bump whenever changing the layout +immutable int _aaVersion = 1; + +import urt.internal.traits : substInout; + +// 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; + +/// AA wrapper +struct AA(K, V) +{ + Impl!(K,V)* impl; + alias impl this; + + @property bool empty() const pure nothrow @nogc @safe + { + pragma(inline, true); + return impl is null || !impl.length; + } + @property size_t length() const pure nothrow @nogc @safe + { + pragma(inline, true); + return impl is null ? 0 : impl.length; + } +} + +/// like urt.internal.traits.Unconst, but stripping inout, too +private template Unconstify(T : const U, U) +{ + static if (is(U == inout V, V)) + alias Unconstify = V; + else + alias Unconstify = U; +} + +ref _refAA(K, V)(ref V[K] aa) @trusted +{ + pragma(inline, true); + return *(cast(AA!(substInout!K, substInout!V)*)&aa); +} + +auto _toAA(K, V)(const V[K] aa) @trusted +{ + pragma(inline, true); + return *(cast(const(AA!(K, V))*)&aa); +} + +auto _toAA(K, V)(inout V[K] aa) @trusted +{ + pragma(inline, true); + return *(cast(inout(AA!(K, V))*)&aa); +} + +// for backward compatibility, but should be deprecated +auto _toAA(K, V)(shared const V[K] aa) @trusted +{ + pragma(inline, true); + return *(cast(AA!(K, V)*)&aa); +} + +// for backward compatibility, but should be deprecated +auto _toAA(K, V)(shared V[K] aa) @trusted +{ + pragma(inline, true); + return *(cast(AA!(K, V)*)&aa); +} + +// resolve ambiguity for immutable converting to const and shared const +auto _toAA(K, V)(immutable V[K] aa) @trusted +{ + pragma(inline, true); + return *(cast(AA!(K, V)*)&aa); +} + +static struct Entry(K, V) +{ + K key; + V value; +} + +// backward compatibility conversions +private ref compat_key(K, K2)(ref K2 key) +{ + pragma(inline, true); + static if (is(K2 == const(char)[]) && is(K == string)) + return (ref (ref return K2 k2) @trusted => *cast(string*)&k2)(key); + else + return key; +} + +private void _aaMove(V)(ref V src, ref V dst) @trusted +{ + import urt.mem : memcpy, memset; + // move without postblit!? + memcpy(&dst, &src, V.sizeof); + static if (__traits(isZeroInit, V)) + memset(&src, 0, V.sizeof); + else + memcpy(&src, &V.init, V.sizeof); +} + +// mimick behaviour of rt.aaA for initialization +Entry!(K, V)* _newEntry(K, V)(ref K key, ref V value) +{ + static if (__traits(compiles, new Entry!(K, V)(key, value))) + { + auto entry = new Entry!(K, V)(key, value); + } + else static if (__traits(compiles, { K k; new Entry!(K, V)(k); })) + { + auto entry = new Entry!(K, V)(key); + _aaMove(value, entry.value); + } + else + { + auto entry = new Entry!(K, V); + _aaMove(key, entry.key); + _aaMove(value, entry.value); + } + return entry; +} + +// mimick behaviour of rt.aaA for initialization +Entry!(K, V)* _newEntry(K, V, K2)(ref K2 key) +{ + static if (__traits(compiles, new Entry!(K, V)(key)) && + !(is(V == struct) && __traits(isNested, V))) // not detected by "compiles" + { + auto entry = new Entry!(K, V)(key); + } + else static if (__traits(compiles, { K2 k; new Entry!(K, V)(k, V.init); })) + { + // with disabled ctor for V + auto entry = new Entry!(K, V)(key, V.init); + } + else + { + // with disabled ctor for K and V + auto entry = new Entry!(K, V); + entry.key = key; + } + static if (!__traits(isZeroInit, V)) + { + () @trusted { (cast(ubyte*)&entry.value)[0..V.sizeof] = 0; }(); + } + return entry; +} + +template pure_hashOf(K) +{ + static if (__traits(compiles, function hash_t(scope const ref K key) pure nothrow @nogc @trusted { return hashOf(cast()key); })) + { + // avoid wrapper call in debug builds if pure nothrow @nogc is inferred + pragma(inline, true) + hash_t pure_hashOf(scope const ref K key) @trusted { return hashOf(cast()key); } + } + else + { + // for backward compatibility, do not require const in hashOf() + hash_t wrap_hashOf(K)(scope const ref K key) @trusted { return hashOf(cast()key); } + enum pure_hashOf = cast(hash_t function(scope ref const K key) pure nothrow @nogc @safe) &wrap_hashOf!K; + } +} + +// for backward compatibilty pretend the comparison is @safe, pure, etc +// this also breaks cyclic inference on recursive data types +template pure_keyEqual(K1, K2 = K1) +{ + static if (__traits(compiles, function bool(ref const K1 k1, ref const K2 k2) pure nothrow @nogc @trusted { return cast()k1 == cast()k2; })) + { + // avoid wrapper call in debug builds if pure nothrow @nogc is inferred + pragma(inline, true) + bool pure_keyEqual(ref const K1 k1, ref const K2 k2) @trusted { return cast()k1 == cast()k2; } + } + else + { + bool keyEqual(ref const K1 k1, ref const K2 k2) @trusted { return cast()k1 == cast()k2; } + enum pure_keyEqual = cast(bool function(ref const K1, ref const K2) pure nothrow @nogc @safe) &keyEqual; + } +} + +private struct Impl(K, V) +{ +private: + alias Bucket = .Bucket!(K, V); + + this(size_t sz /* = INIT_NUM_BUCKETS */) nothrow + { + buckets = alloc_buckets(sz); + first_used = cast(uint) buckets.length; + + // only for binary compatibility + entry_ti = typeid(Entry!(K, V)); + hash_fn = delegate size_t (scope ref const K key) nothrow pure @nogc @safe { + return pure_hashOf!K(key); + }; + + key_sz = cast(uint) K.sizeof; + val_sz = cast(uint) V.sizeof; + val_off = cast(uint) talign(key_sz, V.alignof); + + enum ctflags = () { + import urt.internal.traits; + Impl.Flags flags; + static if (__traits(hasPostblit, K)) + flags |= flags.key_has_postblit; + static if (hasIndirections!K || hasIndirections!V) + flags |= flags.has_pointers; + return flags; + } (); + flags = ctflags; + } + + Bucket[] buckets; + uint used; + uint deleted; + const(TypeInfo) entry_ti; // only for binary compatibility + uint first_used; + immutable uint key_sz; // only for binary compatibility + immutable uint val_sz; // only for binary compatibility + immutable uint val_off; // only for binary compatibility + Flags flags; // only for binary compatibility + size_t delegate(scope ref const K) nothrow pure @nogc @safe hash_fn; + + enum Flags : ubyte + { + none = 0x0, + key_has_postblit = 0x1, + has_pointers = 0x2, + } + + @property size_t length() const pure nothrow @nogc @safe + { + pragma(inline, true); + assert(used >= deleted); + return used - deleted; + } + + @property size_t dim() const pure nothrow @nogc @safe + { + pragma(inline, true); + return buckets.length; + } + + @property size_t mask() const pure nothrow @nogc @safe + { + pragma(inline, true); + return dim - 1; + } + + // find the first slot to insert a value with hash + size_t find_slot_insert(size_t hash) const pure nothrow @nogc @safe + { + for (size_t i = hash & mask, j = 1;; ++j) + { + if (!buckets[i].filled) + return i; + i = (i + j) & mask; + } + } + + // lookup a key + inout(Bucket)* find_slot_lookup(K2)(size_t hash, scope ref const K2 key) inout pure @safe nothrow + { + for (size_t i = hash & mask, j = 1;; ++j) + { + auto b = &buckets[i]; // avoid multiple bounds checks + if (b.hash == hash && b.entry) + if (pure_keyEqual!(K2, K)(key, b.entry.key)) + return b; + if (b.empty) + return null; + i = (i + j) & mask; + } + } + + void grow() pure nothrow @safe + { + // 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() pure nothrow @safe + { + if (dim > INIT_NUM_BUCKETS) + resize(dim / GROW_FAC); + } + + void resize(size_t ndim) pure nothrow @safe + { + auto obuckets = buckets; + buckets = alloc_buckets(ndim); + + foreach (ref b; obuckets[first_used .. $]) + if (b.filled) + buckets[find_slot_insert(b.hash)] = b; + + first_used = 0; + used -= deleted; + deleted = 0; + obuckets.length = 0; // safe to free b/c impossible to reference, but doesn't really free + } + + void clear() pure nothrow + { + // clear all data, but don't change bucket array length + buckets[first_used .. $] = Bucket.init; + deleted = used = 0; + first_used = cast(uint) dim; + } + + size_t calc_hash(K2)(ref K2 key) const nothrow pure @nogc @safe + { + static if(is(K2* : K*)) // ref compatible? + hash_t hash = pure_hashOf!K(key); + else + hash_t hash = pure_hashOf!K2(key); + // highest bit is set to distinguish empty/deleted from filled buckets + return mix(hash) | HASH_FILLED_MARK; + } + + static Bucket[] alloc_buckets(size_t dim) pure nothrow @safe + { + // could allocate with BlkAttr.NO_INTERIOR, but that does not combine + // well with arrays and type info for precise scanning + return new Bucket[dim]; + } +} + +//============================================================================== +// Bucket +//------------------------------------------------------------------------------ + +private struct Bucket(K, V) +{ +private pure nothrow @nogc: + size_t hash; + Entry!(K, V)* entry; + + @property bool empty() const + { + pragma(inline, true); + return hash == HASH_EMPTY; + } + + @property bool deleted() const + { + pragma(inline, true); + return hash == HASH_DELETED; + } + + @property bool filled() const @safe + { + pragma(inline, true); + return cast(ptrdiff_t) hash < 0; + } +} + +//============================================================================== +// 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 next_pow2(const size_t n) pure nothrow @nogc @safe +{ + import urt.internal.bitop : bsr; + + if (!n) + return 1; + + const is_power_of_2 = !((n - 1) & n); + return 1 << (bsr(n) + !is_power_of_2); +} + +pure nothrow @nogc unittest +{ + // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + foreach (const n, const pow2; [1, 1, 2, 4, 4, 8, 8, 8, 8, 16]) + assert(next_pow2(n) == pow2); +} + +//============================================================================== +// API Implementation +//------------------------------------------------------------------------------ + +/** Allocate associative array data. + * Called for `new SomeAA` expression. + * Returns: + * A new associative array. + * Note: + * not supported in CTFE + */ +V[K] _d_aaNew(K, V)() +{ + AA!(K, V) aa; + aa.impl = new Impl!(K,V)(INIT_NUM_BUCKETS); + return *cast(V[K]*)&aa; +} + +/// Determine number of entries in associative array. +/// Note: +/// emulated by the compiler during CTFE +size_t _d_aaLen(K, V)(inout V[K] a) +{ + auto aa = _toAA!(K, V)(a); + return aa ? aa.length : 0; +} + +/****************************** + * Lookup key in aa. + * Called only from implementation of (aa[key]) expressions when value is mutable. + * Params: + * aa = associative array + * key = reference to the key value + * found = returns whether the key was found or a new entry was added + * 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 zero + */ +V* _d_aaGetY(K, V, T : V1[K1], K1, V1, K2)(auto ref scope T aa, auto ref K2 key, out bool found) +{ + ref aax = cast(V[K])cast(V1[K1])aa; // remove outer const from T + return _aaGetX!(K, V)(aax, key, found); +} + +/****************************** + * Lookup key in aa. + * Called only from implementation of require, update and _d_aaGetY + * Params: + * a = associative array + * key = reference 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 V.init + */ +V* _aaGetX(K, V, K2)(auto ref scope V[K] a, auto ref K2 key, out bool found) +{ + ref aa = _refAA!(K, V)(a); + + // lazily alloc implementation + if (aa is null) + { + aa.impl = new Impl!(K, V)(INIT_NUM_BUCKETS); + } + + ref key2 = compat_key!(K)(key); + + // get hash and bucket for key + immutable hash = aa.calc_hash(key2); + + // found a value => return it + if (auto p = aa.find_slot_lookup(hash, key2)) + { + found = true; + return &p.entry.value; + } + + auto pi = aa.find_slot_insert(hash); + if (aa.buckets[pi].deleted) + --aa.deleted; + // check load factor and possibly grow + else if (++aa.used * GROW_DEN > aa.dim * GROW_NUM) + { + aa.grow(); + pi = aa.find_slot_insert(hash); + assert(aa.buckets[pi].empty); + } + + // update search cache and allocate entry + aa.first_used = min(aa.first_used, cast(uint)pi); + ref p = aa.buckets[pi]; + p.hash = hash; + p.entry = _newEntry!(K, V)(key2); + return &p.entry.value; +} + +/****************************** + * Lookup key in aa. + * Called only from implementation of (aa[key]) expressions when value is not mutable. + * Params: + * aa = associative array + * key = key value + * Returns: + * pointer to value if present, null otherwise + */ +auto _d_aaGetRvalueX(K, V, K2)(inout V[K] aa, auto ref scope K2 key) +{ + return _d_aaIn(aa, key); +} + +/// ditto +auto _d_aaGetRvalueX(K, V, K2)(shared(V[K]) aa, auto ref scope K2 key) +{ + // accept shared for backward compatibility, should be deprecated + return cast(shared(V)*)_d_aaIn(cast(V[K]) aa, key); +} + +/// ditto +auto _d_aaGetRvalueX(K, V, K2)(shared const(V[K]) aa, auto ref scope K2 key) +{ + // accept shared for backward compatibility, should be deprecated + return cast(const shared(V)*)_d_aaIn(cast(V[K]) aa, key); +} + +/// ditto +auto _d_aaGetRvalueX(K, V, K2)(immutable(V[K]) aa, auto ref scope K2 key) +{ + // resolve ambiguity for immutable converting to const and shared const + return _d_aaIn((() @trusted => cast(V[K]) aa) (), key); +} + +/*********************************** + * Creates a new associative array of the same size and copies the contents of + * the associative array into it. + * Params: + * a = The associative array. + */ +auto _aaDup(T : V[K], K, V)(T a) +{ + auto aa = _toAA!(K, V)(a); + immutable len = aa.length; + if (len == 0) + return null; + + auto impl = new Impl!(K, V)(aa.dim); + // copy the entries + bool same_hash = aa.hash_fn == impl.hash_fn; // can be different if coming from template/rt + foreach (b; aa.buckets[aa.first_used .. $]) + { + if (!b.filled) + continue; + hash_t hash = same_hash ? b.hash : impl.calc_hash(b.entry.key); + auto pi = impl.find_slot_insert(hash); + auto p = &impl.buckets[pi]; + p.hash = hash; + p.entry = new Entry!(K, V)(b.entry.key, b.entry.value); + impl.first_used = min(impl.first_used, cast(uint)pi); + } + impl.used = cast(uint) len; + return () @trusted { return *cast(Unconstify!V[K]*)&impl; }(); +} + +/****************************** + * Lookup key in aa. + * Called only from implementation of (key in aa) expressions. + * Params: + * a = associative array opaque pointer + * key = reference to the key value + * Returns: + * pointer to value if present, null otherwise + */ +auto _d_aaIn(T : V[K], K, V, K2)(inout T a, auto ref scope K2 key) +{ + auto aa = _toAA!(K, V)(a); + if (aa.empty) + return null; + + ref key2 = compat_key!(K)(key); + + immutable hash = aa.calc_hash(key2); + if (auto p = aa.find_slot_lookup(hash, key2)) + return &p.entry.value; + return null; +} + +// fake purity for backward compatibility with runtime hooks +private extern(C) bool gc_inFinalizer() pure nothrow @safe; + +/// Delete entry scope const AA, return true if it was present +auto _d_aaDel(T : V[K], K, V, K2)(T a, auto ref K2 key) +{ + auto aa = _toAA!(K, V)(a); + if (aa.empty) + return false; + + ref key2 = compat_key!(K)(key); + + immutable hash = aa.calc_hash(key2); + if (auto p = aa.find_slot_lookup(hash, key2)) + { + // 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 && !__ctfe && !gc_inFinalizer()) + aa.shrink(); + + return true; + } + return false; +} + +/// Remove all elements from AA. +void _aaClear(K, V)(V[K] a) +{ + auto aa = _toAA!(K, V)(a); + if (!aa.empty) + { + aa.clear(); + } +} + +/// Rehash AA +V[K] _aaRehash(K, V)(V[K] a) +{ + auto aa = _toAA!(K, V)(a); + if (!aa.empty) + aa.resize(next_pow2(INIT_DEN * aa.length / INIT_NUM)); + return a; +} + +/// Return a GC allocated array of all values +auto _aaValues(K, V)(inout V[K] a) +{ + auto aa = _toAA!(K, V)(a); + if (aa.empty) + return null; + + static if (__traits(compiles, { V val = aa.buckets[0].entry.value; } )) + V[] res; // if value has no const indirections + else + typeof([aa.buckets[0].entry.value]) res; // as mutable as it can get + res = new typeof(res[0])[aa.length]; + + if (false) // never execute, but infer function attributes from this operation + res ~= aa.buckets[0].entry.value; + + size_t i = 0; + foreach (b; aa.buckets[aa.first_used .. $]) + { + if (!b.filled) + continue; + import core.lifetime; + () @trusted { copyEmplace(b.entry.value, res[i++]); }(); + } + return res; +} + +/// Return a GC allocated array of all keys +auto _aaKeys(K, V)(inout V[K] a) +{ + auto aa = _toAA!(K, V)(a); + if (aa.empty) + return null; + + static if (__traits(compiles, { K key = aa.buckets[0].entry.key; } )) + K[] res; // if key has no const indirections + else + typeof([aa.buckets[0].entry.key]) res; // as mutable as it can get + res = new typeof(res[0])[aa.length]; + + if (false) // never execute, but infer function attributes from this operation + res ~= aa.buckets[0].entry.key; + + size_t i = 0; + foreach (b; aa.buckets[aa.first_used .. $]) + { + if (!b.filled) + continue; + // res ~= b.entry.key; + import core.lifetime; + () @trusted { copyEmplace(b.entry.key, res[i++]); }(); + } + return res; +} + +/// foreach opApply over all values +/// Note: +/// emulated by the compiler during CTFE +int _d_aaApply(K, V, DG)(inout V[K] a, DG dg) +{ + auto aa = () @trusted { return cast(AA!(K, V))_toAA!(K, V)(a); }(); + if (aa.empty) + return 0; + + foreach (b; aa.buckets) + { + if (!b.filled) + continue; + if (auto res = dg(b.entry.value)) + return res; + } + return 0; +} + +int _d_aaApply(K, V, DG)(shared V[K] a, DG dg) +{ + return _d_aaApply!(K, V, DG)(cast(V[K]) a, dg); +} + +int _d_aaApply(K, V, DG)(shared const V[K] a, DG dg) +{ + return _d_aaApply!(K, V, DG)(cast(const V[K]) a, dg); +} + +int _d_aaApply(K, V, DG)(immutable V[K] a, DG dg) +{ + return _d_aaApply!(K, V, DG)(cast(const V[K]) a, dg); +} + +/// foreach opApply over all key/value pairs +/// Note: +/// emulated by the compiler during CTFE +int _d_aaApply2(K, V, DG)(inout V[K] a, DG dg) +{ + auto aa = () @trusted { return cast(AA!(K, V))_toAA!(K, V)(a); }(); + if (aa.empty) + return 0; + + foreach (b; aa.buckets) + { + if (!b.filled) + continue; + if (auto res = dg(b.entry.key, b.entry.value)) + return res; + } + return 0; +} + +int _d_aaApply2(K, V, DG)(shared V[K] a, DG dg) +{ + return _d_aaApply2!(K, V, DG)(cast(V[K]) a, dg); +} + +int _d_aaApply2(K, V, DG)(shared const V[K] a, DG dg) +{ + return _d_aaApply2!(K, V, DG)(cast(const V[K]) a, dg); +} + +int _d_aaApply2(K, V, DG)(immutable V[K] a, DG dg) +{ + return _d_aaApply2!(K, V, DG)(cast(const V[K]) a, dg); +} + +/** Construct an associative array of type ti from corresponding keys and values. + * Called for an AA literal `[k1:v1, k2:v2]`. + * Params: + * keys = array of keys + * vals = array of values + * Returns: + * A new associative array opaque pointer, or null if `keys` is empty. + */ +Impl!(K, V)* _d_assocarrayliteralTX(K, V)(K[] keys, V[] vals) +{ + assert(keys.length == vals.length); + + immutable length = keys.length; + + if (!length) + return null; + + auto aa = new Impl!(K, V)(next_pow2(INIT_DEN * length / INIT_NUM)); + size_t duplicates = 0; + foreach (i; 0 .. length) + { + immutable hash = aa.calc_hash(keys[i]); + + auto p = aa.find_slot_lookup!K(hash, keys[i]); + if (p) + { + static if (__traits(compiles, p.entry.value = vals[i])) // immutable? + p.entry.value = vals[i]; + else + p.entry = _newEntry!(K, V)(keys[i], vals[i]); + duplicates++; + continue; + } + auto pi = aa.find_slot_insert(hash); + p = &aa.buckets[pi]; + p.hash = hash; + p.entry = _newEntry!(K, V)(keys[i], vals[i]); // todo: move key and value? + aa.first_used = min(aa.first_used, cast(uint)pi); + } + aa.used = cast(uint) (length - duplicates); + return aa; +} + +/// compares 2 AAs for equality +bool _aaEqual(T : AA!(K, V), K, V)(scope T aa1, scope T aa2) +{ + if (aa1 is aa2) + return true; + + immutable len = aa1.length; + if (len != aa2.length) + return false; + + if (!len) // both empty + return true; + + bool same_hash = aa1.hash_fn == aa2.hash_fn; // can be different if coming from template/rt + // compare the entries + foreach (b1; aa1.buckets[aa1.first_used .. $]) + { + if (!b1.filled) + continue; + hash_t hash = same_hash ? b1.hash : aa2.calc_hash(b1.entry.key); + auto pb2 = aa2.find_slot_lookup!K(hash, b1.entry.key); + if (pb2 is null || !pure_keyEqual!(V, V)(b1.entry.value, pb2.entry.value)) // rarely, inference on opEqual breaks builds here + return false; + } + return true; +} + +/// compares 2 AAs for equality (compiler hook) +bool _d_aaEqual(K, V)(scope const V[K] a1, scope const V[K] a2) +{ + scope aa1 = _toAA!(K, V)(a1); + scope aa2 = _toAA!(K, V)(a2); + return _aaEqual(aa1, aa2); +} + +/// callback from TypeInfo_AssociativeArray.equals (ignore const for now) +bool _aaOpEqual(K, V)(scope /* const */ AA!(K, V)* aa1, scope /* const */ AA!(K, V)* aa2) +{ + return _aaEqual(*aa1, *aa2); +} + +/// compute a hash callback from TypeInfo_AssociativeArray.xtoHash (ignore scope const for now) +hash_t _aaGetHash(K, V)(/* scope const */ AA!(K, V)* paa) +{ + const aa = *paa; + + if (aa.empty) + return 0; + + size_t h; + foreach (b; aa.buckets) + { + // use addition here, so that hash is independent of element order + if (b.filled) + h += hashOf(pure_hashOf!V(b.entry.value), pure_hashOf!K(b.entry.key)); + } + + return h; +} + +/** + * _aaRange implements a ForwardRange + */ +struct AARange(K, V) +{ + alias Key = substInout!K; + alias Value = substInout!V; + + Impl!(Key, Value)* impl; + size_t idx; + alias impl this; +} + +AARange!(K, V) _aaRange(K, V)(V[K] a) +{ + auto aa = _toAA!(K, V)(a); + if (!aa) + return AARange!(K, V)(); + + foreach (i; aa.first_used .. aa.dim) + { + if (aa.buckets[i].filled) + return AARange!(K, V)(aa, i); + } + return AARange!(K, V)(aa, aa.dim); +} + +bool _aaRangeEmpty(K, V)(AARange!(K, V) r) +{ + return r.impl is null || r.idx >= r.dim; +} + +K* _aaRangeFrontKey(K, V)(AARange!(K, V) r) +{ + assert(!_aaRangeEmpty(r)); + if (r.idx >= r.dim) + return null; + auto entry = r.buckets[r.idx].entry; + return entry is null ? null : &r.buckets[r.idx].entry.key; +} + +V* _aaRangeFrontValue(K, V)(AARange!(K, V) r) +{ + assert(!_aaRangeEmpty(r)); + if (r.idx >= r.dim) + return null; + + auto entry = r.buckets[r.idx].entry; + return entry is null ? null : &r.buckets[r.idx].entry.value; +} + +void _aaRangePopFront(K, V)(ref AARange!(K, V) r) +{ + if (r.idx >= r.dim) return; + for (++r.idx; r.idx < r.dim; ++r.idx) + { + if (r.buckets[r.idx].filled) + break; + } +} + +// test postblit for AA literals +// Disabled: uses core.memory/GC and AA syntax that can't resolve inside +// this module when compiled as source (circular object ↔ newaa import). +version (none) unittest +{ + import core.memory; + + static struct T + { + ubyte field; + static size_t postblit, dtor; + this(this) + { + ++postblit; + } + + ~this() + { + ++dtor; + } + } + + T t; + auto aa1 = [0 : t, 1 : t]; + assert(T.dtor == 2 && T.postblit == 4); + aa1[0] = t; + assert(T.dtor == 3 && T.postblit == 5); + + T.dtor = 0; + T.postblit = 0; + + auto aa2 = [0 : t, 1 : t, 0 : t]; // literal with duplicate key => value overwritten + assert(T.dtor == 4 && T.postblit == 6); + + T.dtor = 0; + T.postblit = 0; + + auto aa3 = [t : 0]; + assert(T.dtor == 1 && T.postblit == 2); + aa3[t] = 1; + assert(T.dtor == 1 && T.postblit == 2); + aa3.remove(t); + assert(T.dtor == 1 && T.postblit == 2); + aa3[t] = 2; + assert(T.dtor == 1 && T.postblit == 3); + + // dtor will be called by GC finalizers + aa1 = null; + aa2 = null; + aa3 = null; + auto dtor1 = typeid(TypeInfo_AssociativeArray.Entry!(int, T)).xdtor; + GC.runFinalizers((cast(char*)dtor1)[0 .. 1]); + auto dtor2 = typeid(TypeInfo_AssociativeArray.Entry!(T, int)).xdtor; + GC.runFinalizers((cast(char*)dtor2)[0 .. 1]); + assert(T.dtor == 7 && T.postblit == 3); +} + +// create a binary-compatible AA structure that can be used directly as an +// associative array. +// NOTE: this must only be called during CTFE +AA!(K, V) makeAA(K, V)(V[K] src) @trusted +{ + assert(__ctfe, "makeAA Must only be called at compile time"); + // Iterate the built-in AA directly — .keys/.values are UFCS properties + // that require druntime hooks we don't provide. + K[] keys; + V[] values; + foreach (k, v; src) + { + keys ~= k; + values ~= v; + } + auto impl = _d_assocarrayliteralTX!(K, V)(keys, values); + return AA!(K, V)(impl); +} + +// Disabled: AA-indexing lowering inside this module creates a circular +// import (object → core.internal.newaa → object) that prevents template +// resolution when compiled as source rather than a pre-compiled library. +version (none) unittest +{ + static struct Foo + { + ubyte x; + double d; + } + static int[Foo] utaa = [Foo(1, 2.0) : 5]; + auto k = Foo(1, 2.0); + // verify that getHash doesn't match hashOf for Foo + assert(typeid(Foo).getHash(&k) != hashOf(k)); + assert(utaa[Foo(1, 2.0)] == 5); +} diff --git a/src/urt/internal/bitop.d b/src/urt/internal/bitop.d new file mode 100644 index 0000000..cb68fe7 --- /dev/null +++ b/src/urt/internal/bitop.d @@ -0,0 +1,283 @@ +// TODO: DISSOLVE THIS FILE... +module urt.internal.bitop; + +nothrow @nogc @safe: + +version (D_InlineAsm_X86_64) + version = AsmX86; +else version (D_InlineAsm_X86) + version = AsmX86; + +version (X86_64) + version = AnyX86; +else version (X86) + version = AnyX86; + +// Use to implement 64-bit bitops on 32-bit arch. +private union Split64 +{ + ulong u64; + struct + { + version (LittleEndian) + { + uint lo; + uint hi; + } + else + { + uint hi; + uint lo; + } + } + + pragma(inline, true) + this(ulong u64) @safe pure nothrow @nogc + { + if (__ctfe) + { + lo = cast(uint) u64; + hi = cast(uint) (u64 >>> 32); + } + else + this.u64 = u64; + } +} + +/** + * Scans the bits in v starting with bit 0, looking + * for the first set bit. + * Returns: + * The bit number of the first bit set. + * The return value is undefined if v is zero. + */ +int bsf(uint v) pure +{ + pragma(inline, false); // so intrinsic detection will work + return softBsf!uint(v); +} + +/// ditto +int bsf(ulong v) pure +{ + static if (size_t.sizeof == ulong.sizeof) // 64 bit code gen + { + pragma(inline, false); // so intrinsic detection will work + return softBsf!ulong(v); + } + else + { + const sv = Split64(v); + return (sv.lo == 0)? + bsf(sv.hi) + 32 : + bsf(sv.lo); + } +} + +/** + * Scans the bits in v from the most significant bit + * to the least significant bit, looking + * for the first set bit. + * Returns: + * The bit number of the first bit set. + * The return value is undefined if v is zero. + */ +int bsr(uint v) pure +{ + pragma(inline, false); // so intrinsic detection will work + return softBsr!uint(v); +} + +/// ditto +int bsr(ulong v) pure +{ + static if (size_t.sizeof == ulong.sizeof) // 64 bit code gen + { + pragma(inline, false); // so intrinsic detection will work + return softBsr!ulong(v); + } + else + { + const sv = Split64(v); + return (sv.hi == 0)? + bsr(sv.lo) : + bsr(sv.hi) + 32; + } +} + +private alias softBsf(N) = softScan!(N, true); +private alias softBsr(N) = softScan!(N, false); + +private int softScan(N, bool forward)(N v) pure + if (is(N == uint) || is(N == ulong)) +{ + if (!v) + return -1; + + enum mask(ulong lo) = forward ? cast(N) lo : cast(N)~lo; + enum inc(int up) = forward ? up : -up; + + N x; + int ret; + static if (is(N == ulong)) + { + x = v & mask!0x0000_0000_FFFF_FFFFL; + if (x) + { + v = x; + ret = forward ? 0 : 63; + } + else + ret = forward ? 32 : 31; + + x = v & mask!0x0000_FFFF_0000_FFFFL; + if (x) + v = x; + else + ret += inc!16; + } + else static if (is(N == uint)) + { + x = v & mask!0x0000_FFFF; + if (x) + { + v = x; + ret = forward ? 0 : 31; + } + else + ret = forward ? 16 : 15; + } + else + static assert(false); + + x = v & mask!0x00FF_00FF_00FF_00FFL; + if (x) + v = x; + else + ret += inc!8; + + x = v & mask!0x0F0F_0F0F_0F0F_0F0FL; + if (x) + v = x; + else + ret += inc!4; + + x = v & mask!0x3333_3333_3333_3333L; + if (x) + v = x; + else + ret += inc!2; + + x = v & mask!0x5555_5555_5555_5555L; + if (!x) + ret += inc!1; + + return ret; +} + +/** + * Tests the bit. + */ +int bt(const scope size_t* p, size_t bitnum) pure @system +{ + static if (size_t.sizeof == 8) + return ((p[bitnum >> 6] & (1L << (bitnum & 63)))) != 0; + else static if (size_t.sizeof == 4) + return ((p[bitnum >> 5] & (1 << (bitnum & 31)))) != 0; + else + static assert(0); +} + +/** + * Tests and complements the bit. + */ +int btc(size_t* p, size_t bitnum) pure @system; + +/** + * Tests and resets (sets to 0) the bit. + */ +int btr(size_t* p, size_t bitnum) pure @system; + +/** + * Tests and sets the bit. + */ +int bts(size_t* p, size_t bitnum) pure @system; + +/** + * Swaps bytes in a 2 byte ushort. + */ +pragma(inline, false) +ushort byteswap(ushort x) pure +{ + return cast(ushort) (((x >> 8) & 0xFF) | ((x << 8) & 0xFF00u)); +} + +/** + * Swaps bytes in a 4 byte uint end-to-end. + */ +uint bswap(uint v) pure; + +/** + * Swaps bytes in an 8 byte ulong end-to-end. + */ +ulong bswap(ulong v) pure; + +version (DigitalMars) version (AnyX86) @system // not pure +{ + ubyte inp(uint port_address); + ushort inpw(uint port_address); + uint inpl(uint port_address); + ubyte outp(uint port_address, ubyte value); + ushort outpw(uint port_address, ushort value); + uint outpl(uint port_address, uint value); +} + +/** + * Calculates the number of set bits in an integer. + */ +int popcnt(uint x) pure +{ + return softPopcnt!uint(x); +} + +/// ditto +int popcnt(ulong x) pure +{ + static if (size_t.sizeof == uint.sizeof) + { + const sx = Split64(x); + return softPopcnt!uint(sx.lo) + softPopcnt!uint(sx.hi); + } + else static if (size_t.sizeof == ulong.sizeof) + { + return softPopcnt!ulong(x); + } + else + static assert(false); +} + +version (DigitalMars) version (AnyX86) +{ + ushort _popcnt( ushort x ) pure; + int _popcnt( uint x ) pure; + version (X86_64) + { + int _popcnt( ulong x ) pure; + } +} + +private int softPopcnt(N)(N x) pure + if (is(N == uint) || is(N == ulong)) +{ + enum mask1 = cast(N) 0x5555_5555_5555_5555L; + x = x - ((x>>1) & mask1); + enum mask2a = cast(N) 0xCCCC_CCCC_CCCC_CCCCL; + enum mask2b = cast(N) 0x3333_3333_3333_3333L; + x = ((x & mask2a)>>2) + (x & mask2b); + enum mask4 = cast(N) 0x0F0F_0F0F_0F0F_0F0FL; + x = (x + (x >> 4)) & mask4; + enum shiftbits = is(N == uint)? 24 : 56; + enum maskMul = cast(N) 0x0101_0101_0101_0101L; + x = (x * maskMul) >> shiftbits; + return cast(int) x; +} diff --git a/src/urt/internal/exception.d b/src/urt/internal/exception.d new file mode 100644 index 0000000..b6ed498 --- /dev/null +++ b/src/urt/internal/exception.d @@ -0,0 +1,3600 @@ +/** + * D exception handling — throw, catch, finally. + * + * Ported from druntime for use in uRT. + * Two implementations: + * - Win32 (x86): SEH-based, ported from rt/deh_win32.d + * - Win64/POSIX (x86_64): RBP-chain walking, ported from rt/deh_win64_posix.d + * + * Copyright: Digital Mars 1999-2013 (original), uRT authors (port). + * License: Boost Software License 1.0 + */ +module urt.internal.exception; + +// No top-level gate — individual version blocks guard platform-specific code. + +// ══════════════════════════════════════════════════════════════════════ +// Shared declarations +// ══════════════════════════════════════════════════════════════════════ + +alias ClassInfo = TypeInfo_Class; + +extern(C) int _d_isbaseof(scope ClassInfo oc, scope const ClassInfo c) nothrow @nogc pure @trusted +{ + if (oc is c) + return true; + + do + { + if (oc.base is c) + return true; + + foreach (iface; oc.interfaces) + { + if (iface.classinfo is c || _d_isbaseof(iface.classinfo, c)) + return true; + } + + oc = oc.base; + } + while (oc); + + return false; +} + +// Thread-local trace buffer. _d_createTrace captures here silently; +// terminate() and _d_printLastTrace() read it back for display. +private struct StackTraceData +{ + void*[32] addrs; + ubyte length; +} + +private StackTraceData _tls_trace; // static = TLS in D + +extern(C) void _d_createTrace(Throwable t, void*) nothrow @nogc @trusted +{ + // Capture return addresses into the TLS buffer. No output — + // the trace is only printed on unhandled exceptions (terminate) + // or when explicitly requested via _d_printLastTrace(). + debug + { + _tls_trace.length = 0; + + version (Windows) + { + auto n = rtlCaptureStackBackTrace(2, 32, _tls_trace.addrs.ptr, null); + if (n == 0) + n = cast(ushort) stack_walk64_capture(_tls_trace.addrs); + _tls_trace.length = cast(ubyte) n; + } + else version (D_InlineAsm_X86_64) + { + size_t bp; + asm nothrow @nogc { mov bp, RBP; } + + ubyte n = 0; + foreach (_; 0 .. 32) + { + if (!bp) + break; + auto next_bp = *cast(size_t*) bp; + if (!next_bp || next_bp <= bp) + break; + auto retaddr = *cast(void**)(bp + size_t.sizeof); + if (!retaddr) + break; + _tls_trace.addrs[n++] = retaddr; + bp = next_bp; + } + _tls_trace.length = n; + } + else version (D_InlineAsm_X86) + { + size_t bp; + asm nothrow @nogc { mov bp, EBP; } + + ubyte n = 0; + foreach (_; 0 .. 32) + { + if (!bp) + break; + auto next_bp = *cast(size_t*) bp; + if (!next_bp || next_bp <= bp) + break; + auto retaddr = *cast(void**)(bp + size_t.sizeof); + if (!retaddr) + break; + _tls_trace.addrs[n++] = retaddr; + bp = next_bp; + } + _tls_trace.length = n; + } + else + { + // ARM, AArch64, RISC-V, etc. — use _Unwind_Backtrace + unwind_backtrace(_tls_trace); + } + } +} + +/// Print the stack trace from the most recent throw on this thread. +/// Safe to call from catch blocks, assert handlers, or anywhere else. +extern(C) void _d_printLastTrace(Throwable t) nothrow @nogc @trusted +{ + debug + { + import urt.io : writeln_err, writef_to, WriteTarget; + + if (_tls_trace.length == 0) + return; + + if (t !is null) + { + auto msg = t.msg; + writef_to!(WriteTarget.stderr, true)("Exception: {0}", msg); + } + + writeln_err(" stack trace:"); + + version (Windows) + dbghelp_print_trace(_tls_trace.addrs[0 .. _tls_trace.length]); + else + posix_print_trace(_tls_trace.addrs[0 .. _tls_trace.length]); + } +} + +// ── Stack trace support (Windows, debug only) ──────────────────────── +// +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// TODO: DbgHelp is NOT thread-safe. All calls to SymFromAddr, +// SymGetLineFromAddr64, SymInitialize, and StackWalk64 must be +// serialized with a CRITICAL_SECTION (or equivalent) once this +// program uses threads. Without this, concurrent exceptions from +// multiple threads WILL crash or corrupt DbgHelp's internal state. +// Both DMD and LDC druntime protect all DbgHelp access with a mutex. +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// +version (Windows) debug +{ + // --- DbgHelp types --------------------------------------------------- + + private struct SYMBOL_INFOA + { + uint SizeOfStruct; + uint TypeIndex; + ulong[2] Reserved; + uint Index; + uint Size; + ulong ModBase; + uint Flags; + ulong Value; + ulong Address; + uint Register; + uint Scope; + uint Tag; + uint NameLen; + uint MaxNameLen; + char[1] Name; // variable-length + } + + private struct IMAGEHLP_LINEA64 + { + uint SizeOfStruct; + void* Key; + uint LineNumber; + const(char)* FileName; + ulong Address; + } + + // --- StackWalk64 types ----------------------------------------------- + + private alias HANDLE = void*; + private enum AddrModeFlat = 3; + + private struct ADDRESS64 + { + ulong Offset; + ushort Segment; + uint Mode; + } + + private struct STACKFRAME64 + { + ADDRESS64 AddrPC; + ADDRESS64 AddrReturn; + ADDRESS64 AddrFrame; + ADDRESS64 AddrStack; + ADDRESS64 AddrBStore; + void* FuncTableEntry; + ulong[4] Params; + int Far; + int Virtual; + ulong[3] Reserved; + ubyte[96] KdHelp; // KDHELP64, opaque + } + + // CONTEXT — opaque aligned buffer, accessed via offset constants. + version (Win64) + { + private enum CONTEXT_SIZE = 1232; + private enum CTX_FLAGS_OFF = 48; + private enum CTX_IP_OFF = 248; // Rip + private enum CTX_SP_OFF = 152; // Rsp + private enum CTX_FP_OFF = 160; // Rbp + private enum CTX_FULL = 0x10000B; + private enum MACHINE_TYPE = 0x8664; // IMAGE_FILE_MACHINE_AMD64 + } + else + { + private enum CONTEXT_SIZE = 716; + private enum CTX_FLAGS_OFF = 0; + private enum CTX_IP_OFF = 184; // Eip + private enum CTX_SP_OFF = 196; // Esp + private enum CTX_FP_OFF = 180; // Ebp + private enum CTX_FULL = 0x10007; + private enum MACHINE_TYPE = 0x014C; // IMAGE_FILE_MACHINE_I386 + } + + // --- Function pointer types ------------------------------------------ + + extern (Windows) nothrow @nogc + { + private alias SymInitializeFn = int function(HANDLE, const(char)*, int); + private alias SymSetOptionsFn = uint function(uint); + private alias SymFromAddrFn = int function(HANDLE, ulong, ulong*, SYMBOL_INFOA*); + private alias SymGetLineFromAddr64Fn = int function(HANDLE, ulong, uint*, IMAGEHLP_LINEA64*); + private alias FuncTableAccessFn = void* function(HANDLE, ulong); + private alias GetModuleBaseFn = ulong function(HANDLE, ulong); + private alias StackWalk64Fn = int function( + uint machineType, HANDLE hProcess, HANDLE hThread, + STACKFRAME64* stackFrame, void* contextRecord, + void* readMemory, FuncTableAccessFn funcTableAccess, + GetModuleBaseFn getModuleBase, void* translateAddress); + } + + // --- Globals --------------------------------------------------------- + + private __gshared bool _dbg_inited; + private __gshared bool _dbg_available; + private __gshared HANDLE _dbg_process; + private __gshared SymFromAddrFn _sym_from_addr; + private __gshared SymGetLineFromAddr64Fn _sym_get_line; + private __gshared StackWalk64Fn _stack_walk64; + private __gshared FuncTableAccessFn _func_table_access64; + private __gshared GetModuleBaseFn _get_module_base64; + + // --- Initialization -------------------------------------------------- + + private void dbghelp_init() nothrow @nogc @trusted + { + if (_dbg_inited) + return; + _dbg_inited = true; + + auto hDbg = loadLibraryA("dbghelp.dll"); + if (hDbg is null) + return; + + auto sym_init = cast(SymInitializeFn) getProcAddress(hDbg, "SymInitialize"); + auto sym_set_opt = cast(SymSetOptionsFn) getProcAddress(hDbg, "SymSetOptions"); + _sym_from_addr = cast(SymFromAddrFn) getProcAddress(hDbg, "SymFromAddr"); + _sym_get_line = cast(SymGetLineFromAddr64Fn) getProcAddress(hDbg, "SymGetLineFromAddr64"); + _stack_walk64 = cast(StackWalk64Fn) getProcAddress(hDbg, "StackWalk64"); + _func_table_access64 = cast(FuncTableAccessFn) getProcAddress(hDbg, "SymFunctionTableAccess64"); + _get_module_base64 = cast(GetModuleBaseFn) getProcAddress(hDbg, "SymGetModuleBase64"); + + if (sym_init is null || sym_set_opt is null || _sym_from_addr is null) + return; + + // SYMOPT_DEFERRED_LOAD | SYMOPT_LOAD_LINES + sym_set_opt(0x00000004 | 0x00000010); + + _dbg_process = cast(HANDLE) cast(size_t)-1; // GetCurrentProcess() pseudo-handle + if (!sym_init(_dbg_process, null, 1)) // fInvadeProcess = TRUE + return; + + _dbg_available = true; + } + + // --- Kernel32/ntdll imports ------------------------------------------ + + pragma(mangle, "LoadLibraryA") + extern (Windows) private void* loadLibraryA(const(char)*) nothrow @nogc; + + pragma(mangle, "GetProcAddress") + extern (Windows) private void* getProcAddress(void*, const(char)*) nothrow @nogc; + + pragma(mangle, "RtlCaptureStackBackTrace") + extern (Windows) private ushort rtlCaptureStackBackTrace( + uint FramesToSkip, uint FramesToCapture, void** BackTrace, uint* BackTraceHash) nothrow @nogc; + + pragma(mangle, "RtlCaptureContext") + extern (Windows) private void rtlCaptureContext(void* contextRecord) nothrow @nogc; + + pragma(mangle, "GetCurrentThread") + extern (Windows) private HANDLE getCurrentThread() nothrow @nogc; + + // --- StackWalk64 fallback -------------------------------------------- + + private size_t stack_walk64_capture(ref void*[32] addrs) nothrow @nogc @trusted + { + dbghelp_init(); + + if (_stack_walk64 is null || _func_table_access64 is null || _get_module_base64 is null) + return 0; + + align(16) ubyte[CONTEXT_SIZE] ctx = 0; + *cast(uint*)(ctx.ptr + CTX_FLAGS_OFF) = CTX_FULL; + rtlCaptureContext(ctx.ptr); + + STACKFRAME64 sf; + version (Win64) + { + sf.AddrPC.Offset = *cast(ulong*)(ctx.ptr + CTX_IP_OFF); + sf.AddrFrame.Offset = *cast(ulong*)(ctx.ptr + CTX_FP_OFF); + sf.AddrStack.Offset = *cast(ulong*)(ctx.ptr + CTX_SP_OFF); + } + else + { + sf.AddrPC.Offset = *cast(uint*)(ctx.ptr + CTX_IP_OFF); + sf.AddrFrame.Offset = *cast(uint*)(ctx.ptr + CTX_FP_OFF); + sf.AddrStack.Offset = *cast(uint*)(ctx.ptr + CTX_SP_OFF); + } + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrFrame.Mode = AddrModeFlat; + sf.AddrStack.Mode = AddrModeFlat; + + auto hThread = getCurrentThread(); + size_t n = 0; + + // Skip internal frames (_d_createTrace + stack_walk64_capture + RtlCaptureContext) + uint skip = 3; + + while (n < 32) + { + if (!_stack_walk64(MACHINE_TYPE, _dbg_process, hThread, + &sf, ctx.ptr, null, _func_table_access64, _get_module_base64, null)) + break; + if (sf.AddrPC.Offset == 0) + break; + if (skip > 0) + { + --skip; + continue; + } + addrs[n++] = cast(void*) cast(size_t) sf.AddrPC.Offset; + } + return n; + } + + // --- Symbol resolution & printing ------------------------------------ + + private void dbghelp_print_trace(void*[] addrs) nothrow @nogc @trusted + { + import urt.io : writef_to, writeln_err, WriteTarget; + import urt.mem : strlen; + import urt.string : endsWith; + + dbghelp_init(); + + // Skip frames up through _d_throw_exception (internal machinery). + // LDC druntime does the same — clears accumulated frames when it + // hits _d_throw_exception, so the trace starts at the throw site. + size_t start = 0; + if (_dbg_available) + { + foreach (i, addr; addrs) + { + align(8) ubyte[SYMBOL_INFOA.sizeof + 256] sym_buf = 0; + auto p_sym = cast(SYMBOL_INFOA*) sym_buf.ptr; + p_sym.SizeOfStruct = SYMBOL_INFOA.sizeof; + p_sym.MaxNameLen = 256; + + ulong disp; + if (_sym_from_addr(_dbg_process, cast(ulong) addr, &disp, p_sym)) + { + auto name = p_sym.Name.ptr[0 .. strlen(p_sym.Name.ptr)]; + if (name.endsWith("_d_throw_exception")) + start = i + 1; + } + } + } + + enum addr_fmt = size_t.sizeof == 4 ? "08x" : "016x"; + + foreach (addr; addrs[start .. $]) + { + if (_dbg_available) + { + align(8) ubyte[SYMBOL_INFOA.sizeof + 256] sym_buf = 0; + auto p_sym = cast(SYMBOL_INFOA*) sym_buf.ptr; + p_sym.SizeOfStruct = SYMBOL_INFOA.sizeof; + p_sym.MaxNameLen = 256; + + ulong displacement64; + auto a = cast(ulong) addr; + if (_sym_from_addr(_dbg_process, a, &displacement64, p_sym)) + { + auto name = p_sym.Name.ptr[0 .. strlen(p_sym.Name.ptr)]; + + uint displacement32; + IMAGEHLP_LINEA64 line_info; + line_info.SizeOfStruct = IMAGEHLP_LINEA64.sizeof; + + if (_sym_get_line !is null && + _sym_get_line(_dbg_process, a, &displacement32, &line_info)) + { + auto fname = line_info.FileName[0 .. strlen(line_info.FileName)]; + writef_to!(WriteTarget.stderr, true)(" {0}+0x{1:x} [{2}:{3}]", + name, displacement64, fname, line_info.LineNumber); + } + else + { + writef_to!(WriteTarget.stderr, true)(" {0}+0x{1:x}", name, displacement64); + } + continue; + } + } + + // Fallback: raw address + writef_to!(WriteTarget.stderr, true)(" 0x{0:" ~ addr_fmt ~ "}", cast(size_t)addr); + } + } +} + +// ── Stack trace support (POSIX, debug only) ────────────────────────── +// +// Uses dladdr for symbol names and DWARF .debug_line for file:line info. +// Ported from druntime's core.internal.backtrace.{dwarf,elf} and +// core.internal.elf.{io,dl}. +// +version (Windows) {} else debug +{ + import urt.io : write_err, writeln_err, writef_to, WriteTarget; + import urt.mem : strlen, memcpy; + + import core.sys.posix.dlfcn : dladdr, Dl_info; + import core.sys.posix.fcntl : open, O_RDONLY; + import core.sys.posix.unistd : close, lseek, readlink, sysconf, _SC_PAGE_SIZE; + import core.sys.posix.sys.mman : mmap, munmap, PROT_READ, MAP_PRIVATE, MAP_FAILED; + import core.sys.posix.sys.types : off_t; + + private enum SEEK_END = 2; + + // ── _Unwind_Backtrace capture (ARM, AArch64, RISC-V) ──────────── + + version (D_InlineAsm_X86_64) {} else version (D_InlineAsm_X86) {} else + { + private alias _Unwind_Trace_Fn = extern(C) int function(void* ctx, void* data) nothrow @nogc; + + extern(C) private int _Unwind_Backtrace(_Unwind_Trace_Fn, void*) nothrow @nogc; + extern(C) private size_t _Unwind_GetIP(void*) nothrow @nogc; + + private struct UnwindState { StackTraceData* trace; ubyte skip; } + + extern(C) private int unwind_trace_callback(void* ctx, void* data) nothrow @nogc + { + auto s = cast(UnwindState*) data; + if (s.skip > 0) { s.skip--; return 0; } + if (s.trace.length >= 32) return 1; + auto ip = _Unwind_GetIP(ctx); + if (!ip) return 1; + s.trace.addrs[s.trace.length++] = cast(void*) ip; + return 0; + } + + private void unwind_backtrace(ref StackTraceData trace) nothrow @nogc @trusted + { + UnwindState state = UnwindState(&trace, 2); + _Unwind_Backtrace(&unwind_trace_callback, &state); + } + } + + // ── Minimal ELF types ──────────────────────────────────────────── + + version (D_LP64) + { + private struct Elf_Ehdr + { + ubyte[16] e_ident; + ushort e_type; + ushort e_machine; + uint e_version; + ulong e_entry; + ulong e_phoff; + ulong e_shoff; + uint e_flags; + ushort e_ehsize; + ushort e_phentsize; + ushort e_phnum; + ushort e_shentsize; + ushort e_shnum; + ushort e_shstrndx; + } + + private struct Elf_Shdr + { + uint sh_name; + uint sh_type; + ulong sh_flags; + ulong sh_addr; + ulong sh_offset; + ulong sh_size; + uint sh_link; + uint sh_info; + ulong sh_addralign; + ulong sh_entsize; + } + + private struct Elf_Phdr + { + uint p_type; + uint p_flags; + ulong p_offset; + ulong p_vaddr; + ulong p_paddr; + ulong p_filesz; + ulong p_memsz; + ulong p_align; + } + } + else + { + private struct Elf_Ehdr + { + ubyte[16] e_ident; + ushort e_type; + ushort e_machine; + uint e_version; + uint e_entry; + uint e_phoff; + uint e_shoff; + uint e_flags; + ushort e_ehsize; + ushort e_phentsize; + ushort e_phnum; + ushort e_shentsize; + ushort e_shnum; + ushort e_shstrndx; + } + + private struct Elf_Shdr + { + uint sh_name; + uint sh_type; + uint sh_flags; + uint sh_addr; + uint sh_offset; + uint sh_size; + uint sh_link; + uint sh_info; + uint sh_addralign; + uint sh_entsize; + } + + private struct Elf_Phdr + { + uint p_type; + uint p_offset; + uint p_vaddr; + uint p_paddr; + uint p_filesz; + uint p_memsz; + uint p_flags; + uint p_align; + } + } + + private enum EI_MAG0 = 0; + private enum EI_CLASS = 4; + private enum EI_DATA = 5; + private enum ELFMAG = "\x7fELF"; + private enum ET_DYN = 3; + private enum SHF_COMPRESSED = 0x800; + + version (D_LP64) + private enum ELFCLASS_NATIVE = 2; // ELFCLASS64 + else + private enum ELFCLASS_NATIVE = 1; // ELFCLASS32 + + version (LittleEndian) + private enum ELFDATA_NATIVE = 1; // ELFDATA2LSB + else + private enum ELFDATA_NATIVE = 2; // ELFDATA2MSB + + // ── dl_iterate_phdr for base address ───────────────────────────── + + private struct dl_phdr_info + { + size_t dlpi_addr; + const(char)* dlpi_name; + const(Elf_Phdr)* dlpi_phdr; + ushort dlpi_phnum; + } + + private alias dl_iterate_phdr_callback_t = extern(C) int function(dl_phdr_info*, size_t, void*) nothrow @nogc; + + extern(C) private int dl_iterate_phdr(dl_iterate_phdr_callback_t callback, void* data) nothrow @nogc; + + private size_t get_executable_base_address() nothrow @nogc @trusted + { + size_t result = 0; + + extern(C) static int callback(dl_phdr_info* info, size_t, void* data) nothrow @nogc + { + // First entry is the executable itself + *cast(size_t*) data = info.dlpi_addr; + return 1; // stop iteration + } + + dl_iterate_phdr(&callback, &result); + return result; + } + + // ── Memory-mapped file region ──────────────────────────────────── + + private struct MappedRegion + { + const(ubyte)* data; + size_t mapped_size; + + nothrow @nogc @trusted: + + static MappedRegion map(int fd, size_t offset, size_t length) + { + if (fd == -1 || length == 0) + return MappedRegion.init; + + auto pgsz = cast(size_t) sysconf(_SC_PAGE_SIZE); + if (cast(int) pgsz <= 0) + pgsz = 4096; + + const page_off = offset / pgsz; + const diff = offset - page_off * pgsz; + const needed = length + diff; + const pages = (needed + pgsz - 1) / pgsz; + const msize = pages * pgsz; + + auto p = mmap(null, msize, PROT_READ, MAP_PRIVATE, fd, cast(off_t)(page_off * pgsz)); + if (p is MAP_FAILED) + return MappedRegion.init; + + return MappedRegion(cast(const(ubyte)*) p + diff, msize); + } + + void unmap() + { + if (data !is null) + { + auto pgsz = cast(size_t) sysconf(_SC_PAGE_SIZE); + if (cast(int) pgsz <= 0) + pgsz = 4096; + // Align back to page boundary + auto base = cast(void*)(cast(size_t) data & ~(pgsz - 1)); + munmap(base, mapped_size); + data = null; + } + } + } + + // ── ELF self-reader ────────────────────────────────────────────── + + private struct ElfSelf + { + int fd = -1; + MappedRegion ehdr_region; + const(Elf_Ehdr)* ehdr; + + nothrow @nogc @trusted: + + static ElfSelf open() + { + ElfSelf self; + + // Read /proc/self/exe path + char[512] pathbuf = void; + auto n = readlink("/proc/self/exe", pathbuf.ptr, pathbuf.length - 1); + if (n <= 0) + return self; + pathbuf[n] = 0; + + self.fd = .open(pathbuf.ptr, O_RDONLY); + if (self.fd == -1) + return self; + + // Map the ELF header + self.ehdr_region = MappedRegion.map(self.fd, 0, Elf_Ehdr.sizeof); + if (self.ehdr_region.data is null) + { + .close(self.fd); + self.fd = -1; + return self; + } + + self.ehdr = cast(const(Elf_Ehdr)*) self.ehdr_region.data; + + // Validate ELF magic, class, and byte order + if (self.ehdr.e_ident[0..4] != cast(const(ubyte)[4]) ELFMAG + || self.ehdr.e_ident[EI_CLASS] != ELFCLASS_NATIVE + || self.ehdr.e_ident[EI_DATA] != ELFDATA_NATIVE) + { + self.close(); + return ElfSelf.init; + } + + return self; + } + + void close() + { + ehdr_region.unmap(); + ehdr = null; + if (fd != -1) { .close(fd); fd = -1; } + } + + bool valid() const { return fd != -1 && ehdr !is null; } + + /// Find a section by name, return its offset and size. + bool find_section(const(char)[] name, out size_t offset, out size_t size) + { + if (!valid()) + return false; + + // Map section headers + auto shdr_total = cast(size_t) ehdr.e_shnum * Elf_Shdr.sizeof; + auto shdr_region = MappedRegion.map(fd, cast(size_t) ehdr.e_shoff, shdr_total); + if (shdr_region.data is null) + return false; + scope(exit) shdr_region.unmap(); + + auto shdrs = (cast(const(Elf_Shdr)*) shdr_region.data)[0 .. ehdr.e_shnum]; + + // Map string table section + if (ehdr.e_shstrndx >= ehdr.e_shnum) + return false; + auto strtab_shdr = &shdrs[ehdr.e_shstrndx]; + auto strtab_region = MappedRegion.map(fd, + cast(size_t) strtab_shdr.sh_offset, + cast(size_t) strtab_shdr.sh_size); + if (strtab_region.data is null) + return false; + scope(exit) strtab_region.unmap(); + + auto strtab = cast(const(char)*) strtab_region.data; + + // Search for the named section + foreach (ref shdr; shdrs) + { + if (shdr.sh_name >= strtab_shdr.sh_size) + continue; + auto sec_name = strtab + shdr.sh_name; + auto sec_name_len = strlen(sec_name); + if (sec_name_len == name.length && sec_name[0 .. sec_name_len] == name) + { + if (shdr.sh_flags & SHF_COMPRESSED) + return false; // compressed debug sections not supported + offset = cast(size_t) shdr.sh_offset; + size = cast(size_t) shdr.sh_size; + return true; + } + } + return false; + } + } + + // ── DWARF .debug_line types and constants ──────────────────────── + + private struct LocationInfo + { + int file = -1; + int line = -1; + } + + private struct SourceFile + { + const(char)[] file; + size_t dir_index; // 1-based + } + + private struct LineNumberProgram + { + ulong unit_length; + ushort dwarf_version; + ubyte address_size; + ubyte segment_selector_size; + ulong header_length; + ubyte minimum_instruction_length; + ubyte maximum_operations_per_instruction; + bool default_is_statement; + byte line_base; + ubyte line_range; + ubyte opcode_base; + const(ubyte)[] standard_opcode_lengths; + // Directory and file tables stored as slices into scratch buffers. + // The caller owns the scratch; the LineNumberProgram just borrows. + const(char)[][] include_directories; + size_t num_dirs; + SourceFile[] source_files; + size_t num_files; + const(ubyte)[] program; + } + + private struct StateMachine + { + const(void)* address; + uint operation_index = 0; + uint file_index = 1; + int line = 1; + uint column = 0; + bool is_statement; + bool is_end_sequence = false; + } + + private enum StandardOpcode : ubyte + { + extended_op = 0, + copy = 1, + advance_pc = 2, + advance_line = 3, + set_file = 4, + set_column = 5, + negate_statement = 6, + set_basic_block = 7, + const_add_pc = 8, + fixed_advance_pc = 9, + set_prologue_end = 10, + set_epilogue_begin = 11, + set_isa = 12, + } + + private enum ExtendedOpcode : ubyte + { + end_sequence = 1, + set_address = 2, + define_file = 3, + set_discriminator = 4, + } + + // ── LEB128 and DWARF helpers ───────────────────────────────────── + + private T dw_read(T)(ref const(ubyte)[] buf) nothrow @nogc @trusted + { + if (buf.length < T.sizeof) + return T.init; + version (X86_64) + T result = *cast(const(T)*) buf.ptr; + else version (X86) + T result = *cast(const(T)*) buf.ptr; + else + { + T result = void; + memcpy(&result, buf.ptr, T.sizeof); + } + buf = buf[T.sizeof .. $]; + return result; + } + + private const(char)[] dw_read_stringz(ref const(ubyte)[] buf) nothrow @nogc @trusted + { + auto p = cast(const(char)*) buf.ptr; + auto len = strlen(p); + buf = buf[len + 1 .. $]; + return p[0 .. len]; + } + + private ulong dw_read_uleb128(ref const(ubyte)[] buf) nothrow @nogc + { + ulong val = 0; + uint shift = 0; + while (buf.length > 0) + { + ubyte b = buf[0]; buf = buf[1 .. $]; + val |= cast(ulong)(b & 0x7f) << shift; + if ((b & 0x80) == 0) break; + shift += 7; + } + return val; + } + + private long dw_read_sleb128(ref const(ubyte)[] buf) nothrow @nogc + { + long val = 0; + uint shift = 0; + ubyte b; + while (buf.length > 0) + { + b = buf[0]; buf = buf[1 .. $]; + val |= cast(long)(b & 0x7f) << shift; + shift += 7; + if ((b & 0x80) == 0) break; + } + if (shift < 64 && (b & 0x40) != 0) + val |= -(cast(long) 1 << shift); + return val; + } + + // ── DWARF v5 entry format ──────────────────────────────────────── + + private enum DW_LNCT : ushort + { + path = 1, + directory_index = 2, + } + + private enum DW_FORM : ubyte + { + data1 = 11, + data2 = 5, + data4 = 6, + data8 = 7, + data16 = 30, + string_ = 8, + strp = 14, + line_strp = 31, + udata = 15, + block = 9, + strx = 26, + strx1 = 37, + strx2 = 38, + strx3 = 39, + strx4 = 40, + sec_offset = 23, + sdata = 13, + flag = 12, + flag_present = 25, + } + + private struct EntryFormatPair + { + DW_LNCT type; + DW_FORM form; + } + + /// Skip a DWARF form value we don't care about. + private void dw_skip_form(ref const(ubyte)[] data, DW_FORM form, bool is64bit) nothrow @nogc + { + with (DW_FORM) switch (form) + { + case strp, line_strp, sec_offset: + data = data[is64bit ? 8 : 4 .. $]; break; + case data1, strx1, flag, flag_present: + data = data[1 .. $]; break; + case data2, strx2: + data = data[2 .. $]; break; + case strx3: + data = data[3 .. $]; break; + case data4, strx4: + data = data[4 .. $]; break; + case data8: + data = data[8 .. $]; break; + case data16: + data = data[16 .. $]; break; + case udata, strx, sdata: + dw_read_uleb128(data); break; + case block: + auto length = cast(size_t) dw_read_uleb128(data); + data = data[length .. $]; break; + default: + break; + } + } + + // ── Read DWARF line number program header ──────────────────────── + + // Scratch buffers allocated on the stack for directory/file tables. + // 256 entries each should be more than enough for any compilation unit. + private enum MAX_DIRS = 256; + private enum MAX_FILES = 512; + + private LineNumberProgram dw_read_line_number_program(ref const(ubyte)[] data) nothrow @nogc @trusted + { + const original_data = data; + LineNumberProgram lp; + + bool is_64bit_dwarf = false; + lp.unit_length = dw_read!uint(data); + if (lp.unit_length == uint.max) + { + is_64bit_dwarf = true; + lp.unit_length = dw_read!ulong(data); + } + + const version_field_offset = cast(size_t)(data.ptr - original_data.ptr); + lp.dwarf_version = dw_read!ushort(data); + + if (lp.dwarf_version >= 5) + { + lp.address_size = dw_read!ubyte(data); + lp.segment_selector_size = dw_read!ubyte(data); + } + + lp.header_length = is_64bit_dwarf ? dw_read!ulong(data) : dw_read!uint(data); + + const min_insn_field_offset = cast(size_t)(data.ptr - original_data.ptr); + lp.minimum_instruction_length = dw_read!ubyte(data); + lp.maximum_operations_per_instruction = (lp.dwarf_version >= 4) ? dw_read!ubyte(data) : 1; + lp.default_is_statement = (dw_read!ubyte(data) != 0); + lp.line_base = dw_read!byte(data); + lp.line_range = dw_read!ubyte(data); + lp.opcode_base = dw_read!ubyte(data); + + lp.standard_opcode_lengths = data[0 .. lp.opcode_base - 1]; + data = data[lp.opcode_base - 1 .. $]; + + if (lp.dwarf_version >= 5) + { + // DWARF v5: directory format + entries + auto num_pairs = dw_read!ubyte(data); + EntryFormatPair[8] dir_fmt = void; + foreach (i; 0 .. num_pairs) + { + if (i < 8) + { + dir_fmt[i].type = cast(DW_LNCT) dw_read_uleb128(data); + dir_fmt[i].form = cast(DW_FORM) dw_read_uleb128(data); + } + } + + lp.num_dirs = cast(size_t) dw_read_uleb128(data); + // Caller must provide scratch buffers; we use __gshared static for simplicity. + foreach (d; 0 .. lp.num_dirs) + { + foreach (p; 0 .. num_pairs) + { + if (p < 8 && dir_fmt[p].type == DW_LNCT.path && dir_fmt[p].form == DW_FORM.string_) + { + if (d < MAX_DIRS) + _dir_scratch[d] = dw_read_stringz(data); + else + dw_read_stringz(data); + } + else if (p < 8) + dw_skip_form(data, dir_fmt[p].form, is_64bit_dwarf); + } + } + if (lp.num_dirs > MAX_DIRS) lp.num_dirs = MAX_DIRS; + lp.include_directories = _dir_scratch[0 .. lp.num_dirs]; + + // File format + entries + num_pairs = dw_read!ubyte(data); + EntryFormatPair[8] file_fmt = void; + foreach (i; 0 .. num_pairs) + { + if (i < 8) + { + file_fmt[i].type = cast(DW_LNCT) dw_read_uleb128(data); + file_fmt[i].form = cast(DW_FORM) dw_read_uleb128(data); + } + } + + lp.num_files = cast(size_t) dw_read_uleb128(data); + foreach (f; 0 .. lp.num_files) + { + SourceFile sf; + sf.file = ""; + foreach (p; 0 .. num_pairs) + { + if (p < 8 && file_fmt[p].type == DW_LNCT.path && file_fmt[p].form == DW_FORM.string_) + sf.file = dw_read_stringz(data); + else if (p < 8 && file_fmt[p].type == DW_LNCT.directory_index) + { + if (file_fmt[p].form == DW_FORM.data1) + sf.dir_index = dw_read!ubyte(data); + else if (file_fmt[p].form == DW_FORM.data2) + sf.dir_index = dw_read!ushort(data); + else if (file_fmt[p].form == DW_FORM.udata) + sf.dir_index = cast(size_t) dw_read_uleb128(data); + else + dw_skip_form(data, file_fmt[p].form, is_64bit_dwarf); + sf.dir_index++; // DWARF v5 indices are 0-based, normalize to 1-based + } + else if (p < 8) + dw_skip_form(data, file_fmt[p].form, is_64bit_dwarf); + } + if (f < MAX_FILES) + _file_scratch[f] = sf; + } + if (lp.num_files > MAX_FILES) lp.num_files = MAX_FILES; + lp.source_files = _file_scratch[0 .. lp.num_files]; + } + else + { + // DWARF v3/v4: NUL-terminated sequences + lp.num_dirs = 0; + while (data.length > 0 && data[0] != 0) + { + auto dir = dw_read_stringz(data); + if (lp.num_dirs < MAX_DIRS) + _dir_scratch[lp.num_dirs++] = dir; + } + if (data.length > 0) data = data[1 .. $]; // skip NUL terminator + lp.include_directories = _dir_scratch[0 .. lp.num_dirs]; + + lp.num_files = 0; + while (data.length > 0 && data[0] != 0) + { + SourceFile sf; + sf.file = dw_read_stringz(data); + sf.dir_index = cast(size_t) dw_read_uleb128(data); + dw_read_uleb128(data); // last modification time + dw_read_uleb128(data); // file length + if (lp.num_files < MAX_FILES) + _file_scratch[lp.num_files++] = sf; + } + if (data.length > 0) data = data[1 .. $]; // skip NUL terminator + lp.source_files = _file_scratch[0 .. lp.num_files]; + } + + const program_start = cast(size_t)(min_insn_field_offset + lp.header_length); + const program_end = cast(size_t)(version_field_offset + lp.unit_length); + if (program_start <= original_data.length && program_end <= original_data.length) + lp.program = original_data[program_start .. program_end]; + + data = (program_end <= original_data.length) ? original_data[program_end .. $] : null; + + return lp; + } + + // Static scratch buffers for DWARF parsing (debug-only, no allocator needed). + private __gshared const(char)[][MAX_DIRS] _dir_scratch; + private __gshared SourceFile[MAX_FILES] _file_scratch; + + // ── DWARF state machine — resolve addresses to file:line ───────── + + private struct ResolvedLocation + { + const(char)[] file; + const(char)[] dir; + int line = -1; + } + + /// Resolve an array of addresses to file:line using .debug_line data. + private void dw_resolve_addresses( + const(ubyte)[] debug_line_data, + void*[] addresses, + ResolvedLocation[] results, + size_t base_address) nothrow @nogc @trusted + { + size_t found = 0; + const num_addrs = addresses.length; + + while (debug_line_data.length > 0 && found < num_addrs) + { + auto lp = dw_read_line_number_program(debug_line_data); + if (lp.program.length == 0) + break; + + StateMachine machine; + machine.is_statement = lp.default_is_statement; + + LocationInfo last_loc = LocationInfo(-1, -1); + const(void)* last_address; + + const(ubyte)[] prog = lp.program; + while (prog.length > 0) + { + size_t advance_addr(size_t op_advance) + { + const inc = lp.minimum_instruction_length * + ((machine.operation_index + op_advance) / lp.maximum_operations_per_instruction); + machine.address += inc; + machine.operation_index = + (machine.operation_index + op_advance) % lp.maximum_operations_per_instruction; + return inc; + } + + void emit_row(bool is_end) + { + auto addr = machine.address + base_address; + + foreach (idx; 0 .. num_addrs) + { + if (results[idx].line != -1) + continue; + auto target = addresses[idx]; + + void apply_loc(LocationInfo loc) + { + auto file_idx = loc.file - (lp.dwarf_version < 5 ? 1 : 0); + if (file_idx >= 0 && file_idx < lp.num_files) + { + results[idx].file = lp.source_files[file_idx].file; + auto di = lp.source_files[file_idx].dir_index; + if (di > 0 && di <= lp.num_dirs) + results[idx].dir = lp.include_directories[di - 1]; + } + results[idx].line = loc.line; + found++; + } + + if (target == addr) + apply_loc(LocationInfo(machine.file_index, machine.line)); + else if (last_address !is null && target > last_address && target < addr) + apply_loc(last_loc); + } + + if (is_end) + last_address = null; + else + { + last_address = addr; + last_loc = LocationInfo(machine.file_index, machine.line); + } + } + + ubyte opcode = prog[0]; prog = prog[1 .. $]; + + if (opcode >= lp.opcode_base) + { + // Special opcode + opcode -= lp.opcode_base; + advance_addr(opcode / lp.line_range); + machine.line += lp.line_base + (opcode % lp.line_range); + emit_row(false); + } + else if (opcode == 0) + { + // Extended opcode + auto len = cast(size_t) dw_read_uleb128(prog); + if (prog.length == 0) break; + ubyte eopcode = prog[0]; prog = prog[1 .. $]; + + switch (eopcode) + { + case ExtendedOpcode.end_sequence: + machine.is_end_sequence = true; + emit_row(true); + machine = StateMachine.init; + machine.is_statement = lp.default_is_statement; + break; + case ExtendedOpcode.set_address: + machine.address = dw_read!(const(void)*)(prog); + machine.operation_index = 0; + break; + case ExtendedOpcode.set_discriminator: + dw_read_uleb128(prog); + break; + default: + if (len > 1) + prog = prog[len - 1 .. $]; + break; + } + } + else switch (opcode) with (StandardOpcode) + { + case copy: + emit_row(false); + break; + case advance_pc: + advance_addr(cast(size_t) dw_read_uleb128(prog)); + break; + case advance_line: + machine.line += cast(int) dw_read_sleb128(prog); + break; + case set_file: + machine.file_index = cast(uint) dw_read_uleb128(prog); + break; + case set_column: + machine.column = cast(uint) dw_read_uleb128(prog); + break; + case negate_statement: + machine.is_statement = !machine.is_statement; + break; + case set_basic_block: + break; + case const_add_pc: + advance_addr((255 - lp.opcode_base) / lp.line_range); + break; + case fixed_advance_pc: + machine.address += dw_read!ushort(prog); + machine.operation_index = 0; + break; + case set_prologue_end: + case set_epilogue_begin: + break; + case set_isa: + dw_read_uleb128(prog); + break; + default: + // Unknown standard opcode: skip according to standard_opcode_lengths + if (opcode > 0 && opcode <= lp.standard_opcode_lengths.length) + { + foreach (_; 0 .. lp.standard_opcode_lengths[opcode - 1]) + dw_read_uleb128(prog); + } + break; + } + } + } + } + + // ── Minimal D symbol demangler ───────────────────────────────────── + // + // Extracts the dot-separated qualified name from a D mangled symbol. + // Does not decode type signatures — just returns e.g. "module.Class.func". + + private const(char)[] demangle_symbol( + const(char)[] mangled, return ref char[512] buf) nothrow @nogc @trusted + { + import urt.array : beginsWith; + import urt.conv : parse_uint; + + if (mangled.length < 3 || !mangled.beginsWith("_D")) + return mangled; + + auto src = mangled[2 .. $]; + size_t pos = 0; + bool first = true; + + while (src.length > 0) + { + auto ch = src[0]; + + if (ch >= '1' && ch <= '9') + { + // LName: decimal length followed by that many characters + size_t taken; + size_t len = cast(size_t)parse_uint(src, &taken); + src = src[taken .. $]; + if (len > src.length || pos + len + 1 > buf.length) + break; + + if (!first) + buf[pos++] = '.'; + first = false; + + buf[pos .. pos + len] = src[0 .. len]; + pos += len; + src = src[len .. $]; + } + else if (ch == 'Q') + { + // Back reference: base-26 offset pointing to an earlier LName. + auto q_pos = cast(size_t)(src.ptr - mangled.ptr); + src = src[1 .. $]; + + size_t ref_val = 0; + while (src.length > 0 && src[0] >= 'A' && src[0] <= 'Z') + { + ref_val = ref_val * 26 + (src[0] - 'A'); + src = src[1 .. $]; + } + if (src.length > 0 && src[0] >= 'a' && src[0] <= 'z') + { + ref_val = ref_val * 26 + (src[0] - 'a'); + src = src[1 .. $]; + } + else + break; // malformed + + if (ref_val >= q_pos) + break; + auto target = mangled[q_pos - ref_val .. $]; + if (target.length == 0 || target[0] < '1' || target[0] > '9') + break; + + // Parse LName at target + size_t taken; + size_t len = cast(size_t)parse_uint(target, &taken); + target = target[taken .. $]; + if (len > target.length || pos + len + 1 > buf.length) + break; + + if (!first) + buf[pos++] = '.'; + first = false; + + buf[pos .. pos + len] = target[0 .. len]; + pos += len; + } + else if (ch == '_' && src.length >= 3 && src[1] == '_' + && (src[2] == 'T' || src[2] == 'U')) + { + // Template instance __T/__U: extract name, skip args until Z + src = src[3 .. $]; + + if (src.length > 0 && src[0] >= '1' && src[0] <= '9') + { + size_t taken; + size_t len = cast(size_t)parse_uint(src, &taken); + src = src[taken .. $]; + if (len <= src.length && pos + len + 1 <= buf.length) + { + if (!first) + buf[pos++] = '.'; + first = false; + + buf[pos .. pos + len] = src[0 .. len]; + pos += len; + src = src[len .. $]; + } + } + + // Skip template args until matching Z + int depth = 1; + while (src.length > 0 && depth > 0) + { + if (src[0] == 'Z') + --depth; + else if (src.length >= 3 && src[0] == '_' && src[1] == '_' + && (src[2] == 'T' || src[2] == 'U')) + { + ++depth; + src = src[2 .. $]; + } + src = src[1 .. $]; + } + } + else if (ch == '0') + src = src[1 .. $]; // anonymous — skip + else + break; // type signature — done + } + + if (pos == 0) + return mangled; + + // Append $TypeSignature if there's anything left + if (src.length > 0 && pos + 1 + src.length <= buf.length) + { + buf[pos++] = '$'; + buf[pos .. pos + src.length] = src[]; + pos += src.length; + } + + return buf[0 .. pos]; + } + + // ── Print formatted trace ──────────────────────────────────────── + + private void posix_print_trace(void*[] addrs) nothrow @nogc @trusted + { + if (addrs.length == 0) + return; + + // Skip internal frames (find _d_throw_exception / _d_throwdwarf) + import urt.string : endsWith; + size_t start = 0; + foreach (i, addr; addrs) + { + Dl_info info = void; + if (dladdr(addr, &info) && info.dli_sname !is null) + { + auto sym = info.dli_sname[0 .. strlen(info.dli_sname)]; + if (sym.endsWith("_d_throw_exception") || sym.endsWith("_d_throwdwarf")) + start = i + 1; + } + } + + // Try DWARF .debug_line resolution + ResolvedLocation[32] locations; + foreach (ref loc; locations) + loc = ResolvedLocation.init; + + auto elf = ElfSelf.open(); + scope(exit) elf.close(); + + if (elf.valid()) + { + size_t dbg_offset, dbg_size; + if (elf.find_section(".debug_line", dbg_offset, dbg_size)) + { + auto dbg_region = MappedRegion.map(elf.fd, dbg_offset, dbg_size); + if (dbg_region.data !is null) + { + scope(exit) dbg_region.unmap(); + + auto base_addr = (elf.ehdr.e_type == ET_DYN) + ? get_executable_base_address() : cast(size_t) 0; + + auto dbg_data = dbg_region.data[0 .. dbg_size]; + dw_resolve_addresses(dbg_data, + addrs[0 .. addrs.length], + locations[0 .. addrs.length], + base_addr); + } + } + } + + // Print each frame + foreach (i; start .. addrs.length) + { + auto addr = addrs[i]; + ref loc = locations[i]; + + // Symbol name via dladdr + Dl_info info = void; + const(char)* symname = null; + size_t sym_offset = 0; + if (dladdr(addr, &info) && info.dli_sname !is null) + { + symname = info.dli_sname; + sym_offset = cast(size_t) addr - cast(size_t) info.dli_saddr; + } + + // Format: file:line symbol+0xoffset [0xaddress] + // or: ??:? symbol+0xoffset [0xaddress] + // or: ??:? 0xaddress + + if (loc.line >= 0) + { + // Have file:line + if (loc.dir.length > 0 && loc.dir[$ - 1] != '/') + writef_to!(WriteTarget.stderr, false)(" {0}/{1}:{2}", loc.dir, loc.file, loc.line); + else if (loc.dir.length > 0) + writef_to!(WriteTarget.stderr, false)(" {0}{1}:{2}", loc.dir, loc.file, loc.line); + else + writef_to!(WriteTarget.stderr, false)(" {0}:{1}", loc.file, loc.line); + } + else + write_err(" ??:?"); + + if (symname !is null) + { + auto sym = symname[0 .. strlen(symname)]; + char[512] dbuf = void; + auto demangled = demangle_symbol(sym, dbuf); + writef_to!(WriteTarget.stderr, false)(" {0}+0x{1:x}", demangled, sym_offset); + } + + enum addr_fmt = size_t.sizeof == 4 ? "08x" : "016x"; + writef_to!(WriteTarget.stderr, true)(" [0x{0:" ~ addr_fmt ~ "}]", cast(size_t)addr); + + // Stop at _Dmain + if (symname !is null) + { + auto sym = symname[0 .. strlen(symname)]; + if (sym == "_Dmain") + break; + } + } + } +} // version (!Windows) debug + +private void terminate() nothrow @nogc @trusted +{ + import urt.io : writeln_err; + writeln_err("Unhandled exception -- no catch handler found, terminating."); + + debug + { + if (_tls_trace.length > 0) + { + writeln_err(" stack trace:"); + version (Windows) + dbghelp_print_trace(_tls_trace.addrs[0 .. _tls_trace.length]); + else + posix_print_trace(_tls_trace.addrs[0 .. _tls_trace.length]); + } + } + + version (D_InlineAsm_X86_64) + asm nothrow @nogc { hlt; } + else version (D_InlineAsm_X86) + asm nothrow @nogc { hlt; } + else + { + import urt.internal.stdc : abort; + abort(); + } +} + +// ══════════════════════════════════════════════════════════════════════ +// Platform-specific implementations +// ══════════════════════════════════════════════════════════════════════ + +version (GDC) +{ + static assert(false, "!!"); +} +else version (LDC) +{ + +// ────────────────────────────────────────────────────────────────────── +// LDC MSVC SEH exception handling +// +// LDC on Windows uses the MSVC C++ exception infrastructure. +// _d_throw_exception builds MSVC-compatible type metadata and calls +// RaiseException(). The MSVC CRT's table-based SEH unwinds the stack +// and delivers exceptions to catch landing pads, which call +// _d_eh_enter_catch to extract the D Throwable from the SEH record. +// +// Ported from ldc/eh_msvc.d in LDC's druntime. +// ────────────────────────────────────────────────────────────────────── + +version (Windows) +{ + +// --- Windows ABI types --------------------------------------------------- + +private alias DWORD = uint; +private alias ULONG_PTR = size_t; + +extern (Windows) void RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, + DWORD nNumberOfArguments, ULONG_PTR* lpArguments) nothrow; + +// --- MSVC SEH structures ------------------------------------------------- + +// On Win64, type info pointers are 32-bit offsets from a heap base. +version (Win64) + private struct ImgPtr(T) { uint offset; } +else + private alias ImgPtr(T) = T*; + +private alias PMFN = ImgPtr!(void function(void*)); + +private struct TypeDescriptor +{ + uint hash; + void* spare; + char[1] name; // variable-size, zero-terminated +} + +private struct PMD +{ + int mdisp; + int pdisp; + int vdisp; +} + +private struct CatchableType +{ + uint properties; + ImgPtr!TypeDescriptor pType; + PMD thisDisplacement; + int sizeOrOffset; + PMFN copyFunction; +} + +private enum CT_IsSimpleType = 0x00000001; + +private struct CatchableTypeArray +{ + int nCatchableTypes; + ImgPtr!CatchableType[1] arrayOfCatchableTypes; // variable size +} + +private struct _ThrowInfo +{ + uint attributes; + PMFN pmfnUnwind; + PMFN pForwardCompat; + ImgPtr!CatchableTypeArray pCatchableTypeArray; +} + +private struct CxxExceptionInfo +{ + size_t Magic; + Throwable* pThrowable; + _ThrowInfo* ThrowInfo; + version (Win64) void* ImgBase; +} + +private enum int STATUS_MSC_EXCEPTION = 0xe0000000 | ('m' << 16) | ('s' << 8) | ('c' << 0); +private enum EXCEPTION_NONCONTINUABLE = 0x01; +private enum EH_MAGIC_NUMBER1 = 0x19930520; + + +// --- EH Heap (image-relative pointer support) ---------------------------- + +version (Win64) +{ + private struct EHHeap + { + void* base; + size_t capacity; + size_t length; + + void initialize(size_t initial_capacity) nothrow @nogc + { + import urt.mem : malloc; + base = malloc(initial_capacity); + capacity = initial_capacity; + length = size_t.sizeof; // offset 0 reserved (null sentinel) + } + + size_t alloc(size_t size) nothrow @nogc + { + import urt.mem : malloc, memcpy; + auto offset = length; + enum align_mask = size_t.sizeof - 1; + auto new_length = (length + size + align_mask) & ~align_mask; + auto new_capacity = capacity; + while (new_length > new_capacity) + new_capacity *= 2; + if (new_capacity != capacity) + { + auto new_base = malloc(new_capacity); + memcpy(new_base, base, length); + // Old base leaks — may be referenced by in-flight exceptions. + base = new_base; + capacity = new_capacity; + } + length = new_length; + return offset; + } + } + + private __gshared EHHeap _eh_heap; + + private ImgPtr!T eh_malloc(T)(size_t size = T.sizeof) nothrow @nogc + { + return ImgPtr!T(cast(uint) _eh_heap.alloc(size)); + } + + private T* to_pointer(T)(ImgPtr!T img_ptr) nothrow @nogc + { + return cast(T*)(cast(ubyte*) _eh_heap.base + img_ptr.offset); + } +} +else // Win32 +{ + private ImgPtr!T eh_malloc(T)(size_t size = T.sizeof) nothrow @nogc + { + import urt.mem : malloc; + return cast(T*) malloc(size); + } + + private T* to_pointer(T)(T* img_ptr) nothrow @nogc + { + return img_ptr; + } +} + + +// --- ThrowInfo cache (simple linear-scan arrays) ------------------------- +// No mutex — OpenWatt's exception paths are single-threaded. + +private enum EH_CACHE_SIZE = 64; + +private __gshared ClassInfo[EH_CACHE_SIZE] _throw_info_keys; +private __gshared ImgPtr!_ThrowInfo[EH_CACHE_SIZE] _throw_info_vals; +private __gshared size_t _throw_info_len; + +private __gshared ClassInfo[EH_CACHE_SIZE] _catchable_keys; +private __gshared ImgPtr!CatchableType[EH_CACHE_SIZE] _catchable_vals; +private __gshared size_t _catchable_len; + +private __gshared bool _eh_initialized; + +private void ensure_eh_init() nothrow @nogc +{ + if (_eh_initialized) + return; + version (Win64) + _eh_heap.initialize(0x10000); + _eh_initialized = true; +} + + +// --- ThrowInfo generation ------------------------------------------------ + +private ImgPtr!CatchableType get_catchable_type(ClassInfo ti) nothrow @nogc +{ + import urt.mem : memcpy; + + foreach (i; 0 .. _catchable_len) + if (_catchable_keys[i] is ti) + return _catchable_vals[i]; + + const sz = TypeDescriptor.sizeof + ti.name.length + 1; + auto td = eh_malloc!TypeDescriptor(sz); + auto ptd = td.to_pointer; + + ptd.hash = 0; + ptd.spare = null; + ptd.name.ptr[0] = 'D'; + memcpy(ptd.name.ptr + 1, ti.name.ptr, ti.name.length); + ptd.name.ptr[ti.name.length + 1] = 0; + + auto ct = eh_malloc!CatchableType(); + ct.to_pointer[0] = CatchableType( + CT_IsSimpleType, td, PMD(0, -1, 0), + cast(int) size_t.sizeof, PMFN.init); + + if (_catchable_len < EH_CACHE_SIZE) + { + _catchable_keys[_catchable_len] = ti; + _catchable_vals[_catchable_len] = ct; + ++_catchable_len; + } + return ct; +} + +private ImgPtr!_ThrowInfo get_throw_info(ClassInfo ti) nothrow @nogc +{ + foreach (i; 0 .. _throw_info_len) + if (_throw_info_keys[i] is ti) + return _throw_info_vals[i]; + + int classes = 0; + for (ClassInfo tic = ti; tic !is null; tic = tic.base) + ++classes; + + const arr_size = int.sizeof + classes * ImgPtr!(CatchableType).sizeof; + ImgPtr!CatchableTypeArray cta = eh_malloc!CatchableTypeArray(arr_size); + to_pointer(cta).nCatchableTypes = classes; + + size_t c = 0; + for (ClassInfo tic = ti; tic !is null; tic = tic.base) + cta.to_pointer.arrayOfCatchableTypes.ptr[c++] = get_catchable_type(tic); + + auto tinf = eh_malloc!_ThrowInfo(); + *(tinf.to_pointer) = _ThrowInfo(0, PMFN.init, PMFN.init, cta); + + if (_throw_info_len < EH_CACHE_SIZE) + { + _throw_info_keys[_throw_info_len] = ti; + _throw_info_vals[_throw_info_len] = tinf; + ++_throw_info_len; + } + return tinf; +} + + +// --- Exception stack (thread-local) -------------------------------------- + +private struct ExceptionStack +{ +nothrow @nogc: + + void push(Throwable e) + { + if (_length == _cap) + grow(); + _p[_length++] = e; + } + + Throwable pop() + { + return _p[--_length]; + } + + void shrink(size_t sz) + { + while (_length > sz) + _p[--_length] = null; + } + + ref inout(Throwable) opIndex(size_t idx) inout + { + return _p[idx]; + } + + size_t find(Throwable e) + { + for (size_t i = _length; i > 0;) + if (_p[--i] is e) + return i; + return ~cast(size_t) 0; + } + + @property size_t length() const { return _length; } + +private: + + void grow() + { + import urt.mem : malloc, free, memcpy; + immutable ncap = _cap ? 2 * _cap : 16; + auto p = cast(Throwable*) malloc(ncap * size_t.sizeof); + if (_length > 0) + memcpy(p, _p, _length * size_t.sizeof); + if (_p !is null) + free(_p); + _p = p; + _cap = ncap; + } + + size_t _length; + Throwable* _p; + size_t _cap; +} + +private ExceptionStack _exception_stack; + + +// --- Exception chaining -------------------------------------------------- + +private Throwable chain_exceptions(Throwable e, Throwable t) nothrow @nogc +{ + if (!cast(Error) e) + if (auto err = cast(Error) t) + { + err.bypassedException = e; + return err; + } + return Throwable.chainTogether(e, t); +} + + +// --- Core SEH API -------------------------------------------------------- + +extern (C) void _d_throw_exception(Throwable throwable) +{ + if (throwable is null || typeid(throwable) is null) + { + terminate(); + assert(0); + } + + auto refcount = throwable.refcount(); + if (refcount) + throwable.refcount() = refcount + 1; + + ensure_eh_init(); + + _exception_stack.push(throwable); + _d_createTrace(throwable, null); + + CxxExceptionInfo info; + info.Magic = EH_MAGIC_NUMBER1; + info.pThrowable = &throwable; + info.ThrowInfo = get_throw_info(typeid(throwable)).to_pointer; + version (Win64) + info.ImgBase = _eh_heap.base; + + RaiseException(STATUS_MSC_EXCEPTION, EXCEPTION_NONCONTINUABLE, + info.sizeof / size_t.sizeof, cast(ULONG_PTR*) &info); +} + +extern (C) Throwable _d_eh_enter_catch(void* ptr, ClassInfo catch_type) +{ + if (ptr is null) + return null; + + auto e = *(cast(Throwable*) ptr); + size_t pos = _exception_stack.find(e); + if (pos >= _exception_stack.length()) + return null; // not a D exception + + auto caught = e; + + // Chain inner unhandled exceptions as collateral + for (size_t p = pos + 1; p < _exception_stack.length(); ++p) + e = chain_exceptions(e, _exception_stack[p]); + _exception_stack.shrink(pos); + + if (e !is caught) + { + if (_d_isbaseof(typeid(e), catch_type)) + *cast(Throwable*) ptr = e; + else + _d_throw_exception(e); // rethrow collateral + } + return e; +} + +extern (C) bool _d_enter_cleanup(void* ptr) nothrow @nogc @trusted +{ + // Prevents LLVM from optimizing away cleanup (finally) blocks. + return true; +} + +extern (C) void _d_leave_cleanup(void* ptr) nothrow @nogc @trusted +{ +} + +} // version (Windows) +else +{ +// Non-Windows LDC: DWARF EH is in the shared block below. +} // else (non-Windows LDC) + +} +else version (Win32) // DMD Win32 +{ + +// ────────────────────────────────────────────────────────────────────── +// Win32 SEH-based exception handling +// ────────────────────────────────────────────────────────────────────── + +// Windows types (inlined to avoid core.sys.windows dependency) +alias DWORD = uint; +alias BYTE = ubyte; +alias PVOID = void*; +alias ULONG_PTR = size_t; + +enum size_t EXCEPTION_MAXIMUM_PARAMETERS = 15; +enum DWORD EXCEPTION_NONCONTINUABLE = 1; +enum EXCEPTION_UNWIND = 6; +enum EXCEPTION_COLLATERAL = 0x100; + +enum DWORD STATUS_DIGITAL_MARS_D_EXCEPTION = + (3 << 30) | (1 << 29) | (0 << 28) | ('D' << 16) | 1; + +struct EXCEPTION_RECORD +{ + DWORD ExceptionCode; + DWORD ExceptionFlags; + EXCEPTION_RECORD* ExceptionRecord; + PVOID ExceptionAddress; + DWORD NumberParameters; + ULONG_PTR[EXCEPTION_MAXIMUM_PARAMETERS] ExceptionInformation; +} + +enum MAXIMUM_SUPPORTED_EXTENSION = 512; + +struct FLOATING_SAVE_AREA +{ + DWORD ControlWord, StatusWord, TagWord; + DWORD ErrorOffset, ErrorSelector; + DWORD DataOffset, DataSelector; + BYTE[80] RegisterArea; + DWORD Cr0NpxState; +} + +struct CONTEXT +{ + DWORD ContextFlags; + DWORD Dr0, Dr1, Dr2, Dr3, Dr6, Dr7; + FLOATING_SAVE_AREA FloatSave; + DWORD SegGs, SegFs, SegEs, SegDs; + DWORD Edi, Esi, Ebx, Edx, Ecx, Eax; + DWORD Ebp, Eip, SegCs, EFlags, Esp, SegSs; + BYTE[MAXIMUM_SUPPORTED_EXTENSION] ExtendedRegisters; +} + +struct EXCEPTION_POINTERS +{ + EXCEPTION_RECORD* ExceptionRecord; + CONTEXT* ContextRecord; +} + +enum EXCEPTION_DISPOSITION +{ + ExceptionContinueExecution, + ExceptionContinueSearch, + ExceptionNestedException, + ExceptionCollidedUnwind, +} + +alias LanguageSpecificHandler = extern(C) + EXCEPTION_DISPOSITION function( + EXCEPTION_RECORD* exceptionRecord, + DEstablisherFrame* frame, + CONTEXT* context, + void* dispatcherContext); + +extern (Windows) void RaiseException(DWORD, DWORD, DWORD, void*); +extern (Windows) void RtlUnwind(void* targetFrame, void* targetIp, + EXCEPTION_RECORD* pExceptRec, void* valueForEAX); + +extern(C) extern __gshared DWORD _except_list; // FS:[0] + +// Data structures — compiler-generated exception handler tables (Win32) + +struct DEstablisherFrame +{ + DEstablisherFrame* prev; + LanguageSpecificHandler handler; + DWORD table_index; + DWORD ebp; +} + +struct DHandlerInfo +{ + int prev_index; + uint cioffset; // offset to DCatchInfo data from start of table + void* finally_code; // pointer to finally code (!=null if try-finally) +} + +struct DHandlerTable +{ + void* fptr; // pointer to start of function + uint espoffset; + uint retoffset; + DHandlerInfo[1] handler_info; +} + +struct DCatchBlock +{ + ClassInfo type; + uint bpoffset; // EBP offset of catch var + void* code; // catch handler code pointer +} + +struct DCatchInfo +{ + uint ncatches; + DCatchBlock[1] catch_block; +} + +// InFlight exception list (per-stack, swapped on fiber switches) + +EXCEPTION_RECORD* inflight_exception_list = null; + +extern(C) void* _d_eh_swapContext(void* newContext) nothrow @nogc +{ + auto old = inflight_exception_list; + inflight_exception_list = cast(EXCEPTION_RECORD*) newContext; + return old; +} + +private EXCEPTION_RECORD* skip_collateral_exceptions(EXCEPTION_RECORD* n) nothrow @nogc @trusted +{ + while (n.ExceptionRecord && n.ExceptionFlags & EXCEPTION_COLLATERAL) + n = n.ExceptionRecord; + return n; +} + +// SEH to D exception translation + +private Throwable _d_translate_se_to_d_exception( + EXCEPTION_RECORD* exceptionRecord, CONTEXT*) nothrow @nogc @trusted +{ + if (exceptionRecord.ExceptionCode == STATUS_DIGITAL_MARS_D_EXCEPTION) + return cast(Throwable) cast(void*)(exceptionRecord.ExceptionInformation[0]); + + // Non-D (hardware) exceptions: cannot create Error objects without GC. + terminate(); + assert(0); +} + +// _d_throwc — throw a D exception via Windows SEH + +private void throw_impl(Throwable h) @trusted +{ + auto refcount = h.refcount(); + if (refcount) + h.refcount() = refcount + 1; + + _d_createTrace(h, null); + RaiseException(STATUS_DIGITAL_MARS_D_EXCEPTION, + EXCEPTION_NONCONTINUABLE, 1, cast(void*)&h); +} + +extern(C) void _d_throwc(Throwable h) @trusted +{ + version (D_InlineAsm_X86) + asm + { + naked; + enter 0, 0; + mov EAX, [EBP + 8]; + call throw_impl; + leave; + ret; + } +} + +// _d_framehandler — SEH frame handler called by OS for each frame. +// The handler table address is passed in EAX by compiler-generated thunks. + +extern(C) EXCEPTION_DISPOSITION _d_framehandler( + EXCEPTION_RECORD* exceptionRecord, + DEstablisherFrame* frame, + CONTEXT* context, + void* dispatcherContext) @trusted +{ + DHandlerTable* handler_table; + asm { mov handler_table, EAX; } + + if (exceptionRecord.ExceptionFlags & EXCEPTION_UNWIND) + { + // Unwind phase: call all finally blocks in this frame + _d_local_unwind(handler_table, frame, -1, &unwindCollisionExceptionHandler); + } + else + { + // Search phase: look for a matching catch handler + + EXCEPTION_RECORD* master = null; + ClassInfo master_class_info = null; + + int prev_ndx; + for (auto ndx = frame.table_index; ndx != -1; ndx = prev_ndx) + { + auto phi = &handler_table.handler_info.ptr[ndx]; + prev_ndx = phi.prev_index; + + if (phi.cioffset) + { + auto pci = cast(DCatchInfo*)(cast(ubyte*) handler_table + phi.cioffset); + auto ncatches = pci.ncatches; + + foreach (i; 0 .. ncatches) + { + auto pcb = &pci.catch_block.ptr[i]; + + // Walk the collateral exception chain to find the master + EXCEPTION_RECORD* er = exceptionRecord; + master = null; + master_class_info = null; + + for (;;) + { + if (er.ExceptionCode == STATUS_DIGITAL_MARS_D_EXCEPTION) + { + ClassInfo ci = (**(cast(ClassInfo**)(er.ExceptionInformation[0]))); + if (!master && !(er.ExceptionFlags & EXCEPTION_COLLATERAL)) + { + master = er; + master_class_info = ci; + break; + } + if (_d_isbaseof(ci, typeid(Error))) + { + master = er; + master_class_info = ci; + } + } + else + { + // Non-D exception — cannot translate without GC + terminate(); + } + + if (!(er.ExceptionFlags & EXCEPTION_COLLATERAL)) + break; + + if (er.ExceptionRecord) + er = er.ExceptionRecord; + else + er = inflight_exception_list; + } + + if (_d_isbaseof(master_class_info, pcb.type)) + { + // Found matching catch handler + + auto original_exception = skip_collateral_exceptions(exceptionRecord); + if (original_exception.ExceptionRecord is null + && !(exceptionRecord is inflight_exception_list)) + original_exception.ExceptionRecord = inflight_exception_list; + inflight_exception_list = exceptionRecord; + + // Global unwind: call finally blocks in intervening frames + _d_global_unwind(frame, exceptionRecord); + + // Local unwind: call finally blocks skipped in this frame + _d_local_unwind(handler_table, frame, ndx, + &searchCollisionExceptionHandler); + + frame.table_index = prev_ndx; + + // Build D exception chain from SEH records + EXCEPTION_RECORD* z = exceptionRecord; + Throwable prev = null; + Error master_error = null; + + for (;;) + { + Throwable w = _d_translate_se_to_d_exception(z, context); + if (z == master && (z.ExceptionFlags & EXCEPTION_COLLATERAL)) + master_error = cast(Error) w; + prev = Throwable.chainTogether(w, prev); + if (!(z.ExceptionFlags & EXCEPTION_COLLATERAL)) + break; + z = z.ExceptionRecord; + } + + Throwable pti; + if (master_error) + { + master_error.bypassedException = prev; + pti = master_error; + } + else + pti = prev; + + inflight_exception_list = z.ExceptionRecord; + + // Initialize catch variable and jump to handler + int regebp = cast(int)&frame.ebp; + *cast(Object*)(regebp + pcb.bpoffset) = pti; + + { + uint catch_esp; + alias fp_t = void function(); + fp_t catch_addr = cast(fp_t) pcb.code; + catch_esp = regebp - handler_table.espoffset - fp_t.sizeof; + asm + { + mov EAX, catch_esp; + mov ECX, catch_addr; + mov [EAX], ECX; + mov EBP, regebp; + mov ESP, EAX; + ret; + } + } + } + } + } + } + } + return EXCEPTION_DISPOSITION.ExceptionContinueSearch; +} + +// Exception filter for __try/__except around Dmain + +extern(C) int _d_exception_filter(EXCEPTION_POINTERS* eptrs, + int retval, Object* exception_object) @trusted +{ + *exception_object = _d_translate_se_to_d_exception( + eptrs.ExceptionRecord, eptrs.ContextRecord); + return retval; +} + +// Collision exception handlers + +extern(C) EXCEPTION_DISPOSITION searchCollisionExceptionHandler( + EXCEPTION_RECORD* exceptionRecord, + DEstablisherFrame*, + CONTEXT*, + void* dispatcherContext) @trusted +{ + if (!(exceptionRecord.ExceptionFlags & EXCEPTION_UNWIND)) + { + auto n = skip_collateral_exceptions(exceptionRecord); + n.ExceptionFlags |= EXCEPTION_COLLATERAL; + return EXCEPTION_DISPOSITION.ExceptionContinueSearch; + } + // Collision during SEARCH phase — restart from 'frame' + *(cast(void**) dispatcherContext) = dispatcherContext; // frame + return EXCEPTION_DISPOSITION.ExceptionCollidedUnwind; +} + +extern(C) EXCEPTION_DISPOSITION unwindCollisionExceptionHandler( + EXCEPTION_RECORD* exceptionRecord, + DEstablisherFrame* frame, + CONTEXT*, + void* dispatcherContext) @trusted +{ + if (!(exceptionRecord.ExceptionFlags & EXCEPTION_UNWIND)) + { + auto n = skip_collateral_exceptions(exceptionRecord); + n.ExceptionFlags |= EXCEPTION_COLLATERAL; + return EXCEPTION_DISPOSITION.ExceptionContinueSearch; + } + // Collision during UNWIND phase — restart from 'frame.prev' + *(cast(void**) dispatcherContext) = frame.prev; + return EXCEPTION_DISPOSITION.ExceptionCollidedUnwind; +} + +// Local unwind — run finally blocks in the current frame + +extern(C) void _d_local_unwind(DHandlerTable* handler_table, + DEstablisherFrame* frame, int stop_index, + LanguageSpecificHandler collision_handler) @trusted +{ + // Install collision handler on SEH chain + asm + { + push dword ptr -1; + push dword ptr 0; + push collision_handler; + push dword ptr FS:_except_list; + mov FS:_except_list, ESP; + } + + for (auto i = frame.table_index; i != -1 && i != stop_index;) + { + auto phi = &handler_table.handler_info.ptr[i]; + i = phi.prev_index; + + if (phi.finally_code) + { + auto catch_ebp = &frame.ebp; + auto blockaddr = phi.finally_code; + asm + { + push EBX; + mov EBX, blockaddr; + push EBP; + mov EBP, catch_ebp; + call EBX; + pop EBP; + pop EBX; + } + } + } + + // Remove collision handler from SEH chain + asm + { + pop FS:_except_list; + add ESP, 12; + } +} + +// Global unwind — thin wrapper around RtlUnwind + +extern(C) int _d_global_unwind(DEstablisherFrame* pFrame, + EXCEPTION_RECORD* eRecord) @trusted +{ + asm + { + naked; + push EBP; + mov EBP, ESP; + push ECX; + push EBX; + push ESI; + push EDI; + push EBP; + push 0; + push dword ptr 12[EBP]; // eRecord + call __system_unwind; + jmp __unwind_exit; + __system_unwind: + push dword ptr 8[EBP]; // pFrame + call RtlUnwind; + __unwind_exit: + pop EBP; + pop EDI; + pop ESI; + pop EBX; + pop ECX; + mov ESP, EBP; + pop EBP; + ret; + } +} + +// Local unwind for goto/return across finally blocks + +extern(C) void _d_local_unwind2() @trusted +{ + asm + { + naked; + jmp _d_localUnwindForGoto; + } +} + +extern(C) void _d_localUnwindForGoto(DHandlerTable* handler_table, + DEstablisherFrame* frame, int stop_index) @trusted +{ + _d_local_unwind(handler_table, frame, stop_index, + &searchCollisionExceptionHandler); +} + +// Monitor handler stubs (for synchronized blocks) + +extern(C) EXCEPTION_DISPOSITION _d_monitor_handler( + EXCEPTION_RECORD* exceptionRecord, + DEstablisherFrame*, + CONTEXT*, + void*) @trusted +{ + if (exceptionRecord.ExceptionFlags & EXCEPTION_UNWIND) + { + // TODO: _d_monitorexit(cast(Object)cast(void*)frame.table_index); + } + return EXCEPTION_DISPOSITION.ExceptionContinueSearch; +} + +extern(C) void _d_monitor_prolog(void*, void*, Object) @trusted +{ + // TODO: _d_monitorenter(h); +} + +extern(C) void _d_monitor_epilog(void*, void*, Object) @trusted +{ + // TODO: _d_monitorexit(h); +} + + +} +else version (Win64) +{ + +// ────────────────────────────────────────────────────────────────────── +// Win64 — RBP-chain walking with ._deh section tables +// ────────────────────────────────────────────────────────────────────── + +// Data structures — compiler-generated exception handler tables + +struct DHandlerInfo +{ + uint offset; // offset from function address to start of guarded section + uint endoffset; // offset of end of guarded section + int prev_index; // previous table index + uint cioffset; // offset to DCatchInfo data from start of table (!=0 if try-catch) + size_t finally_offset; // offset to finally code to execute (!=0 if try-finally) +} + +struct DHandlerTable +{ + uint espoffset; // offset of ESP from EBP + uint retoffset; // offset from start of function to return code + size_t nhandlers; // dimension of handler_info[] + DHandlerInfo[1] handler_info; +} + +struct DCatchBlock +{ + ClassInfo type; // catch type + size_t bpoffset; // EBP offset of catch var + size_t codeoffset; // catch handler offset +} + +struct DCatchInfo +{ + size_t ncatches; // number of catch blocks + DCatchBlock[1] catch_block; +} + +struct FuncTable +{ + void* fptr; // pointer to start of function + DHandlerTable* handlertable; + uint fsize; // size of function in bytes +} + +// InFlight exception tracking (per-stack, swapped on fiber switches) + +private struct InFlight +{ + InFlight* next; + void* addr; + Throwable t; +} + +private __gshared InFlight* __inflight = null; + +extern(C) void* _d_eh_swapContext(void* newContext) nothrow @nogc +{ + auto old = __inflight; + __inflight = cast(InFlight*) newContext; + return old; +} + +// DEH section scanning — find exception handler tables in the binary + +version (Windows) + extern(C) extern __gshared ubyte __ImageBase; + +private __gshared immutable(FuncTable)* _deh_start; +private __gshared immutable(FuncTable)* _deh_end; + +private void ensure_deh_loaded() nothrow @nogc @trusted +{ + if (_deh_start !is null) + return; + + version (Windows) + { + auto section = find_pe_section(cast(void*) &__ImageBase, "._deh\0\0\0"); + if (section.length) + { + _deh_start = cast(immutable(FuncTable)*) section.ptr; + _deh_end = cast(immutable(FuncTable)*)(section.ptr + section.length); + } + } + else version (linux) + { + // TODO: ELF section scanning for .deh + } +} + +/// PE section lookup — duplicated from urt.package to avoid import cycle. +private void[] find_pe_section(void* imageBase, string name) nothrow @nogc @trusted +{ + if (name.length > 8) return null; + + auto base = cast(ubyte*) imageBase; + if (base[0] != 0x4D || base[1] != 0x5A) + return null; + + auto lfanew = *cast(int*)(base + 0x3C); + auto pe = base + lfanew; + if (pe[0] != 'P' || pe[1] != 'E' || pe[2] != 0 || pe[3] != 0) + return null; + + auto fileHeader = pe + 4; + ushort numSections = *cast(ushort*)(fileHeader + 2); + ushort optHeaderSize = *cast(ushort*)(fileHeader + 16); + auto sections = fileHeader + 20 + optHeaderSize; + + foreach (i; 0 .. numSections) + { + auto sec = sections + i * 40; + auto secName = (cast(char*) sec)[0 .. 8]; + bool match = true; + foreach (j; 0 .. 8) + { + if (secName[j] != name[j]) + { + match = false; + break; + } + } + if (match) + { + auto virtualSize = *cast(uint*)(sec + 8); + auto virtualAddress = *cast(uint*)(sec + 12); + return (base + virtualAddress)[0 .. virtualSize]; + } + } + return null; +} + +// Handler table lookup + +immutable(FuncTable)* __eh_finddata(void* address) nothrow @nogc @trusted +{ + ensure_deh_loaded(); + if (_deh_start is null) + return null; + return __eh_finddata_range(address, _deh_start, _deh_end); +} + +immutable(FuncTable)* __eh_finddata_range(void* address, + immutable(FuncTable)* pstart, immutable(FuncTable)* pend) nothrow @nogc @trusted +{ + for (auto ft = pstart; ; ft++) + { + Lagain: + if (ft >= pend) + break; + + version (Win64) + { + // MS Linker sometimes inserts zero padding between .obj sections + if (ft.fptr == null) + { + ft = cast(immutable(FuncTable)*)(cast(void**) ft + 1); + goto Lagain; + } + } + + immutable(void)* fptr = ft.fptr; + version (Win64) + { + // Follow JMP indirection from /DEBUG linker + if ((cast(ubyte*) fptr)[0] == 0xE9) + fptr = fptr + 5 + *cast(int*)(fptr + 1); + } + + if (fptr <= address && address < cast(void*)(cast(char*) fptr + ft.fsize)) + return ft; + } + return null; +} + +// Stack frame walking + +size_t __eh_find_caller(size_t regbp, size_t* pretaddr) nothrow @nogc @trusted +{ + size_t bp = *cast(size_t*) regbp; + + if (bp) + { + // Stack grows downward — new BP must be above old + if (bp <= regbp) + terminate(); + + *pretaddr = *cast(size_t*)(regbp + size_t.sizeof); + } + return bp; +} + +// _d_throwc — the core throw implementation (RBP-chain walking) + +alias fp_t = int function(); + +extern(C) void _d_throwc(Throwable h) @trusted +{ + size_t regebp; + + version (D_InlineAsm_X86) + asm { mov regebp, EBP; } + else version (D_InlineAsm_X86_64) + asm { mov regebp, RBP; } + + // Increment reference count if refcounted + auto refcount = h.refcount(); + if (refcount) + h.refcount() = refcount + 1; + + _d_createTrace(h, null); + + while (1) + { + size_t retaddr; + + regebp = __eh_find_caller(regebp, &retaddr); + if (!regebp) + break; + + auto func_table = __eh_finddata(cast(void*) retaddr); + auto handler_table = func_table ? func_table.handlertable : null; + if (!handler_table) + continue; + + auto funcoffset = cast(size_t) func_table.fptr; + version (Win64) + { + // Follow JMP indirection from /DEBUG linker + if ((cast(ubyte*) funcoffset)[0] == 0xE9) + funcoffset = funcoffset + 5 + *cast(int*)(funcoffset + 1); + } + + // Find start index for retaddr in handler table + auto dim = handler_table.nhandlers; + auto index = -1; + for (uint i = 0; i < dim; i++) + { + auto phi = &handler_table.handler_info.ptr[i]; + if (retaddr > funcoffset + phi.offset && + retaddr <= funcoffset + phi.endoffset) + index = i; + } + + // Handle inflight exception chaining + if (dim) + { + auto phi = &handler_table.handler_info.ptr[index + 1]; + auto prev = cast(InFlight*) &__inflight; + auto curr = prev.next; + + if (curr !is null && curr.addr == cast(void*)(funcoffset + phi.finally_offset)) + { + auto e = cast(Error) h; + if (e !is null && (cast(Error) curr.t) is null) + { + e.bypassedException = curr.t; + prev.next = curr.next; + } + else + { + h = Throwable.chainTogether(curr.t, h); + prev.next = curr.next; + } + } + } + + // Walk handler table entries + int prev_ndx; + for (auto ndx = index; ndx != -1; ndx = prev_ndx) + { + auto phi = &handler_table.handler_info.ptr[ndx]; + prev_ndx = phi.prev_index; + + if (phi.cioffset) + { + // Catch handler + auto pci = cast(DCatchInfo*)(cast(char*) handler_table + phi.cioffset); + auto ncatches = pci.ncatches; + + for (uint i = 0; i < ncatches; i++) + { + auto ci = **cast(ClassInfo**) h; + auto pcb = &pci.catch_block.ptr[i]; + + if (_d_isbaseof(ci, pcb.type)) + { + // Initialize catch variable + *cast(void**)(regebp + pcb.bpoffset) = cast(void*) h; + + // Jump to catch block — does not return + { + size_t catch_esp; + fp_t catch_addr; + + catch_addr = cast(fp_t)(funcoffset + pcb.codeoffset); + catch_esp = regebp - handler_table.espoffset - fp_t.sizeof; + + version (D_InlineAsm_X86) + asm + { + mov EAX, catch_esp; + mov ECX, catch_addr; + mov [EAX], ECX; + mov EBP, regebp; + mov ESP, EAX; + ret; + } + else version (D_InlineAsm_X86_64) + asm + { + mov RAX, catch_esp; + mov RCX, catch_esp; + mov RCX, catch_addr; + mov [RAX], RCX; + mov RBP, regebp; + mov RSP, RAX; + ret; + } + } + } + } + } + else if (phi.finally_offset) + { + // Finally block + auto blockaddr = cast(void*)(funcoffset + phi.finally_offset); + InFlight inflight; + + inflight.addr = blockaddr; + inflight.next = __inflight; + inflight.t = h; + __inflight = &inflight; + + version (D_InlineAsm_X86) + asm + { + push EBX; + mov EBX, blockaddr; + push EBP; + mov EBP, regebp; + call EBX; + pop EBP; + pop EBX; + } + else version (D_InlineAsm_X86_64) + asm + { + sub RSP, 8; + push RBX; + mov RBX, blockaddr; + push RBP; + mov RBP, regebp; + call RBX; + pop RBP; + pop RBX; + add RSP, 8; + } + + if (__inflight is &inflight) + __inflight = __inflight.next; + } + } + } + terminate(); +} + +} // version (Win64) +// Shared DWARF block — not part of the version chain above. +// Compiles on all non-Windows platforms (DMD Linux, LDC everywhere). +version (Windows) {} else +{ + +// ────────────────────────────────────────────────────────────────────── +// DWARF exception handling (shared by DMD and LDC) +// +// Uses the GCC/DWARF unwinder (libgcc_s / libunwind). +// pragma(mangle) selects the compiler-specific symbol names: +// DMD: _d_throwdwarf, __dmd_begin_catch, __dmd_personality_v0 +// LDC: _d_throw_exception, _d_eh_enter_catch, _d_eh_personality +// +// Ported from druntime rt/dwarfeh.d. +// Copyright: Digital Mars 2015-2016 (original), uRT authors (port). +// License: Boost Software License 1.0 +// ────────────────────────────────────────────────────────────────────── + +// ── libunwind / libgcc_s bindings ─────────────────────────────────── + +private: + +alias _Unwind_Ptr = size_t; +alias _Unwind_Word = size_t; +alias _Unwind_Exception_Class = ulong; +alias _uleb128_t = size_t; +alias _sleb128_t = ptrdiff_t; + +alias _Unwind_Reason_Code = int; +enum +{ + _URC_NO_REASON = 0, + _URC_FOREIGN_EXCEPTION_CAUGHT = 1, + _URC_FATAL_PHASE2_ERROR = 2, + _URC_FATAL_PHASE1_ERROR = 3, + _URC_NORMAL_STOP = 4, + _URC_END_OF_STACK = 5, + _URC_HANDLER_FOUND = 6, + _URC_INSTALL_CONTEXT = 7, + _URC_CONTINUE_UNWIND = 8, +} + +alias _Unwind_Action = int; +enum : _Unwind_Action +{ + _UA_SEARCH_PHASE = 1, + _UA_CLEANUP_PHASE = 2, + _UA_HANDLER_FRAME = 4, + _UA_FORCE_UNWIND = 8, +} + +alias _Unwind_Exception_Cleanup_Fn = extern(C) void function( + _Unwind_Reason_Code, _Unwind_Exception*); + +version (X86_64) +{ + align(16) struct _Unwind_Exception + { + _Unwind_Exception_Class exception_class; + _Unwind_Exception_Cleanup_Fn exception_cleanup; + _Unwind_Word private_1; + _Unwind_Word private_2; + } +} +else +{ + align(8) struct _Unwind_Exception + { + _Unwind_Exception_Class exception_class; + _Unwind_Exception_Cleanup_Fn exception_cleanup; + _Unwind_Word private_1; + _Unwind_Word private_2; + } +} + +struct _Unwind_Context; + +extern(C) nothrow @nogc +{ + _Unwind_Reason_Code _Unwind_RaiseException(_Unwind_Exception*); + void _Unwind_DeleteException(_Unwind_Exception*); + _Unwind_Word _Unwind_GetGR(_Unwind_Context*, int); + void _Unwind_SetGR(_Unwind_Context*, int, _Unwind_Word); + _Unwind_Ptr _Unwind_GetIP(_Unwind_Context*); + _Unwind_Ptr _Unwind_GetIPInfo(_Unwind_Context*, int*); + void _Unwind_SetIP(_Unwind_Context*, _Unwind_Ptr); + void* _Unwind_GetLanguageSpecificData(_Unwind_Context*); + _Unwind_Ptr _Unwind_GetRegionStart(_Unwind_Context*); +} + + +// ── ARM EABI unwinder types ──────────────────────────────────────── + +version (ARM) +{ + alias _Unwind_State = int; + enum : _Unwind_State + { + _US_VIRTUAL_UNWIND_FRAME = 0, + _US_UNWIND_FRAME_STARTING = 1, + _US_UNWIND_FRAME_RESUME = 2, + _US_ACTION_MASK = 3, + _US_FORCE_UNWIND = 8, + } + + extern(C) void _Unwind_Complete(_Unwind_Exception*) nothrow @nogc; +} + +// ── EH register numbers (architecture-specific) ──────────────────── + +version (X86_64) +{ + enum eh_exception_regno = 0; + enum eh_selector_regno = 1; +} +else version (X86) +{ + enum eh_exception_regno = 0; + enum eh_selector_regno = 2; +} +else version (AArch64) +{ + enum eh_exception_regno = 0; + enum eh_selector_regno = 1; +} +else version (ARM) +{ + enum eh_exception_regno = 0; + enum eh_selector_regno = 1; +} +else version (RISCV64) +{ + enum eh_exception_regno = 10; + enum eh_selector_regno = 11; +} +else version (RISCV32) +{ + enum eh_exception_regno = 10; + enum eh_selector_regno = 11; +} +else + static assert(0, "Unknown EH register numbers for this architecture"); + +// ── DWARF encoding constants ─────────────────────────────────────── + +enum +{ + DW_EH_PE_FORMAT_MASK = 0x0F, + DW_EH_PE_APPL_MASK = 0x70, + DW_EH_PE_indirect = 0x80, + + DW_EH_PE_omit = 0xFF, + DW_EH_PE_ptr = 0x00, + DW_EH_PE_uleb128 = 0x01, + DW_EH_PE_udata2 = 0x02, + DW_EH_PE_udata4 = 0x03, + DW_EH_PE_udata8 = 0x04, + DW_EH_PE_sleb128 = 0x09, + DW_EH_PE_sdata2 = 0x0A, + DW_EH_PE_sdata4 = 0x0B, + DW_EH_PE_sdata8 = 0x0C, + + DW_EH_PE_absptr = 0x00, + DW_EH_PE_pcrel = 0x10, + DW_EH_PE_textrel = 0x20, + DW_EH_PE_datarel = 0x30, + DW_EH_PE_funcrel = 0x40, + DW_EH_PE_aligned = 0x50, +} + +// ── DMD exception class identifier ───────────────────────────────── + +enum _Unwind_Exception_Class dmd_exception_class = + (cast(_Unwind_Exception_Class)'D' << 56) | + (cast(_Unwind_Exception_Class)'M' << 48) | + (cast(_Unwind_Exception_Class)'D' << 40) | + (cast(_Unwind_Exception_Class)'D' << 24); + +// ── Compiler-specific symbol names ───────────────────────────────── + +version (LDC) +{ + private enum throw_mangle = "_d_throw_exception"; + private enum catch_mangle = "_d_eh_enter_catch"; + private enum personality_mangle = "_d_eh_personality"; +} +else +{ + private enum throw_mangle = "_d_throwdwarf"; + private enum catch_mangle = "__dmd_begin_catch"; + private enum personality_mangle = "__dmd_personality_v0"; +} + +// ── ExceptionHeader ──────────────────────────────────────────────── + +struct ExceptionHeader +{ + Throwable object; + _Unwind_Exception exception_object; + + int handler; + const(ubyte)* language_specific_data; + _Unwind_Ptr landing_pad; + + ExceptionHeader* next; + + static ExceptionHeader* stack; // thread-local chain + static ExceptionHeader ehstorage; // pre-allocated (one per thread) + + static ExceptionHeader* create(Throwable o) nothrow @nogc + { + import urt.mem : calloc; + auto eh = &ehstorage; + if (eh.object) + { + eh = cast(ExceptionHeader*) calloc(1, ExceptionHeader.sizeof); + if (!eh) + dwarf_terminate(__LINE__); + } + eh.object = o; + eh.exception_object.exception_class = dmd_exception_class; + return eh; + } + + static void release(ExceptionHeader* eh) nothrow @nogc + { + import urt.mem : free; + *eh = ExceptionHeader.init; + if (eh != &ehstorage) + free(eh); + } + + void push() nothrow @nogc + { + next = stack; + stack = &this; + } + + static ExceptionHeader* pop() nothrow @nogc + { + auto eh = stack; + stack = eh.next; + return eh; + } + + static ExceptionHeader* to_exception_header(_Unwind_Exception* eo) nothrow @nogc + { + return cast(ExceptionHeader*)(cast(void*) eo - ExceptionHeader.exception_object.offsetof); + } +} + +// ── Helpers ──────────────────────────────────────────────────────── + +_Unwind_Ptr read_unaligned(T, bool consume)(ref const(ubyte)* p) nothrow @nogc @trusted +{ + import urt.processor : SupportUnalignedLoadStore; + static if (SupportUnalignedLoadStore) + T value = *cast(T*) p; + else + { + import urt.mem : memcpy; + T value = void; + memcpy(&value, p, T.sizeof); + } + + static if (consume) + p += T.sizeof; + return cast(_Unwind_Ptr) value; +} + +_uleb128_t u_leb128(const(ubyte)** p) nothrow @nogc +{ + auto q = *p; + _uleb128_t result = 0; + uint shift = 0; + while (1) + { + ubyte b = *q++; + result |= cast(_uleb128_t)(b & 0x7F) << shift; + if ((b & 0x80) == 0) + break; + shift += 7; + } + *p = q; + return result; +} + +_sleb128_t s_leb128(const(ubyte)** p) nothrow @nogc +{ + auto q = *p; + ubyte b; + _sleb128_t result = 0; + uint shift = 0; + while (1) + { + b = *q++; + result |= cast(_sleb128_t)(b & 0x7F) << shift; + shift += 7; + if ((b & 0x80) == 0) + break; + } + if (shift < result.sizeof * 8 && (b & 0x40)) + result |= -(cast(_sleb128_t)1 << shift); + *p = q; + return result; +} + +void dwarf_terminate(uint line) nothrow @nogc +{ + import urt.io : writef_to, WriteTarget; + import urt.internal.stdc : abort; + writef_to!(WriteTarget.stderr, true)("dwarfeh({0}) fatal error", line); + abort(); +} + +// ── LSDA scanning ────────────────────────────────────────────────── + +enum LsdaResult +{ + not_found, + foreign, + corrupt, + no_action, + cleanup, + handler, +} + +ClassInfo get_class_info(_Unwind_Exception* exception_object, const(ubyte)* current_lsd) nothrow @nogc +{ + ExceptionHeader* eh = ExceptionHeader.to_exception_header(exception_object); + Throwable ehobject = eh.object; + for (ExceptionHeader* ehn = eh.next; ehn; ehn = ehn.next) + { + if (current_lsd != ehn.language_specific_data) + break; + Error e = cast(Error) ehobject; + if (e is null || (cast(Error) ehn.object) !is null) + ehobject = ehn.object; + } + return typeid(ehobject); +} + +int action_table_lookup(_Unwind_Exception* exception_object, uint action_record_ptr, const(ubyte)* p_action_table, const(ubyte)* tt, + ubyte ttype, _Unwind_Exception_Class exception_class, const(ubyte)* lsda) nothrow @nogc +{ + ClassInfo thrown_type; + if (exception_class == dmd_exception_class) + thrown_type = get_class_info(exception_object, lsda); + + for (auto ap = p_action_table + action_record_ptr - 1; 1; ) + { + auto type_filter = s_leb128(&ap); + auto apn = ap; + auto next_record_ptr = s_leb128(&ap); + + if (type_filter <= 0) + return -1; + + _Unwind_Ptr entry; + const(ubyte)* tt2; + switch (ttype & DW_EH_PE_FORMAT_MASK) + { + case DW_EH_PE_sdata2: entry = read_unaligned!(short, false)(tt2 = tt - type_filter * 2); break; + case DW_EH_PE_udata2: entry = read_unaligned!(ushort, false)(tt2 = tt - type_filter * 2); break; + case DW_EH_PE_sdata4: entry = read_unaligned!(int, false)(tt2 = tt - type_filter * 4); break; + case DW_EH_PE_udata4: entry = read_unaligned!(uint, false)(tt2 = tt - type_filter * 4); break; + case DW_EH_PE_sdata8: entry = read_unaligned!(long, false)(tt2 = tt - type_filter * 8); break; + case DW_EH_PE_udata8: entry = read_unaligned!(ulong, false)(tt2 = tt - type_filter * 8); break; + case DW_EH_PE_ptr: if (size_t.sizeof == 8) + goto case DW_EH_PE_udata8; + else + goto case DW_EH_PE_udata4; + default: + return -1; + } + if (!entry) + return -1; + + switch (ttype & DW_EH_PE_APPL_MASK) + { + case DW_EH_PE_absptr: + break; + case DW_EH_PE_pcrel: + entry += cast(_Unwind_Ptr) tt2; + break; + default: + return -1; + } + if (ttype & DW_EH_PE_indirect) + entry = *cast(_Unwind_Ptr*) entry; + + ClassInfo ci = cast(ClassInfo) cast(void*) entry; + + // D exception — check class hierarchy + if (exception_class == dmd_exception_class && _d_isbaseof(thrown_type, ci)) + return cast(int) type_filter; + + if (!next_record_ptr) + return 0; + + ap = apn + next_record_ptr; + } + assert(0); // unreachable — all paths return inside the loop +} + +LsdaResult scan_lsda(const(ubyte)* lsda, _Unwind_Ptr ip, _Unwind_Exception_Class exception_class, bool cleanups_only, bool prefer_handler, + _Unwind_Exception* exception_object, out _Unwind_Ptr landing_pad, out int handler) nothrow @nogc +{ + auto p = lsda; + if (!p) + return LsdaResult.no_action; + + _Unwind_Ptr dw_pe_value(ubyte pe) + { + switch (pe) + { + case DW_EH_PE_sdata2: return read_unaligned!(short, true)(p); + case DW_EH_PE_udata2: return read_unaligned!(ushort, true)(p); + case DW_EH_PE_sdata4: return read_unaligned!(int, true)(p); + case DW_EH_PE_udata4: return read_unaligned!(uint, true)(p); + case DW_EH_PE_sdata8: return read_unaligned!(long, true)(p); + case DW_EH_PE_udata8: return read_unaligned!(ulong, true)(p); + case DW_EH_PE_sleb128: return cast(_Unwind_Ptr) s_leb128(&p); + case DW_EH_PE_uleb128: return cast(_Unwind_Ptr) u_leb128(&p); + case DW_EH_PE_ptr: if (size_t.sizeof == 8) + goto case DW_EH_PE_udata8; + else + goto case DW_EH_PE_udata4; + default: + dwarf_terminate(__LINE__); + return 0; + } + } + + ubyte lp_start = *p++; + + _Unwind_Ptr lp_base = 0; + if (lp_start != DW_EH_PE_omit) + lp_base = dw_pe_value(lp_start); + + ubyte ttype = *p++; + _Unwind_Ptr tt_base = 0; + _Unwind_Ptr tt_offset = 0; + if (ttype != DW_EH_PE_omit) + { + tt_base = u_leb128(&p); + tt_offset = (p - lsda) + tt_base; + } + + ubyte call_site_format = *p++; + _Unwind_Ptr call_site_table_size = dw_pe_value(DW_EH_PE_uleb128); + + _Unwind_Ptr ip_offset = ip - lp_base; + bool no_action = false; + auto tt = lsda + tt_offset; + const(ubyte)* p_action_table = p + call_site_table_size; + + while (1) + { + if (p >= p_action_table) + { + if (p == p_action_table) + break; + return LsdaResult.corrupt; + } + + _Unwind_Ptr call_site_start = dw_pe_value(call_site_format); + _Unwind_Ptr call_site_range = dw_pe_value(call_site_format); + _Unwind_Ptr call_site_lp = dw_pe_value(call_site_format); + _uleb128_t action_record_ptr_val = u_leb128(&p); + + if (ip_offset < call_site_start) + break; + + if (ip_offset < call_site_start + call_site_range) + { + if (action_record_ptr_val) + { + if (cleanups_only) + continue; + + auto h = action_table_lookup(exception_object, cast(uint) action_record_ptr_val, + p_action_table, tt, ttype, exception_class, lsda); + if (h < 0) + return LsdaResult.corrupt; + if (h == 0) + continue; + + no_action = false; + landing_pad = call_site_lp; + handler = h; + } + else if (call_site_lp) + { + if (prefer_handler && handler) + continue; + no_action = false; + landing_pad = call_site_lp; + handler = 0; + } + else + no_action = true; + } + } + + if (no_action) + return LsdaResult.no_action; + + if (landing_pad) + return handler ? LsdaResult.handler : LsdaResult.cleanup; + + return LsdaResult.not_found; +} + +// ── Public API ────────────────────────────────────────────────────── + +pragma(mangle, catch_mangle) +extern(C) Throwable dwarfeh_begin_catch(_Unwind_Exception* exception_object) nothrow @nogc +{ + version (ARM) version (LDC) + _Unwind_Complete(exception_object); + + ExceptionHeader* eh = ExceptionHeader.to_exception_header(exception_object); + auto o = eh.object; + eh.object = null; + + if (eh != ExceptionHeader.pop()) + dwarf_terminate(__LINE__); + + _Unwind_DeleteException(&eh.exception_object); + return o; +} + +extern(C) void* _d_eh_swapContextDwarf(void* newContext) nothrow @nogc +{ + auto old = ExceptionHeader.stack; + ExceptionHeader.stack = cast(ExceptionHeader*) newContext; + return old; +} + +pragma(mangle, throw_mangle) +extern(C) void dwarfeh_throw(Throwable o) +{ + import urt.io : writeln_err, writef_to, WriteTarget; + import urt.internal.stdc : abort; + + ExceptionHeader* eh = ExceptionHeader.create(o); + eh.push(); + + auto refcount = o.refcount(); + if (refcount) + o.refcount() = refcount + 1; + + extern(C) static void exception_cleanup(_Unwind_Reason_Code reason, + _Unwind_Exception* eo) nothrow @nogc + { + switch (reason) + { + case _URC_FOREIGN_EXCEPTION_CAUGHT: + case _URC_NO_REASON: + ExceptionHeader.release(ExceptionHeader.to_exception_header(eo)); + break; + default: + dwarf_terminate(__LINE__); + } + } + + eh.exception_object.exception_cleanup = &exception_cleanup; + _d_createTrace(o, null); + + auto r = _Unwind_RaiseException(&eh.exception_object); + + // Should not return — if it did, the exception was not caught. + dwarfeh_begin_catch(&eh.exception_object); + writeln_err("uncaught exception reached top of stack"); + auto msg = o.msg; + if (msg.length) + writef_to!(WriteTarget.stderr, true)(" {0}", msg); + abort(); +} + +// Common personality implementation. +_Unwind_Reason_Code dwarfeh_personality_common(_Unwind_Action actions, _Unwind_Exception_Class exception_class, _Unwind_Exception* exception_object, _Unwind_Context* context) nothrow @nogc +{ + const(ubyte)* language_specific_data; + int handler; + _Unwind_Ptr landing_pad; + + language_specific_data = cast(const(ubyte)*) _Unwind_GetLanguageSpecificData(context); + auto Start = _Unwind_GetRegionStart(context); + + // Get IP; use _Unwind_GetIPInfo to handle signal frames correctly. + int ip_before_insn; + auto ip = _Unwind_GetIPInfo(context, &ip_before_insn); + if (!ip_before_insn) + --ip; + + auto result = scan_lsda(language_specific_data, ip - Start, exception_class, + (actions & _UA_FORCE_UNWIND) != 0, + (actions & _UA_SEARCH_PHASE) != 0, + exception_object, + landing_pad, + handler); + landing_pad += Start; + + final switch (result) + { + case LsdaResult.not_found: + dwarf_terminate(__LINE__); + assert(0); + + case LsdaResult.foreign: + dwarf_terminate(__LINE__); + assert(0); + + case LsdaResult.corrupt: + dwarf_terminate(__LINE__); + assert(0); + + case LsdaResult.no_action: + return _URC_CONTINUE_UNWIND; + + case LsdaResult.cleanup: + if (actions & _UA_SEARCH_PHASE) + return _URC_CONTINUE_UNWIND; + break; + + case LsdaResult.handler: + if (actions & _UA_SEARCH_PHASE) + { + if (exception_class == dmd_exception_class) + { + auto eh = ExceptionHeader.to_exception_header(exception_object); + eh.handler = handler; + eh.language_specific_data = language_specific_data; + eh.landing_pad = landing_pad; + } + return _URC_HANDLER_FOUND; + } + break; + } + + // Multiple exceptions in flight — chain them + if (exception_class == dmd_exception_class) + { + auto eh = ExceptionHeader.to_exception_header(exception_object); + auto current_lsd = language_specific_data; + bool bypassed = false; + + while (eh.next) + { + ExceptionHeader* ehn = eh.next; + + Error e = cast(Error) eh.object; + if (e !is null && !cast(Error) ehn.object) + { + current_lsd = ehn.language_specific_data; + eh = ehn; + bypassed = true; + continue; + } + + if (current_lsd != ehn.language_specific_data) + break; + + eh.object = Throwable.chainTogether(ehn.object, eh.object); + + if (ehn.handler != handler && !bypassed) + { + handler = ehn.handler; + eh.handler = handler; + eh.language_specific_data = language_specific_data; + eh.landing_pad = landing_pad; + } + + eh.next = ehn.next; + _Unwind_DeleteException(&ehn.exception_object); + } + + if (bypassed) + { + eh = ExceptionHeader.to_exception_header(exception_object); + Error e = cast(Error) eh.object; + auto ehn = eh.next; + e.bypassedException = ehn.object; + eh.next = ehn.next; + _Unwind_DeleteException(&ehn.exception_object); + } + } + + _Unwind_SetGR(context, eh_exception_regno, cast(_Unwind_Word) exception_object); + _Unwind_SetGR(context, eh_selector_regno, handler); + _Unwind_SetIP(context, landing_pad); + + return _URC_INSTALL_CONTEXT; +} + +// Personality function entry points. +// ARM EABI uses a different calling convention than the standard Itanium ABI. +version (ARM) +{ + version (LDC) + { + extern(C) _Unwind_Reason_Code _d_eh_personality(_Unwind_State state, _Unwind_Exception* exception_object, _Unwind_Context* context) nothrow @nogc + { + _Unwind_Action actions; + switch (state & _US_ACTION_MASK) + { + case _US_VIRTUAL_UNWIND_FRAME: + actions = _UA_SEARCH_PHASE; + break; + case _US_UNWIND_FRAME_STARTING: + actions = _UA_CLEANUP_PHASE; + break; + case _US_UNWIND_FRAME_RESUME: + return _URC_CONTINUE_UNWIND; + default: + dwarf_terminate(__LINE__); + return _URC_FATAL_PHASE1_ERROR; + } + if (state & _US_FORCE_UNWIND) + actions |= _UA_FORCE_UNWIND; + + return dwarfeh_personality_common(actions, exception_object.exception_class, + exception_object, context); + } + } +} +else +{ + pragma(mangle, personality_mangle) + extern(C) _Unwind_Reason_Code dwarfeh_personality(int ver, _Unwind_Action actions, _Unwind_Exception_Class exception_class, _Unwind_Exception* exception_object, _Unwind_Context* context) nothrow @nogc + { + if (ver != 1) + return _URC_FATAL_PHASE1_ERROR; + + return dwarfeh_personality_common(actions, exception_class, + exception_object, context); + } +} + +// LDC-only: trivial cleanup hooks (DWARF handles cleanup via personality). +version (LDC) +{ + extern(C) bool _d_enter_cleanup(void* ptr) nothrow @nogc @trusted => true; + extern(C) void _d_leave_cleanup(void* ptr) nothrow @nogc @trusted {} +} + +} // version (!Windows) — shared DWARF EH diff --git a/src/urt/internal/lifetime.d b/src/urt/internal/lifetime.d index 314164f..1f9c3a9 100644 --- a/src/urt/internal/lifetime.d +++ b/src/urt/internal/lifetime.d @@ -92,7 +92,7 @@ constructors etc. void emplaceInitializer(T)(scope ref T chunk) nothrow pure @trusted if (!is(T == const) && !is(T == immutable) && !is(T == inout)) { - import core.internal.traits : hasElaborateAssign; + import urt.internal.traits : hasElaborateAssign; static if (__traits(isZeroInit, T)) { diff --git a/src/urt/internal/os.c b/src/urt/internal/os.c index d6b9c02..075965f 100644 --- a/src/urt/internal/os.c +++ b/src/urt/internal/os.c @@ -2,10 +2,18 @@ #if defined(__linux) # define _DEFAULT_SOURCE +# include # include # include +# include # include # include # include # include +# include + +// EWOULDBLOCK is #define EWOULDBLOCK EAGAIN on Linux — ImportC cannot resolve +// chained macros, so re-define as a plain integer. +# undef EWOULDBLOCK +# define EWOULDBLOCK 11 /* same as EAGAIN on Linux */ #endif diff --git a/src/urt/internal/stdc.c b/src/urt/internal/stdc.c new file mode 100644 index 0000000..26fecdb --- /dev/null +++ b/src/urt/internal/stdc.c @@ -0,0 +1,19 @@ +#pragma attribute(push, nothrow, nogc) + +#include +#include +#include +#include +#include + +// The errno macro expands to (*__errno_location()) on Linux. ImportC would +// generate a function symbol `errno` that clashes with libc's TLS errno. +#undef errno + +// Some errno values are defined as chained macros (e.g. ENOTSUP → EOPNOTSUPP, +// EWOULDBLOCK → EAGAIN) that ImportC cannot resolve to integers. +// Re-define as plain integer literals so ImportC can export them. +#undef ENOTSUP +#define ENOTSUP 95 /* == EOPNOTSUPP on Linux */ +#undef EWOULDBLOCK +#define EWOULDBLOCK 11 /* == EAGAIN on Linux */ diff --git a/src/urt/internal/sys/windows/basetsd.d b/src/urt/internal/sys/windows/basetsd.d new file mode 100644 index 0000000..b881b5a --- /dev/null +++ b/src/urt/internal/sys/windows/basetsd.d @@ -0,0 +1,141 @@ +/** + * Windows API header module + * + * Translated from MinGW API for MS-Windows 3.12 + * + * Authors: Stewart Gordon + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_basetsd.d) + */ +module urt.internal.sys.windows.basetsd; +version (Windows): + +// [SnakE 2009-02-23] Moved HANDLE definition here from winnt.d to avoid +// 'forwatd template reference' to CPtr from winnt.d caused by a circular +// import. +alias HANDLE = void*; + +alias HANDLE* PHANDLE, LPHANDLE; + +// helper for aligned structs +// alignVal 0 means the default align. +// _alignSpec as parameter does not pollute namespace. +package mixin template AlignedStr(int alignVal, string name, string memberlist, + string _alignSpec = !alignVal ? "align" : "align("~alignVal.stringof~")" ) +{ + mixin( _alignSpec ~ " struct " ~ name ~" { " ~ _alignSpec ~":"~ memberlist~" }" ); +} + +version (CoreUnittest) { + private mixin AlignedStr!(16, "_Test_Aligned_Str", q{char a; char b;}); + private mixin AlignedStr!(0, "_Test_NoAligned_Str", q{char a; char b;}); +} + +version (Win64) { + alias long __int3264; +enum ulong ADDRESS_TAG_BIT = 0x40000000000; + + alias long INT_PTR, LONG_PTR; + alias long* PINT_PTR, PLONG_PTR; + alias ulong UINT_PTR, ULONG_PTR, HANDLE_PTR; + alias ulong* PUINT_PTR, PULONG_PTR; + alias int HALF_PTR; + alias int* PHALF_PTR; + alias uint UHALF_PTR; + alias uint* PUHALF_PTR; + + uint HandleToULong()(void* h) { return(cast(uint) cast(ULONG_PTR) h); } + int HandleToLong()(void* h) { return(cast(int) cast(LONG_PTR) h); } + void* ULongToHandle()(uint h) { return(cast(void*) cast(UINT_PTR) h); } + void* LongToHandle()(int h) { return(cast(void*) cast(INT_PTR) h); } + uint PtrToUlong()(void* p) { return(cast(uint) cast(ULONG_PTR) p); } + uint PtrToUint()(void* p) { return(cast(uint) cast(UINT_PTR) p); } + ushort PtrToUshort()(void* p) { return(cast(ushort) cast(uint) cast(ULONG_PTR) p); } + int PtrToLong()(void* p) { return(cast(int) cast(LONG_PTR) p); } + int PtrToInt()(void* p) { return(cast(int) cast(INT_PTR) p); } + short PtrToShort()(void* p) { return(cast(short) cast(int) cast(LONG_PTR) p); } + void* IntToPtr()(int i) { return(cast(void*) cast(INT_PTR) i); } + void* UIntToPtr()(uint ui) { return(cast(void*) cast(UINT_PTR) ui); } + void* LongToPtr()(int l) { return(cast(void*) cast(LONG_PTR) l); } + void* ULongToPtr()(uint ul) { return(cast(void*) cast(ULONG_PTR) ul); } + +} else { + alias int __int3264; +enum uint ADDRESS_TAG_BIT = 0x80000000; + + alias int INT_PTR, LONG_PTR; + alias int* PINT_PTR, PLONG_PTR; + alias uint UINT_PTR, ULONG_PTR, HANDLE_PTR; + alias uint* PUINT_PTR, PULONG_PTR; + alias short HALF_PTR; + alias short* PHALF_PTR; + alias ushort UHALF_PTR; + alias ushort* PUHALF_PTR; + + uint HandleToUlong()(HANDLE h) { return cast(uint) h; } + int HandleToLong()(HANDLE h) { return cast(int) h; } + HANDLE LongToHandle()(LONG_PTR h) { return cast(HANDLE)h; } + uint PtrToUlong(const(void)* p) { return cast(uint) p; } + uint PtrToUint(const(void)* p) { return cast(uint) p; } + int PtrToInt(const(void)* p) { return cast(int) p; } + ushort PtrToUshort(const(void)* p) { return cast(ushort) p; } + short PtrToShort(const(void)* p) { return cast(short) p; } + void* IntToPtr()(int i) { return cast(void*) i; } + void* UIntToPtr()(uint ui) { return cast(void*) ui; } + alias IntToPtr LongToPtr; + alias UIntToPtr ULongToPtr; +} + +alias UIntToPtr UintToPtr, UlongToPtr; + +enum : UINT_PTR { + MAXUINT_PTR = UINT_PTR.max +} + +enum : INT_PTR { + MAXINT_PTR = INT_PTR.max, + MININT_PTR = INT_PTR.min +} + +enum : ULONG_PTR { + MAXULONG_PTR = ULONG_PTR.max +} + +enum : LONG_PTR { + MAXLONG_PTR = LONG_PTR.max, + MINLONG_PTR = LONG_PTR.min +} + +enum : UHALF_PTR { + MAXUHALF_PTR = UHALF_PTR.max +} + +enum : HALF_PTR { + MAXHALF_PTR = HALF_PTR.max, + MINHALF_PTR = HALF_PTR.min +} + +alias byte INT8; +alias byte* PINT8; +alias ubyte UINT8; +alias ubyte* PUINT8; + +alias short INT16; +alias short* PINT16; +alias ushort UINT16; +alias ushort* PUINT16; + +alias int LONG32, INT32; +alias int* PLONG32, PINT32; +alias uint ULONG32, DWORD32, UINT32; +alias uint* PULONG32, PDWORD32, PUINT32; + +alias ULONG_PTR SIZE_T, DWORD_PTR; +alias ULONG_PTR* PSIZE_T, PDWORD_PTR; +alias LONG_PTR SSIZE_T; +alias LONG_PTR* PSSIZE_T; + +alias long LONG64, INT64; +alias long* PLONG64, PINT64; +alias ulong ULONG64, DWORD64, UINT64; +alias ulong* PULONG64, PDWORD64, PUINT64; diff --git a/src/urt/internal/sys/windows/basetyps.d b/src/urt/internal/sys/windows/basetyps.d new file mode 100644 index 0000000..d6cee67 --- /dev/null +++ b/src/urt/internal/sys/windows/basetyps.d @@ -0,0 +1,27 @@ +/** + * Windows API header module + * + * Translated from MinGW API for MS-Windows 3.10 + * + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_basetyps.d) + */ +module urt.internal.sys.windows.basetyps; +version (Windows): + +import urt.internal.sys.windows.windef, urt.internal.sys.windows.basetsd; + +align(1) struct GUID { // size is 16 + align(1): + DWORD Data1; + WORD Data2; + WORD Data3; + BYTE[8] Data4; +} +alias GUID UUID, /*IID, CLSID, */FMTID, uuid_t; +alias IID = const(GUID); +alias CLSID = const(GUID); + +alias GUID* LPGUID, LPCLSID, LPIID; +alias const(GUID)* LPCGUID, REFGUID, REFIID, REFCLSID, REFFMTID; +alias uint error_status_t, PROPID; diff --git a/src/urt/internal/sys/windows/ntdef.d b/src/urt/internal/sys/windows/ntdef.d new file mode 100644 index 0000000..a80762d --- /dev/null +++ b/src/urt/internal/sys/windows/ntdef.d @@ -0,0 +1,85 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * Authors: Stewart Gordon + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_ntdef.d) + */ +module urt.internal.sys.windows.ntdef; +version (Windows): + +import urt.internal.sys.windows.basetsd, urt.internal.sys.windows.subauth, urt.internal.sys.windows.windef, urt.internal.sys.windows.winnt; + +enum uint + OBJ_INHERIT = 0x0002, + OBJ_PERMANENT = 0x0010, + OBJ_EXCLUSIVE = 0x0020, + OBJ_CASE_INSENSITIVE = 0x0040, + OBJ_OPENIF = 0x0080, + OBJ_OPENLINK = 0x0100, + OBJ_VALID_ATTRIBUTES = 0x01F2; + +void InitializeObjectAttributes(OBJECT_ATTRIBUTES* p, UNICODE_STRING* n, + uint a, HANDLE r, void* s) { + with (*p) { + Length = OBJECT_ATTRIBUTES.sizeof; + RootDirectory = r; + Attributes = a; + ObjectName = n; + SecurityDescriptor = s; + SecurityQualityOfService = null; + } +} + +pragma(inline, true) @safe pure nothrow @nogc { + bool NT_SUCCESS()(NTSTATUS Status) { return Status >= 0; } + bool NT_INFORMATION()(NTSTATUS Status) { return ((cast(ULONG) Status) >> 30) == 1; } + bool NT_WARNING()(NTSTATUS Status) { return ((cast(ULONG) Status) >> 30) == 2; } + bool NT_ERROR()(NTSTATUS Status) { return ((cast(ULONG) Status) >> 30) == 3; } +} + +/* In MinGW, NTSTATUS, UNICODE_STRING, STRING and their associated pointer + * type aliases are defined in ntdef.h, ntsecapi.h and subauth.h, each of + * which checks that none of the others is already included. + */ +alias int NTSTATUS; +alias int* PNTSTATUS; + +struct UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} +alias UNICODE_STRING* PUNICODE_STRING; +alias const(UNICODE_STRING)* PCUNICODE_STRING; + +struct STRING { + USHORT Length; + USHORT MaximumLength; + PCHAR Buffer; +} +alias STRING ANSI_STRING, OEM_STRING; +alias STRING* PSTRING, PANSI_STRING, POEM_STRING; + +alias LARGE_INTEGER PHYSICAL_ADDRESS; +alias LARGE_INTEGER* PPHYSICAL_ADDRESS; + +enum SECTION_INHERIT { + ViewShare = 1, + ViewUnmap +} + +/* In MinGW, this is defined in ntdef.h and ntsecapi.h, each of which checks + * that the other isn't already included. + */ +struct OBJECT_ATTRIBUTES { + ULONG Length = OBJECT_ATTRIBUTES.sizeof; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} +alias OBJECT_ATTRIBUTES* POBJECT_ATTRIBUTES; diff --git a/src/urt/internal/sys/windows/ntsecapi.d b/src/urt/internal/sys/windows/ntsecapi.d new file mode 100644 index 0000000..bf372bf --- /dev/null +++ b/src/urt/internal/sys/windows/ntsecapi.d @@ -0,0 +1,796 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * Authors: Stewart Gordon + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_ntsecapi.d) + */ +module urt.internal.sys.windows.ntsecapi; +version (Windows): +pragma(lib, "advapi32"); + +version (ANSI) {} else version = Unicode; + +private import + urt.internal.sys.windows.basetyps, urt.internal.sys.windows.ntdef, urt.internal.sys.windows.windef, urt.internal.sys.windows.winnt, urt.internal.sys.windows.w32api; + +// FIXME: check types and grouping of constants +// FIXME: check Windows version support + +enum KERB_WRAP_NO_ENCRYPT = 0x80000001; + +enum LOGON_GUEST = 0x00000001; +enum LOGON_NOENCRYPTION = 0x00000002; +enum LOGON_CACHED_ACCOUNT = 0x00000004; +enum LOGON_USED_LM_PASSWORD = 0x00000008; +enum LOGON_EXTRA_SIDS = 0x00000020; +enum LOGON_SUBAUTH_SESSION_KEY = 0x00000040; +enum LOGON_SERVER_TRUST_ACCOUNT = 0x00000080; +enum LOGON_NTLMV2_ENABLED = 0x00000100; +enum LOGON_RESOURCE_GROUPS = 0x00000200; +enum LOGON_PROFILE_PATH_RETURNED = 0x00000400; +enum LOGON_GRACE_LOGON = 0x01000000; + +enum { + LSA_MODE_PASSWORD_PROTECTED = 1, + LSA_MODE_INDIVIDUAL_ACCOUNTS, + LSA_MODE_MANDATORY_ACCESS, + LSA_MODE_LOG_FULL +} + +bool LSA_SUCCESS()(int x) { return x >= 0; } + +/* TOTHINKABOUT: These constants don't have ANSI/Unicode versioned + * aliases. Should we merge them anyway? + */ +const char[] MICROSOFT_KERBEROS_NAME_A = "Kerberos"; +const wchar[] MICROSOFT_KERBEROS_NAME_W = "Kerberos"; +const char[] MSV1_0_PACKAGE_NAME = "MICROSOFT_AUTHENTICATION_PACKAGE_V1_0"; +const wchar[] MSV1_0_PACKAGE_NAMEW = "MICROSOFT_AUTHENTICATION_PACKAGE_V1_0"; + +enum MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT = 32; +enum MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT = 2048; +enum MSV1_0_CLEARTEXT_PASSWORD_ALLOWED = 2; +enum MSV1_0_CRED_LM_PRESENT = 1; +enum MSV1_0_CRED_NT_PRESENT = 2; +enum MSV1_0_CRED_VERSION = 0; +enum MSV1_0_DONT_TRY_GUEST_ACCOUNT = 16; +enum MSV1_0_MAX_NTLM3_LIFE = 1800; +enum MSV1_0_MAX_AVL_SIZE = 64000; +enum MSV1_0_MNS_LOGON = 16777216; + +enum size_t + MSV1_0_CHALLENGE_LENGTH = 8, + MSV1_0_LANMAN_SESSION_KEY_LENGTH = 8, + MSV1_0_NTLM3_RESPONSE_LENGTH = 16, + MSV1_0_NTLM3_OWF_LENGTH = 16, + MSV1_0_NTLM3_INPUT_LENGTH = MSV1_0_NTLM3_RESPONSE.sizeof + - MSV1_0_NTLM3_RESPONSE_LENGTH, + MSV1_0_OWF_PASSWORD_LENGTH = 16, + MSV1_0_PACKAGE_NAMEW_LENGTH = MSV1_0_PACKAGE_NAMEW.sizeof + - WCHAR.sizeof; + +enum MSV1_0_RETURN_USER_PARAMETERS = 8; +enum MSV1_0_RETURN_PASSWORD_EXPIRY = 64; +enum MSV1_0_RETURN_PROFILE_PATH = 512; +enum MSV1_0_SUBAUTHENTICATION_DLL_EX = 1048576; +enum MSV1_0_SUBAUTHENTICATION_DLL = 0xff000000; +enum MSV1_0_SUBAUTHENTICATION_DLL_SHIFT = 24; +enum MSV1_0_SUBAUTHENTICATION_DLL_RAS = 2; +enum MSV1_0_SUBAUTHENTICATION_DLL_IIS = 132; +enum MSV1_0_SUBAUTHENTICATION_FLAGS = 0xff000000; +enum MSV1_0_TRY_GUEST_ACCOUNT_ONLY = 256; +enum MSV1_0_TRY_SPECIFIED_DOMAIN_ONLY = 1024; +enum MSV1_0_UPDATE_LOGON_STATISTICS = 4; +enum MSV1_0_USE_CLIENT_CHALLENGE = 128; +enum MSV1_0_USER_SESSION_KEY_LENGTH = 16; + +const char[] + MSV1_0_SUBAUTHENTICATION_KEY + = `System\CurrentControlSet\Control\Lsa\MSV1_0`, + MSV1_0_SUBAUTHENTICATION_VALUE = "Auth"; + +enum ACCESS_MASK + POLICY_VIEW_LOCAL_INFORMATION = 0x0001, + POLICY_VIEW_AUDIT_INFORMATION = 0x0002, + POLICY_GET_PRIVATE_INFORMATION = 0x0004, + POLICY_TRUST_ADMIN = 0x0008, + POLICY_CREATE_ACCOUNT = 0x0010, + POLICY_CREATE_SECRET = 0x0020, + POLICY_CREATE_PRIVILEGE = 0x0040, + POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x0080, + POLICY_SET_AUDIT_REQUIREMENTS = 0x0100, + POLICY_AUDIT_LOG_ADMIN = 0x0200, + POLICY_SERVER_ADMIN = 0x0400, + POLICY_LOOKUP_NAMES = 0x0800, + + POLICY_READ = STANDARD_RIGHTS_READ | 0x0006, + POLICY_WRITE = STANDARD_RIGHTS_WRITE | 0x07F8, + POLICY_EXECUTE = STANDARD_RIGHTS_EXECUTE | 0x0801, + POLICY_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | 0x0FFF; + +enum POLICY_AUDIT_EVENT_UNCHANGED = 0; +enum POLICY_AUDIT_EVENT_SUCCESS = 1; +enum POLICY_AUDIT_EVENT_FAILURE = 2; +enum POLICY_AUDIT_EVENT_NONE = 4; +enum POLICY_AUDIT_EVENT_MASK = 7; + +enum { + POLICY_LOCATION_LOCAL = 1, + POLICY_LOCATION_DS +} + +enum : uint { + POLICY_MACHINE_POLICY_LOCAL = 0, + POLICY_MACHINE_POLICY_DEFAULTED, + POLICY_MACHINE_POLICY_EXPLICIT, + POLICY_MACHINE_POLICY_UNKNOWN = 0xFFFFFFFF +} + + +enum POLICY_QOS_SCHANEL_REQUIRED = 0x0001; +enum POLICY_QOS_OUTBOUND_INTEGRITY = 0x0002; +enum POLICY_QOS_OUTBOUND_CONFIDENTIALITY = 0x0004; +enum POLICY_QOS_INBOUND_INTEGREITY = 0x0008; +enum POLICY_QOS_INBOUND_CONFIDENTIALITY = 0x0010; +enum POLICY_QOS_ALLOW_LOCAL_ROOT_CERT_STORE = 0x0020; +enum POLICY_QOS_RAS_SERVER_ALLOWED = 0x0040; +enum POLICY_QOS_DHCP_SERVER_ALLOWD = 0x0080; + +enum POLICY_KERBEROS_FORWARDABLE = 1; +enum POLICY_KERBEROS_PROXYABLE = 2; +enum POLICY_KERBEROS_RENEWABLE = 4; +enum POLICY_KERBEROS_POSTDATEABLE = 8; + +const char[] + SAM_PASSWORD_CHANGE_NOTIFY_ROUTINE = "PasswordChangeNotify", + SAM_INIT_NOTIFICATION_ROUTINE = "InitializeChangeNotify", + SAM_PASSWORD_FILTER_ROUTINE = "PasswordFilter"; + +const TCHAR[] + SE_INTERACTIVE_LOGON_NAME = "SeInteractiveLogonRight", + SE_NETWORK_LOGON_NAME = "SeNetworkLogonRight", + SE_BATCH_LOGON_NAME = "SeBatchLogonRight", + SE_SERVICE_LOGON_NAME = "SeServiceLogonRight"; + +enum { + TRUST_ATTRIBUTE_NON_TRANSITIVE = 1, + TRUST_ATTRIBUTE_UPLEVEL_ONLY = 2, + TRUST_ATTRIBUTE_TREE_PARENT = 4194304, + TRUST_ATTRIBUTES_VALID = -16580609 +} + +enum { + TRUST_AUTH_TYPE_NONE, + TRUST_AUTH_TYPE_NT4OWF, + TRUST_AUTH_TYPE_CLEAR +} + +enum { + TRUST_DIRECTION_DISABLED, + TRUST_DIRECTION_INBOUND, + TRUST_DIRECTION_OUTBOUND, + TRUST_DIRECTION_BIDIRECTIONAL +} + +enum { + TRUST_TYPE_DOWNLEVEL = 1, + TRUST_TYPE_UPLEVEL, + TRUST_TYPE_MIT, + TRUST_TYPE_DCE +} + +alias UNICODE_STRING LSA_UNICODE_STRING; +alias UNICODE_STRING* PLSA_UNICODE_STRING; +alias STRING LSA_STRING; +alias STRING* PLSA_STRING; + +enum MSV1_0_LOGON_SUBMIT_TYPE { + MsV1_0InteractiveLogon = 2, + MsV1_0Lm20Logon, + MsV1_0NetworkLogon, + MsV1_0SubAuthLogon, + MsV1_0WorkstationUnlockLogon = 7 +} +alias MSV1_0_LOGON_SUBMIT_TYPE* PMSV1_0_LOGON_SUBMIT_TYPE; + +enum MSV1_0_PROFILE_BUFFER_TYPE { + MsV1_0InteractiveProfile = 2, + MsV1_0Lm20LogonProfile, + MsV1_0SmartCardProfile +} +alias MSV1_0_PROFILE_BUFFER_TYPE* PMSV1_0_PROFILE_BUFFER_TYPE; + + +enum MSV1_0_AVID { + MsvAvEOL, + MsvAvNbComputerName, + MsvAvNbDomainName, + MsvAvDnsComputerName, + MsvAvDnsDomainName +} + +enum MSV1_0_PROTOCOL_MESSAGE_TYPE { + MsV1_0Lm20ChallengeRequest = 0, + MsV1_0Lm20GetChallengeResponse, + MsV1_0EnumerateUsers, + MsV1_0GetUserInfo, + MsV1_0ReLogonUsers, + MsV1_0ChangePassword, + MsV1_0ChangeCachedPassword, + MsV1_0GenericPassthrough, + MsV1_0CacheLogon, + MsV1_0SubAuth, + MsV1_0DeriveCredential, + MsV1_0CacheLookup +} +alias MSV1_0_PROTOCOL_MESSAGE_TYPE* PMSV1_0_PROTOCOL_MESSAGE_TYPE; + +enum POLICY_LSA_SERVER_ROLE { + PolicyServerRoleBackup = 2, + PolicyServerRolePrimary +} +alias POLICY_LSA_SERVER_ROLE* PPOLICY_LSA_SERVER_ROLE; + +enum POLICY_SERVER_ENABLE_STATE { + PolicyServerEnabled = 2, + PolicyServerDisabled +} +alias POLICY_SERVER_ENABLE_STATE* PPOLICY_SERVER_ENABLE_STATE; + +enum POLICY_INFORMATION_CLASS { + PolicyAuditLogInformation = 1, + PolicyAuditEventsInformation, + PolicyPrimaryDomainInformation, + PolicyPdAccountInformation, + PolicyAccountDomainInformation, + PolicyLsaServerRoleInformation, + PolicyReplicaSourceInformation, + PolicyDefaultQuotaInformation, + PolicyModificationInformation, + PolicyAuditFullSetInformation, + PolicyAuditFullQueryInformation, + PolicyDnsDomainInformation, + PolicyEfsInformation +} +alias POLICY_INFORMATION_CLASS* PPOLICY_INFORMATION_CLASS; + +enum POLICY_AUDIT_EVENT_TYPE { + AuditCategorySystem, + AuditCategoryLogon, + AuditCategoryObjectAccess, + AuditCategoryPrivilegeUse, + AuditCategoryDetailedTracking, + AuditCategoryPolicyChange, + AuditCategoryAccountManagement, + AuditCategoryDirectoryServiceAccess, + AuditCategoryAccountLogon +} +alias POLICY_AUDIT_EVENT_TYPE* PPOLICY_AUDIT_EVENT_TYPE; + +enum POLICY_LOCAL_INFORMATION_CLASS { + PolicyLocalAuditEventsInformation = 1, + PolicyLocalPdAccountInformation, + PolicyLocalAccountDomainInformation, + PolicyLocalLsaServerRoleInformation, + PolicyLocalReplicaSourceInformation, + PolicyLocalModificationInformation, + PolicyLocalAuditFullSetInformation, + PolicyLocalAuditFullQueryInformation, + PolicyLocalDnsDomainInformation, + PolicyLocalIPSecReferenceInformation, + PolicyLocalMachinePasswordInformation, + PolicyLocalQualityOfServiceInformation, + PolicyLocalPolicyLocationInformation +} +alias POLICY_LOCAL_INFORMATION_CLASS* PPOLICY_LOCAL_INFORMATION_CLASS; + +enum POLICY_DOMAIN_INFORMATION_CLASS { + PolicyDomainIPSecReferenceInformation = 1, + PolicyDomainQualityOfServiceInformation, + PolicyDomainEfsInformation, + PolicyDomainPublicKeyInformation, + PolicyDomainPasswordPolicyInformation, + PolicyDomainLockoutInformation, + PolicyDomainKerberosTicketInformation +} +alias POLICY_DOMAIN_INFORMATION_CLASS* PPOLICY_DOMAIN_INFORMATION_CLASS; + +enum SECURITY_LOGON_TYPE { + Interactive = 2, + Network, + Batch, + Service, + Proxy, + Unlock +} +alias SECURITY_LOGON_TYPE* PSECURITY_LOGON_TYPE; + +enum TRUSTED_INFORMATION_CLASS { + TrustedDomainNameInformation = 1, + TrustedControllersInformation, + TrustedPosixOffsetInformation, + TrustedPasswordInformation, + TrustedDomainInformationBasic, + TrustedDomainInformationEx, + TrustedDomainAuthInformation, + TrustedDomainFullInformation +} +alias TRUSTED_INFORMATION_CLASS* PTRUSTED_INFORMATION_CLASS; + +struct DOMAIN_PASSWORD_INFORMATION { + USHORT MinPasswordLength; + USHORT PasswordHistoryLength; + ULONG PasswordProperties; + LARGE_INTEGER MaxPasswordAge; + LARGE_INTEGER MinPasswordAge; +} +alias DOMAIN_PASSWORD_INFORMATION* PDOMAIN_PASSWORD_INFORMATION; + +struct LSA_ENUMERATION_INFORMATION { + PSID Sid; +} +alias LSA_ENUMERATION_INFORMATION* PLSA_ENUMERATION_INFORMATION; + +alias OBJECT_ATTRIBUTES LSA_OBJECT_ATTRIBUTES; +alias OBJECT_ATTRIBUTES* PLSA_OBJECT_ATTRIBUTES; + +struct LSA_TRUST_INFORMATION { + LSA_UNICODE_STRING Name; + PSID Sid; +} +alias LSA_TRUST_INFORMATION TRUSTED_DOMAIN_INFORMATION_BASIC; +alias LSA_TRUST_INFORMATION* PLSA_TRUST_INFORMATION; +/* in MinGW (further down the code): + * typedef PLSA_TRUST_INFORMATION *PTRUSTED_DOMAIN_INFORMATION_BASIC; + * but it doesn't look right.... + */ +alias LSA_TRUST_INFORMATION** PTRUSTED_DOMAIN_INFORMATION_BASIC; + +struct LSA_REFERENCED_DOMAIN_LIST { + ULONG Entries; + PLSA_TRUST_INFORMATION Domains; +} +alias LSA_REFERENCED_DOMAIN_LIST* PLSA_REFERENCED_DOMAIN_LIST; + +struct LSA_TRANSLATED_SID { + SID_NAME_USE Use; + ULONG RelativeId; + LONG DomainIndex; +} +alias LSA_TRANSLATED_SID* PLSA_TRANSLATED_SID; + +struct LSA_TRANSLATED_NAME { + SID_NAME_USE Use; + LSA_UNICODE_STRING Name; + LONG DomainIndex; +} +alias LSA_TRANSLATED_NAME* PLSA_TRANSLATED_NAME; + +struct MSV1_0_INTERACTIVE_LOGON { + MSV1_0_LOGON_SUBMIT_TYPE MessageType; + UNICODE_STRING LogonDomainName; + UNICODE_STRING UserName; + UNICODE_STRING Password; +} +alias MSV1_0_INTERACTIVE_LOGON* PMSV1_0_INTERACTIVE_LOGON; + +struct MSV1_0_INTERACTIVE_PROFILE { + MSV1_0_PROFILE_BUFFER_TYPE MessageType; + USHORT LogonCount; + USHORT BadPasswordCount; + LARGE_INTEGER LogonTime; + LARGE_INTEGER LogoffTime; + LARGE_INTEGER KickOffTime; + LARGE_INTEGER PasswordLastSet; + LARGE_INTEGER PasswordCanChange; + LARGE_INTEGER PasswordMustChange; + UNICODE_STRING LogonScript; + UNICODE_STRING HomeDirectory; + UNICODE_STRING FullName; + UNICODE_STRING ProfilePath; + UNICODE_STRING HomeDirectoryDrive; + UNICODE_STRING LogonServer; + ULONG UserFlags; +} +alias MSV1_0_INTERACTIVE_PROFILE* PMSV1_0_INTERACTIVE_PROFILE; + +struct MSV1_0_LM20_LOGON { + MSV1_0_LOGON_SUBMIT_TYPE MessageType; + UNICODE_STRING LogonDomainName; + UNICODE_STRING UserName; + UNICODE_STRING Workstation; + UCHAR[MSV1_0_CHALLENGE_LENGTH] ChallengeToClient; + STRING CaseSensitiveChallengeResponse; + STRING CaseInsensitiveChallengeResponse; + ULONG ParameterControl; +} +alias MSV1_0_LM20_LOGON* PMSV1_0_LM20_LOGON; + +//static if (_WIN32_WINNT >= 0x500) { + struct MSV1_0_SUBAUTH_LOGON { + MSV1_0_LOGON_SUBMIT_TYPE MessageType; + UNICODE_STRING LogonDomainName; + UNICODE_STRING UserName; + UNICODE_STRING Workstation; + UCHAR[MSV1_0_CHALLENGE_LENGTH] ChallengeToClient; + STRING AuthenticationInfo1; + STRING AuthenticationInfo2; + ULONG ParameterControl; + ULONG SubAuthPackageId; + } + alias MSV1_0_SUBAUTH_LOGON* PMSV1_0_SUBAUTH_LOGON; +//} + +struct MSV1_0_LM20_LOGON_PROFILE { + MSV1_0_PROFILE_BUFFER_TYPE MessageType; + LARGE_INTEGER KickOffTime; + LARGE_INTEGER LogoffTime; + ULONG UserFlags; + UCHAR[MSV1_0_USER_SESSION_KEY_LENGTH] UserSessionKey; + UNICODE_STRING LogonDomainName; + UCHAR[MSV1_0_LANMAN_SESSION_KEY_LENGTH] LanmanSessionKey; + UNICODE_STRING LogonServer; + UNICODE_STRING UserParameters; +} +alias MSV1_0_LM20_LOGON_PROFILE* PMSV1_0_LM20_LOGON_PROFILE; + +struct MSV1_0_SUPPLEMENTAL_CREDENTIAL { + ULONG Version; + ULONG Flags; + UCHAR[MSV1_0_OWF_PASSWORD_LENGTH] LmPassword; + UCHAR[MSV1_0_OWF_PASSWORD_LENGTH] NtPassword; +} +alias MSV1_0_SUPPLEMENTAL_CREDENTIAL* PMSV1_0_SUPPLEMENTAL_CREDENTIAL; + +struct MSV1_0_NTLM3_RESPONSE { + UCHAR[MSV1_0_NTLM3_RESPONSE_LENGTH] Response; + UCHAR RespType; + UCHAR HiRespType; + USHORT Flags; + ULONG MsgWord; + ULONGLONG TimeStamp; + UCHAR[MSV1_0_CHALLENGE_LENGTH] ChallengeFromClient; + ULONG AvPairsOff; + UCHAR _Buffer; + UCHAR* Buffer() return { return &_Buffer; } +} +alias MSV1_0_NTLM3_RESPONSE* PMSV1_0_NTLM3_RESPONSE; + +struct MSV1_0_AV_PAIR { + USHORT AvId; + USHORT AvLen; +} +alias MSV1_0_AV_PAIR* PMSV1_0_AV_PAIR; + +struct MSV1_0_CHANGEPASSWORD_REQUEST { + MSV1_0_PROTOCOL_MESSAGE_TYPE MessageType; + UNICODE_STRING DomainName; + UNICODE_STRING AccountName; + UNICODE_STRING OldPassword; + UNICODE_STRING NewPassword; + BOOLEAN Impersonating; +} +alias MSV1_0_CHANGEPASSWORD_REQUEST* PMSV1_0_CHANGEPASSWORD_REQUEST; + +struct MSV1_0_CHANGEPASSWORD_RESPONSE { + MSV1_0_PROTOCOL_MESSAGE_TYPE MessageType; + BOOLEAN PasswordInfoValid; + DOMAIN_PASSWORD_INFORMATION DomainPasswordInfo; +} +alias MSV1_0_CHANGEPASSWORD_RESPONSE* PMSV1_0_CHANGEPASSWORD_RESPONSE; + +struct MSV1_0_SUBAUTH_REQUEST { + MSV1_0_PROTOCOL_MESSAGE_TYPE MessageType; + ULONG SubAuthPackageId; + ULONG SubAuthInfoLength; + PUCHAR SubAuthSubmitBuffer; +} +alias MSV1_0_SUBAUTH_REQUEST* PMSV1_0_SUBAUTH_REQUEST; + +struct MSV1_0_SUBAUTH_RESPONSE { + MSV1_0_PROTOCOL_MESSAGE_TYPE MessageType; + ULONG SubAuthInfoLength; + PUCHAR SubAuthReturnBuffer; +} +alias MSV1_0_SUBAUTH_RESPONSE* PMSV1_0_SUBAUTH_RESPONSE; + +enum MSV1_0_DERIVECRED_TYPE_SHA1 = 0; + +struct MSV1_0_DERIVECRED_REQUEST { + MSV1_0_PROTOCOL_MESSAGE_TYPE MessageType; + LUID LogonId; + ULONG DeriveCredType; + ULONG DeriveCredInfoLength; + UCHAR _DeriveCredSubmitBuffer; + UCHAR* DeriveCredSubmitBuffer() return { return &_DeriveCredSubmitBuffer; } +} +alias MSV1_0_DERIVECRED_REQUEST* PMSV1_0_DERIVECRED_REQUEST; + +struct MSV1_0_DERIVECRED_RESPONSE { + MSV1_0_PROTOCOL_MESSAGE_TYPE MessageType; + ULONG DeriveCredInfoLength; + UCHAR _DeriveCredReturnBuffer; + UCHAR* DeriveCredReturnBuffer() return { return &_DeriveCredReturnBuffer; } +} +alias MSV1_0_DERIVECRED_RESPONSE* PMSV1_0_DERIVECRED_RESPONSE; + +alias uint LSA_ENUMERATION_HANDLE, LSA_OPERATIONAL_MODE, + POLICY_AUDIT_EVENT_OPTIONS; +alias uint* PLSA_ENUMERATION_HANDLE, PLSA_OPERATIONAL_MODE, + PPOLICY_AUDIT_EVENT_OPTIONS; + +struct POLICY_PRIVILEGE_DEFINITION { + LSA_UNICODE_STRING Name; + LUID LocalValue; +} +alias POLICY_PRIVILEGE_DEFINITION* PPOLICY_PRIVILEGE_DEFINITION; + +struct POLICY_AUDIT_LOG_INFO { + ULONG AuditLogPercentFull; + ULONG MaximumLogSize; + LARGE_INTEGER AuditRetentionPeriod; + BOOLEAN AuditLogFullShutdownInProgress; + LARGE_INTEGER TimeToShutdown; + ULONG NextAuditRecordId; +} +alias POLICY_AUDIT_LOG_INFO* PPOLICY_AUDIT_LOG_INFO; + +struct POLICY_AUDIT_EVENTS_INFO { + BOOLEAN AuditingMode; + PPOLICY_AUDIT_EVENT_OPTIONS EventAuditingOptions; + ULONG MaximumAuditEventCount; +} +alias POLICY_AUDIT_EVENTS_INFO* PPOLICY_AUDIT_EVENTS_INFO; + +struct POLICY_ACCOUNT_DOMAIN_INFO { + LSA_UNICODE_STRING DomainName; + PSID DomainSid; +} +alias POLICY_ACCOUNT_DOMAIN_INFO* PPOLICY_ACCOUNT_DOMAIN_INFO; + +struct POLICY_PRIMARY_DOMAIN_INFO { + LSA_UNICODE_STRING Name; + PSID Sid; +} +alias POLICY_PRIMARY_DOMAIN_INFO* PPOLICY_PRIMARY_DOMAIN_INFO; + +struct POLICY_DNS_DOMAIN_INFO { + LSA_UNICODE_STRING Name; + LSA_UNICODE_STRING DnsDomainName; + LSA_UNICODE_STRING DnsTreeName; + GUID DomainGuid; + PSID Sid; +} +alias POLICY_DNS_DOMAIN_INFO* PPOLICY_DNS_DOMAIN_INFO; + +struct POLICY_PD_ACCOUNT_INFO { + LSA_UNICODE_STRING Name; +} +alias POLICY_PD_ACCOUNT_INFO* PPOLICY_PD_ACCOUNT_INFO; + +struct POLICY_LSA_SERVER_ROLE_INFO { + POLICY_LSA_SERVER_ROLE LsaServerRole; +} +alias POLICY_LSA_SERVER_ROLE_INFO* PPOLICY_LSA_SERVER_ROLE_INFO; + +struct POLICY_REPLICA_SOURCE_INFO { + LSA_UNICODE_STRING ReplicaSource; + LSA_UNICODE_STRING ReplicaAccountName; +} +alias POLICY_REPLICA_SOURCE_INFO* PPOLICY_REPLICA_SOURCE_INFO; + +struct POLICY_DEFAULT_QUOTA_INFO { + QUOTA_LIMITS QuotaLimits; +} +alias POLICY_DEFAULT_QUOTA_INFO* PPOLICY_DEFAULT_QUOTA_INFO; + +struct POLICY_MODIFICATION_INFO { + LARGE_INTEGER ModifiedId; + LARGE_INTEGER DatabaseCreationTime; +} +alias POLICY_MODIFICATION_INFO* PPOLICY_MODIFICATION_INFO; + +struct POLICY_AUDIT_FULL_SET_INFO { + BOOLEAN ShutDownOnFull; +} +alias POLICY_AUDIT_FULL_SET_INFO* PPOLICY_AUDIT_FULL_SET_INFO; + +struct POLICY_AUDIT_FULL_QUERY_INFO { + BOOLEAN ShutDownOnFull; + BOOLEAN LogIsFull; +} +alias POLICY_AUDIT_FULL_QUERY_INFO* PPOLICY_AUDIT_FULL_QUERY_INFO; + +struct POLICY_EFS_INFO { + ULONG InfoLength; + PUCHAR EfsBlob; +} +alias POLICY_EFS_INFO* PPOLICY_EFS_INFO; + +struct POLICY_LOCAL_IPSEC_REFERENCE_INFO { + LSA_UNICODE_STRING ObjectPath; +} +alias POLICY_LOCAL_IPSEC_REFERENCE_INFO* PPOLICY_LOCAL_IPSEC_REFERENCE_INFO; + +struct POLICY_LOCAL_MACHINE_PASSWORD_INFO { + LARGE_INTEGER PasswordChangeInterval; +} +alias POLICY_LOCAL_MACHINE_PASSWORD_INFO* PPOLICY_LOCAL_MACHINE_PASSWORD_INFO; + +struct POLICY_LOCAL_POLICY_LOCATION_INFO { + ULONG PolicyLocation; +} +alias POLICY_LOCAL_POLICY_LOCATION_INFO* PPOLICY_LOCAL_POLICY_LOCATION_INFO; + +struct POLICY_LOCAL_QUALITY_OF_SERVICE_INFO{ + ULONG QualityOfService; +} +alias POLICY_LOCAL_QUALITY_OF_SERVICE_INFO + POLICY_DOMAIN_QUALITY_OF_SERVICE_INFO; +alias POLICY_LOCAL_QUALITY_OF_SERVICE_INFO* + PPOLICY_LOCAL_QUALITY_OF_SERVICE_INFO, + PPOLICY_DOMAIN_QUALITY_OF_SERVICE_INFO; + +struct POLICY_DOMAIN_PUBLIC_KEY_INFO { + ULONG InfoLength; + PUCHAR PublicKeyInfo; +} +alias POLICY_DOMAIN_PUBLIC_KEY_INFO* PPOLICY_DOMAIN_PUBLIC_KEY_INFO; + +struct POLICY_DOMAIN_LOCKOUT_INFO { + LARGE_INTEGER LockoutDuration; + LARGE_INTEGER LockoutObservationWindow; + USHORT LockoutThreshold; +} +alias POLICY_DOMAIN_LOCKOUT_INFO* PPOLICY_DOMAIN_LOCKOUT_INFO; + +struct POLICY_DOMAIN_PASSWORD_INFO { + USHORT MinPasswordLength; + USHORT PasswordHistoryLength; + ULONG PasswordProperties; + LARGE_INTEGER MaxPasswordAge; + LARGE_INTEGER MinPasswordAge; +} +alias POLICY_DOMAIN_PASSWORD_INFO* PPOLICY_DOMAIN_PASSWORD_INFO; + +struct POLICY_DOMAIN_KERBEROS_TICKET_INFO { + ULONG AuthenticationOptions; + LARGE_INTEGER MinTicketAge; + LARGE_INTEGER MaxTicketAge; + LARGE_INTEGER MaxRenewAge; + LARGE_INTEGER ProxyLifetime; + LARGE_INTEGER ForceLogoff; +} +alias POLICY_DOMAIN_KERBEROS_TICKET_INFO* PPOLICY_DOMAIN_KERBEROS_TICKET_INFO; + +alias LSA_HANDLE = HANDLE; +alias LSA_HANDLE* PLSA_HANDLE; + +struct TRUSTED_DOMAIN_NAME_INFO { + LSA_UNICODE_STRING Name; +} +alias TRUSTED_DOMAIN_NAME_INFO* PTRUSTED_DOMAIN_NAME_INFO; + +struct TRUSTED_CONTROLLERS_INFO { + ULONG Entries; + PLSA_UNICODE_STRING Names; +} +alias TRUSTED_CONTROLLERS_INFO* PTRUSTED_CONTROLLERS_INFO; + +struct TRUSTED_POSIX_OFFSET_INFO { + ULONG Offset; +} +alias TRUSTED_POSIX_OFFSET_INFO* PTRUSTED_POSIX_OFFSET_INFO; + +struct TRUSTED_PASSWORD_INFO { + LSA_UNICODE_STRING Password; + LSA_UNICODE_STRING OldPassword; +} +alias TRUSTED_PASSWORD_INFO* PTRUSTED_PASSWORD_INFO; + +struct TRUSTED_DOMAIN_INFORMATION_EX { + LSA_UNICODE_STRING Name; + LSA_UNICODE_STRING FlatName; + PSID Sid; + ULONG TrustDirection; + ULONG TrustType; + ULONG TrustAttributes; +} +alias TRUSTED_DOMAIN_INFORMATION_EX* PTRUSTED_DOMAIN_INFORMATION_EX; + +struct LSA_AUTH_INFORMATION { + LARGE_INTEGER LastUpdateTime; + ULONG AuthType; + ULONG AuthInfoLength; + PUCHAR AuthInfo; +} +alias LSA_AUTH_INFORMATION* PLSA_AUTH_INFORMATION; + +struct TRUSTED_DOMAIN_AUTH_INFORMATION { + ULONG IncomingAuthInfos; + PLSA_AUTH_INFORMATION IncomingAuthenticationInformation; + PLSA_AUTH_INFORMATION IncomingPreviousAuthenticationInformation; + ULONG OutgoingAuthInfos; + PLSA_AUTH_INFORMATION OutgoingAuthenticationInformation; + PLSA_AUTH_INFORMATION OutgoingPreviousAuthenticationInformation; +} +alias TRUSTED_DOMAIN_AUTH_INFORMATION* PTRUSTED_DOMAIN_AUTH_INFORMATION; + +struct TRUSTED_DOMAIN_FULL_INFORMATION { + TRUSTED_DOMAIN_INFORMATION_EX Information; + TRUSTED_POSIX_OFFSET_INFO PosixOffset; + TRUSTED_DOMAIN_AUTH_INFORMATION AuthInformation; +} +alias TRUSTED_DOMAIN_FULL_INFORMATION* PTRUSTED_DOMAIN_FULL_INFORMATION; + +extern (Windows) nothrow @nogc { + NTSTATUS LsaAddAccountRights(LSA_HANDLE, PSID, PLSA_UNICODE_STRING, + ULONG); + NTSTATUS LsaCallAuthenticationPackage(HANDLE, ULONG, PVOID, ULONG, + PVOID*, PULONG, PNTSTATUS); + NTSTATUS LsaClose(LSA_HANDLE); + NTSTATUS LsaConnectUntrusted(PHANDLE); + NTSTATUS LsaCreateTrustedDomainEx(LSA_HANDLE, + PTRUSTED_DOMAIN_INFORMATION_EX, PTRUSTED_DOMAIN_AUTH_INFORMATION, + ACCESS_MASK, PLSA_HANDLE); + NTSTATUS LsaDeleteTrustedDomain(LSA_HANDLE, PSID); + NTSTATUS LsaDeregisterLogonProcess(HANDLE); + NTSTATUS LsaEnumerateAccountRights(LSA_HANDLE, PSID, PLSA_UNICODE_STRING*, + PULONG); + NTSTATUS LsaEnumerateAccountsWithUserRight(LSA_HANDLE, + PLSA_UNICODE_STRING, PVOID*, PULONG); + NTSTATUS LsaEnumerateTrustedDomains(LSA_HANDLE, PLSA_ENUMERATION_HANDLE, + PVOID*, ULONG, PULONG); + NTSTATUS LsaEnumerateTrustedDomainsEx(LSA_HANDLE, PLSA_ENUMERATION_HANDLE, + TRUSTED_INFORMATION_CLASS, PVOID*, ULONG, PULONG); + NTSTATUS LsaFreeMemory(PVOID); + NTSTATUS LsaFreeReturnBuffer(PVOID); + NTSTATUS LsaLogonUser(HANDLE, PLSA_STRING, SECURITY_LOGON_TYPE, ULONG, + PVOID, ULONG, PTOKEN_GROUPS, PTOKEN_SOURCE, PVOID*, PULONG, PLUID, + PHANDLE, PQUOTA_LIMITS, PNTSTATUS); + NTSTATUS LsaLookupAuthenticationPackage(HANDLE, PLSA_STRING, PULONG); + NTSTATUS LsaLookupNames(LSA_HANDLE, ULONG, PLSA_UNICODE_STRING, + PLSA_REFERENCED_DOMAIN_LIST*, PLSA_TRANSLATED_SID*); + NTSTATUS LsaLookupSids(LSA_HANDLE, ULONG, PSID*, + PLSA_REFERENCED_DOMAIN_LIST*, PLSA_TRANSLATED_NAME*); + ULONG LsaNtStatusToWinError(NTSTATUS); + NTSTATUS LsaOpenPolicy(PLSA_UNICODE_STRING, PLSA_OBJECT_ATTRIBUTES, + ACCESS_MASK, PLSA_HANDLE); + NTSTATUS LsaQueryDomainInformationPolicy(LSA_HANDLE, + POLICY_DOMAIN_INFORMATION_CLASS, PVOID*); + NTSTATUS LsaQueryInformationPolicy(LSA_HANDLE, POLICY_INFORMATION_CLASS, + PVOID*); + NTSTATUS LsaQueryLocalInformationPolicy(LSA_HANDLE, + POLICY_LOCAL_INFORMATION_CLASS, PVOID*); + NTSTATUS LsaQueryTrustedDomainInfo(LSA_HANDLE, PSID, + TRUSTED_INFORMATION_CLASS, PVOID*); + NTSTATUS LsaQueryTrustedDomainInfoByName(LSA_HANDLE, PLSA_UNICODE_STRING, + TRUSTED_INFORMATION_CLASS, PVOID*); + NTSTATUS LsaRegisterLogonProcess(PLSA_STRING, PHANDLE, + PLSA_OPERATIONAL_MODE); + NTSTATUS LsaRemoveAccountRights(LSA_HANDLE, PSID, BOOLEAN, + PLSA_UNICODE_STRING, ULONG); + NTSTATUS LsaRetrievePrivateData(LSA_HANDLE, PLSA_UNICODE_STRING, + PLSA_UNICODE_STRING*); + NTSTATUS LsaSetDomainInformationPolicy(LSA_HANDLE, + POLICY_DOMAIN_INFORMATION_CLASS, PVOID); + NTSTATUS LsaSetInformationPolicy(LSA_HANDLE, POLICY_INFORMATION_CLASS, + PVOID); + NTSTATUS LsaSetLocalInformationPolicy(LSA_HANDLE, + POLICY_LOCAL_INFORMATION_CLASS, PVOID); + NTSTATUS LsaSetTrustedDomainInformation(LSA_HANDLE, PSID, + TRUSTED_INFORMATION_CLASS, PVOID); + NTSTATUS LsaSetTrustedDomainInfoByName(LSA_HANDLE, PLSA_UNICODE_STRING, + TRUSTED_INFORMATION_CLASS, PVOID); + NTSTATUS LsaStorePrivateData(LSA_HANDLE, PLSA_UNICODE_STRING, + PLSA_UNICODE_STRING); +} + +alias NTSTATUS function(PUNICODE_STRING, ULONG, PUNICODE_STRING) + PSAM_PASSWORD_NOTIFICATION_ROUTINE; +alias BOOLEAN function() PSAM_INIT_NOTIFICATION_ROUTINE; +alias BOOLEAN function(PUNICODE_STRING, PUNICODE_STRING, + PUNICODE_STRING, BOOLEAN) PSAM_PASSWORD_FILTER_ROUTINE; diff --git a/src/urt/internal/sys/windows/ntsecpkg.d b/src/urt/internal/sys/windows/ntsecpkg.d new file mode 100644 index 0000000..6a2dc69 --- /dev/null +++ b/src/urt/internal/sys/windows/ntsecpkg.d @@ -0,0 +1,445 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * Authors: Ellery Newcomer + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_ntsecpkg.d) + */ +module urt.internal.sys.windows.ntsecpkg; +version (Windows): + +import urt.internal.sys.windows.windef, urt.internal.sys.windows.ntsecapi, urt.internal.sys.windows.security, urt.internal.sys.windows.ntdef, urt.internal.sys.windows.sspi; +import urt.internal.sys.windows.basetyps : GUID; +import urt.internal.sys.windows.winbase; + +extern(Windows): + +enum :ULONG{ + ISC_REQ_DELEGATE = 1, + ISC_REQ_MUTUAL_AUTH = 2, + ISC_REQ_REPLAY_DETECT = 4, + ISC_REQ_SEQUENCE_DETECT = 8, + ISC_REQ_CONFIDENTIALITY = 16, + ISC_REQ_USE_SESSION_KEY = 32, + ISC_REQ_PROMPT_FOR_CREDS = 64, + ISC_REQ_USE_SUPPLIED_CREDS = 128, + ISC_REQ_ALLOCATE_MEMORY = 256, + ISC_REQ_USE_DCE_STYLE = 512, + ISC_REQ_DATAGRAM = 1024, + ISC_REQ_CONNECTION = 2048, + ISC_REQ_EXTENDED_ERROR = 16384, + ISC_REQ_STREAM = 32768, + ISC_REQ_INTEGRITY = 65536, + ISC_REQ_MANUAL_CRED_VALIDATION = 524288, + ISC_REQ_HTTP = 268435456, +} + +enum ISC_RET_EXTENDED_ERROR = 16384; + +enum :ULONG{ + ASC_REQ_DELEGATE = 1, + ASC_REQ_MUTUAL_AUTH = 2, + ASC_REQ_REPLAY_DETECT = 4, + ASC_REQ_SEQUENCE_DETECT = 8, + ASC_REQ_CONFIDENTIALITY = 16, + ASC_REQ_USE_SESSION_KEY = 32, + ASC_REQ_ALLOCATE_MEMORY = 256, + ASC_REQ_USE_DCE_STYLE = 512, + ASC_REQ_DATAGRAM = 1024, + ASC_REQ_CONNECTION = 2048, + ASC_REQ_EXTENDED_ERROR = 32768, + ASC_REQ_STREAM = 65536, + ASC_REQ_INTEGRITY = 131072, +} + +enum SECURITY_NATIVE_DREP = 16; +enum SECURITY_NETWORK_DREP = 0; + +enum :ULONG{ + SECPKG_STATE_ENCRYPTION_PERMITTED = 0x01, + SECPKG_STATE_STRONG_ENCRYPTION_PERMITTED = 0x02, + SECPKG_STATE_DOMAIN_CONTROLLER = 0x04, + SECPKG_STATE_WORKSTATION = 0x08, + SECPKG_STATE_STANDALONE = 0x10, +} + +/* enum definitions for Secure Service Provider/Authentication Packages */ +enum LSA_TOKEN_INFORMATION_TYPE { + LsaTokenInformationNull, + LsaTokenInformationV1 +} +alias LSA_TOKEN_INFORMATION_TYPE* PLSA_TOKEN_INFORMATION_TYPE; +enum SECPKG_EXTENDED_INFORMATION_CLASS +{ + SecpkgGssInfo = 1, + SecpkgContextThunks, + SecpkgMutualAuthLevel, + SecpkgMaxInfo +} +enum SECPKG_NAME_TYPE { + SecNameSamCompatible, + SecNameAlternateId, + SecNameFlat, + SecNameDN +} + +/* struct definitions for SSP/AP */ +struct SECPKG_PRIMARY_CRED { + LUID LogonId; + UNICODE_STRING DownlevelName; + UNICODE_STRING DomainName; + UNICODE_STRING Password; + UNICODE_STRING OldPassword; + PSID UserSid; + ULONG Flags; + UNICODE_STRING DnsDomainName; + UNICODE_STRING Upn; + UNICODE_STRING LogonServer; + UNICODE_STRING Spare1; + UNICODE_STRING Spare2; + UNICODE_STRING Spare3; + UNICODE_STRING Spare4; +} +alias SECPKG_PRIMARY_CRED* PSECPKG_PRIMARY_CRED; +struct SECPKG_SUPPLEMENTAL_CRED { + UNICODE_STRING PackageName; + ULONG CredentialSize; + PUCHAR Credentials; +} +alias SECPKG_SUPPLEMENTAL_CRED* PSECPKG_SUPPLEMENTAL_CRED; +struct SECPKG_SUPPLEMENTAL_CRED_ARRAY { + ULONG CredentialCount; + SECPKG_SUPPLEMENTAL_CRED[1] Credentials; +} +alias SECPKG_SUPPLEMENTAL_CRED_ARRAY* PSECPKG_SUPPLEMENTAL_CRED_ARRAY; +struct SECPKG_PARAMETERS { + ULONG Version; + ULONG MachineState; + ULONG SetupMode; + PSID DomainSid; + UNICODE_STRING DomainName; + UNICODE_STRING DnsDomainName; + GUID DomainGuid; +} +alias SECPKG_PARAMETERS* PSECPKG_PARAMETERS,PSECPKG_EVENT_DOMAIN_CHANGE; +alias SECPKG_PARAMETERS SECPKG_EVENT_DOMAIN_CHANGE; +struct SECPKG_CLIENT_INFO { + LUID LogonId; + ULONG ProcessID; + ULONG ThreadID; + BOOLEAN HasTcbPrivilege; + BOOLEAN Impersonating; + BOOLEAN Restricted; +} +alias SECPKG_CLIENT_INFO* PSECPKG_CLIENT_INFO; +struct SECURITY_USER_DATA { + SECURITY_STRING UserName; + SECURITY_STRING LogonDomainName; + SECURITY_STRING LogonServer; + PSID pSid; +} +alias SECURITY_USER_DATA* PSECURITY_USER_DATA,PSecurityUserData; +alias SECURITY_USER_DATA SecurityUserData; +struct SECPKG_GSS_INFO { + ULONG EncodedIdLength; + UCHAR[4] EncodedId; +} +alias SECPKG_GSS_INFO* PSECPKG_GSS_INFO; +struct SECPKG_CONTEXT_THUNKS { + ULONG InfoLevelCount; + ULONG[1] Levels; +} +alias SECPKG_CONTEXT_THUNKS* PSECPKG_CONTEXT_THUNKS; +struct SECPKG_MUTUAL_AUTH_LEVEL { + ULONG MutualAuthLevel; +} +alias SECPKG_MUTUAL_AUTH_LEVEL* PSECPKG_MUTUAL_AUTH_LEVEL; +struct SECPKG_CALL_INFO { + ULONG ProcessId; + ULONG ThreadId; + ULONG Attributes; + ULONG CallCount; +} +alias SECPKG_CALL_INFO* PSECPKG_CALL_INFO; +struct SECPKG_EXTENDED_INFORMATION { + SECPKG_EXTENDED_INFORMATION_CLASS Class; + union _Info{ + SECPKG_GSS_INFO GssInfo; + SECPKG_CONTEXT_THUNKS ContextThunks; + SECPKG_MUTUAL_AUTH_LEVEL MutualAuthLevel; + } + _Info Info; +} +alias SECPKG_EXTENDED_INFORMATION* PSECPKG_EXTENDED_INFORMATION; + +/* callbacks implemented by SSP/AP dlls and called by the LSA */ +alias void function(ULONG_PTR, ULONG_PTR, PSecBuffer, + PSecBuffer) PLSA_CALLBACK_FUNCTION; + +/* misc typedefs used in the below prototypes */ +alias PVOID* PLSA_CLIENT_REQUEST; +alias ULONG_PTR LSA_SEC_HANDLE; +alias LSA_SEC_HANDLE* PLSA_SEC_HANDLE; +alias LPTHREAD_START_ROUTINE SEC_THREAD_START; +alias PSECURITY_ATTRIBUTES SEC_ATTRS; + +/* functions used by SSP/AP obtainable by dispatch tables */ +alias NTSTATUS function(ULONG, PLSA_CALLBACK_FUNCTION) PLSA_REGISTER_CALLBACK; +alias NTSTATUS function(PLUID) PLSA_CREATE_LOGON_SESSION; +alias NTSTATUS function(PLUID) PLSA_DELETE_LOGON_SESSION; +alias NTSTATUS function(PLUID, ULONG, PLSA_STRING, + PLSA_STRING) PLSA_ADD_CREDENTIAL; +alias NTSTATUS function(PLUID, ULONG, PULONG, BOOLEAN, + PLSA_STRING, PULONG, PLSA_STRING) PLSA_GET_CREDENTIALS; +alias NTSTATUS function(PLUID, ULONG, PLSA_STRING) PLSA_DELETE_CREDENTIAL; +alias PVOID function(ULONG) PLSA_ALLOCATE_LSA_HEAP; +alias void function(PVOID) PLSA_FREE_LSA_HEAP; +alias NTSTATUS function(PLSA_CLIENT_REQUEST, + ULONG, PVOID*) PLSA_ALLOCATE_CLIENT_BUFFER; +alias NTSTATUS function(PLSA_CLIENT_REQUEST, PVOID) PLSA_FREE_CLIENT_BUFFER; +alias NTSTATUS function(PLSA_CLIENT_REQUEST, ULONG, + PVOID, PVOID) PLSA_COPY_TO_CLIENT_BUFFER; +alias NTSTATUS function(PLSA_CLIENT_REQUEST, + ULONG, PVOID, PVOID) PLSA_COPY_FROM_CLIENT_BUFFER; +alias NTSTATUS function() PLSA_IMPERSONATE_CLIENT; +alias NTSTATUS function() PLSA_UNLOAD_PACKAGE; +alias NTSTATUS function(HANDLE, PHANDLE) PLSA_DUPLICATE_HANDLE; +alias NTSTATUS function(PLUID, ULONG, + PVOID, BOOLEAN) PLSA_SAVE_SUPPLEMENTAL_CREDENTIALS; +alias HANDLE function(SEC_ATTRS, ULONG, SEC_THREAD_START, + PVOID, ULONG, PULONG) PLSA_CREATE_THREAD; +alias NTSTATUS function(PSECPKG_CLIENT_INFO) PLSA_GET_CLIENT_INFO; +alias HANDLE function(SEC_THREAD_START, PVOID, + ULONG, ULONG, ULONG, ULONG, HANDLE) PLSA_REGISTER_NOTIFICATION; +alias NTSTATUS function(HANDLE) PLSA_CANCEL_NOTIFICATION; +alias NTSTATUS function(PSecBuffer, PSecBuffer) PLSA_MAP_BUFFER; +alias NTSTATUS function(PLUID, PTOKEN_SOURCE, + SECURITY_LOGON_TYPE, SECURITY_IMPERSONATION_LEVEL, LSA_TOKEN_INFORMATION_TYPE, + PVOID, PTOKEN_GROUPS, PUNICODE_STRING, PUNICODE_STRING, PUNICODE_STRING, + PUNICODE_STRING, PHANDLE, PNTSTATUS) PLSA_CREATE_TOKEN; +alias void function(NTSTATUS, NTSTATUS, PUNICODE_STRING, + PUNICODE_STRING, PUNICODE_STRING, PSID, SECURITY_LOGON_TYPE, + PTOKEN_SOURCE, PLUID) PLSA_AUDIT_LOGON; +alias NTSTATUS function(PUNICODE_STRING, PVOID, ULONG, + PVOID*, PULONG, PNTSTATUS) PLSA_CALL_PACKAGE; +alias BOOLEAN function(PSECPKG_CALL_INFO) PLSA_GET_CALL_INFO; +alias NTSTATUS function(PUNICODE_STRING, PVOID, PVOID, + ULONG, PVOID*, PULONG, PNTSTATUS) PLSA_CALL_PACKAGEEX; +alias PVOID function(ULONG, ULONG) PLSA_CREATE_SHARED_MEMORY; +alias PVOID function(PVOID, ULONG) PLSA_ALLOCATE_SHARED_MEMORY; +alias void function(PVOID, PVOID) PLSA_FREE_SHARED_MEMORY; +alias BOOLEAN function(PVOID) PLSA_DELETE_SHARED_MEMORY; +alias NTSTATUS function(PSECURITY_STRING, SECPKG_NAME_TYPE, + PSECURITY_STRING, BOOLEAN, ULONG, PVOID*) PLSA_OPEN_SAM_USER; +alias NTSTATUS function(PVOID, PVOID *, PULONG, + PVOID *, PULONG) PLSA_GET_USER_CREDENTIALS; +alias NTSTATUS function(PVOID, PUCHAR *, PULONG) PLSA_GET_USER_AUTH_DATA; +alias NTSTATUS function(PVOID) PLSA_CLOSE_SAM_USER; +alias NTSTATUS function(PVOID, ULONG, + SECURITY_IMPERSONATION_LEVEL, PTOKEN_SOURCE, SECURITY_LOGON_TYPE, + PUNICODE_STRING, PHANDLE, PLUID, PUNICODE_STRING, PNTSTATUS) PLSA_CONVERT_AUTH_DATA_TO_TOKEN; +alias NTSTATUS function(PCHAR, ULONG_PTR, ULONG_PTR, + PSecBuffer, PSecBuffer) PLSA_CLIENT_CALLBACK; +alias NTSTATUS function(PSECPKG_PRIMARY_CRED, PSECPKG_SUPPLEMENTAL_CRED_ARRAY) PLSA_UPDATE_PRIMARY_CREDENTIALS; +alias NTSTATUS function(PSECURITY_STRING, + SECPKG_NAME_TYPE, PSECURITY_STRING, PUCHAR *, PULONG, PUNICODE_STRING) PLSA_GET_AUTH_DATA_FOR_USER; +alias NTSTATUS function(ULONG, BOOLEAN, + PUNICODE_STRING, PUNICODE_STRING, ULONG, PUNICODE_STRING, PUNICODE_STRING, + PULONG) PLSA_CRACK_SINGLE_NAME; +alias NTSTATUS function(ULONG, BOOLEAN, + PUNICODE_STRING, PUNICODE_STRING, PUNICODE_STRING, NTSTATUS) PLSA_AUDIT_ACCOUNT_LOGON; +alias NTSTATUS function(PUNICODE_STRING, PVOID, + PVOID, ULONG, PVOID*, PULONG, PNTSTATUS) PLSA_CALL_PACKAGE_PASSTHROUGH; + +/* Dispatch tables of functions used by SSP/AP */ +struct SECPKG_DLL_FUNCTIONS { + PLSA_ALLOCATE_LSA_HEAP AllocateHeap; + PLSA_FREE_LSA_HEAP FreeHeap; + PLSA_REGISTER_CALLBACK RegisterCallback; +} +alias SECPKG_DLL_FUNCTIONS* PSECPKG_DLL_FUNCTIONS; +struct LSA_DISPATCH_TABLE { + PLSA_CREATE_LOGON_SESSION CreateLogonSession; + PLSA_DELETE_LOGON_SESSION DeleteLogonSession; + PLSA_ADD_CREDENTIAL AddCredential; + PLSA_GET_CREDENTIALS GetCredentials; + PLSA_DELETE_CREDENTIAL DeleteCredential; + PLSA_ALLOCATE_LSA_HEAP AllocateLsaHeap; + PLSA_FREE_LSA_HEAP FreeLsaHeap; + PLSA_ALLOCATE_CLIENT_BUFFER AllocateClientBuffer; + PLSA_FREE_CLIENT_BUFFER FreeClientBuffer; + PLSA_COPY_TO_CLIENT_BUFFER CopyToClientBuffer; + PLSA_COPY_FROM_CLIENT_BUFFER CopyFromClientBuffer; +} +alias LSA_DISPATCH_TABLE* PLSA_DISPATCH_TABLE; +struct LSA_SECPKG_FUNCTION_TABLE { + PLSA_CREATE_LOGON_SESSION CreateLogonSession; + PLSA_DELETE_LOGON_SESSION DeleteLogonSession; + PLSA_ADD_CREDENTIAL AddCredential; + PLSA_GET_CREDENTIALS GetCredentials; + PLSA_DELETE_CREDENTIAL DeleteCredential; + PLSA_ALLOCATE_LSA_HEAP AllocateLsaHeap; + PLSA_FREE_LSA_HEAP FreeLsaHeap; + PLSA_ALLOCATE_CLIENT_BUFFER AllocateClientBuffer; + PLSA_FREE_CLIENT_BUFFER FreeClientBuffer; + PLSA_COPY_TO_CLIENT_BUFFER CopyToClientBuffer; + PLSA_COPY_FROM_CLIENT_BUFFER CopyFromClientBuffer; + PLSA_IMPERSONATE_CLIENT ImpersonateClient; + PLSA_UNLOAD_PACKAGE UnloadPackage; + PLSA_DUPLICATE_HANDLE DuplicateHandle; + PLSA_SAVE_SUPPLEMENTAL_CREDENTIALS SaveSupplementalCredentials; + PLSA_CREATE_THREAD CreateThread; + PLSA_GET_CLIENT_INFO GetClientInfo; + PLSA_REGISTER_NOTIFICATION RegisterNotification; + PLSA_CANCEL_NOTIFICATION CancelNotification; + PLSA_MAP_BUFFER MapBuffer; + PLSA_CREATE_TOKEN CreateToken; + PLSA_AUDIT_LOGON AuditLogon; + PLSA_CALL_PACKAGE CallPackage; + PLSA_FREE_LSA_HEAP FreeReturnBuffer; + PLSA_GET_CALL_INFO GetCallInfo; + PLSA_CALL_PACKAGEEX CallPackageEx; + PLSA_CREATE_SHARED_MEMORY CreateSharedMemory; + PLSA_ALLOCATE_SHARED_MEMORY AllocateSharedMemory; + PLSA_FREE_SHARED_MEMORY FreeSharedMemory; + PLSA_DELETE_SHARED_MEMORY DeleteSharedMemory; + PLSA_OPEN_SAM_USER OpenSamUser; + PLSA_GET_USER_CREDENTIALS GetUserCredentials; + PLSA_GET_USER_AUTH_DATA GetUserAuthData; + PLSA_CLOSE_SAM_USER CloseSamUser; + PLSA_CONVERT_AUTH_DATA_TO_TOKEN ConvertAuthDataToToken; + PLSA_CLIENT_CALLBACK ClientCallback; + PLSA_UPDATE_PRIMARY_CREDENTIALS UpdateCredentials; + PLSA_GET_AUTH_DATA_FOR_USER GetAuthDataForUser; + PLSA_CRACK_SINGLE_NAME CrackSingleName; + PLSA_AUDIT_ACCOUNT_LOGON AuditAccountLogon; + PLSA_CALL_PACKAGE_PASSTHROUGH CallPackagePassthrough; +} +alias LSA_SECPKG_FUNCTION_TABLE* PLSA_SECPKG_FUNCTION_TABLE; + +/* functions implemented by SSP/AP obtainable by dispatch tables */ +alias NTSTATUS function(ULONG, PLSA_DISPATCH_TABLE, + PLSA_STRING, PLSA_STRING, PLSA_STRING *) PLSA_AP_INITIALIZE_PACKAGE; +alias NTSTATUS function(LPWSTR, LPWSTR, LPWSTR, LPWSTR, + DWORD, DWORD, PHANDLE) PLSA_AP_LOGON_USER; +alias NTSTATUS function(PUNICODE_STRING, PVOID, ULONG, + PVOID *, PULONG, PNTSTATUS) PLSA_AP_CALL_PACKAGE; +alias void function(PLUID) PLSA_AP_LOGON_TERMINATED; +alias NTSTATUS function(PLSA_CLIENT_REQUEST, + PVOID, PVOID, ULONG, PVOID *, PULONG, PNTSTATUS) PLSA_AP_CALL_PACKAGE_UNTRUSTED; +alias NTSTATUS function(PUNICODE_STRING, + PVOID, PVOID, ULONG, PVOID *, PULONG, PNTSTATUS) PLSA_AP_CALL_PACKAGE_PASSTHROUGH; +alias NTSTATUS function(PLSA_CLIENT_REQUEST, + SECURITY_LOGON_TYPE, PVOID, PVOID, ULONG, PVOID *, PULONG, PLUID, PNTSTATUS, + PLSA_TOKEN_INFORMATION_TYPE, PVOID *, PUNICODE_STRING *, PUNICODE_STRING *, + PUNICODE_STRING *) PLSA_AP_LOGON_USER_EX; +alias NTSTATUS function(PLSA_CLIENT_REQUEST, + SECURITY_LOGON_TYPE, PVOID, PVOID, ULONG, PVOID *, PULONG, PLUID, PNTSTATUS, + PLSA_TOKEN_INFORMATION_TYPE, PVOID *, PUNICODE_STRING *, PUNICODE_STRING *, + PUNICODE_STRING *, PSECPKG_PRIMARY_CRED, PSECPKG_SUPPLEMENTAL_CRED_ARRAY *) PLSA_AP_LOGON_USER_EX2; +alias NTSTATUS function(ULONG_PTR, PSECPKG_PARAMETERS, + PLSA_SECPKG_FUNCTION_TABLE) SpInitializeFn; +alias NTSTATUS function() SpShutDownFn; +alias NTSTATUS function(PSecPkgInfoW) SpGetInfoFn; +alias NTSTATUS function(SECURITY_LOGON_TYPE, + PUNICODE_STRING, PSECPKG_PRIMARY_CRED, PSECPKG_SUPPLEMENTAL_CRED) SpAcceptCredentialsFn; +alias NTSTATUS function(PUNICODE_STRING, ULONG, + PLUID, PVOID, PVOID, PVOID, PLSA_SEC_HANDLE, PTimeStamp) SpAcquireCredentialsHandleFn; +alias NTSTATUS function(LSA_SEC_HANDLE, ULONG, PVOID) SpQueryCredentialsAttributesFn; +alias NTSTATUS function(LSA_SEC_HANDLE) SpFreeCredentialsHandleFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PSecBuffer) SpSaveCredentialsFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PSecBuffer) SpGetCredentialsFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PSecBuffer) SpDeleteCredentialsFn; +alias NTSTATUS function(LSA_SEC_HANDLE, LSA_SEC_HANDLE, + PUNICODE_STRING, ULONG, ULONG, PSecBufferDesc, PLSA_SEC_HANDLE, PSecBufferDesc, + PULONG, PTimeStamp, PBOOLEAN, PSecBuffer) SpInitLsaModeContextFn; +alias NTSTATUS function(LSA_SEC_HANDLE, + LSA_SEC_HANDLE, PSecBufferDesc, ULONG, ULONG, PLSA_SEC_HANDLE, PSecBufferDesc, + PULONG, PTimeStamp, PBOOLEAN, PSecBuffer) SpAcceptLsaModeContextFn; +alias NTSTATUS function(LSA_SEC_HANDLE) SpDeleteContextFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PSecBufferDesc) SpApplyControlTokenFn; +alias NTSTATUS function(PLUID, ULONG, PSecurityUserData *) SpGetUserInfoFn; +alias NTSTATUS function(SECPKG_EXTENDED_INFORMATION_CLASS, PSECPKG_EXTENDED_INFORMATION *) SpGetExtendedInformationFn; +alias NTSTATUS function(LSA_SEC_HANDLE, ULONG, PVOID) SpQueryContextAttributesFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PUNICODE_STRING, + PUNICODE_STRING, ULONG, PVOID, PVOID, PVOID, PTimeStamp) SpAddCredentialsFn; +alias NTSTATUS function( + SECPKG_EXTENDED_INFORMATION_CLASS, PSECPKG_EXTENDED_INFORMATION) SpSetExtendedInformationFn; +alias NTSTATUS function(ULONG, PSECPKG_DLL_FUNCTIONS, + PVOID *) SpInstanceInitFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PSecBuffer) SpInitUserModeContextFn; +alias NTSTATUS function(LSA_SEC_HANDLE, ULONG, + PSecBufferDesc, ULONG) SpMakeSignatureFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PSecBufferDesc, + ULONG, PULONG) SpVerifySignatureFn; +alias NTSTATUS function(LSA_SEC_HANDLE, ULONG, PSecBufferDesc, + ULONG) SpSealMessageFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PSecBufferDesc, + ULONG, PULONG) SpUnsealMessageFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PHANDLE) SpGetContextTokenFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PSecBufferDesc) SpCompleteAuthTokenFn; +alias NTSTATUS function(PSecBuffer, PSecBuffer) SpFormatCredentialsFn; +alias NTSTATUS function(ULONG, PUCHAR, PULONG, + PVOID *) SpMarshallSupplementalCredsFn; +alias NTSTATUS function(LSA_SEC_HANDLE, ULONG, + PSecBuffer, PHANDLE) SpExportSecurityContextFn; +alias NTSTATUS function(PSecBuffer, HANDLE, + PLSA_SEC_HANDLE) SpImportSecurityContextFn; + +/* Dispatch tables of functions implemented by SSP/AP */ +struct SECPKG_FUNCTION_TABLE { + PLSA_AP_INITIALIZE_PACKAGE InitializePackage; + PLSA_AP_LOGON_USER LogonUser; + PLSA_AP_CALL_PACKAGE CallPackage; + PLSA_AP_LOGON_TERMINATED LogonTerminated; + PLSA_AP_CALL_PACKAGE_UNTRUSTED CallPackageUntrusted; + PLSA_AP_CALL_PACKAGE_PASSTHROUGH CallPackagePassthrough; + PLSA_AP_LOGON_USER_EX LogonUserEx; + PLSA_AP_LOGON_USER_EX2 LogonUserEx2; + SpInitializeFn *Initialize; + SpShutDownFn *Shutdown; + SpGetInfoFn *GetInfo; + SpAcceptCredentialsFn *AcceptCredentials; + SpAcquireCredentialsHandleFn *AcquireCredentialsHandle; + SpQueryCredentialsAttributesFn *QueryCredentialsAttributes; + SpFreeCredentialsHandleFn *FreeCredentialsHandle; + SpSaveCredentialsFn *SaveCredentials; + SpGetCredentialsFn *GetCredentials; + SpDeleteCredentialsFn *DeleteCredentials; + SpInitLsaModeContextFn *InitLsaModeContext; + SpAcceptLsaModeContextFn *AcceptLsaModeContext; + SpDeleteContextFn *DeleteContext; + SpApplyControlTokenFn *ApplyControlToken; + SpGetUserInfoFn *GetUserInfo; + SpGetExtendedInformationFn *GetExtendedInformation; + SpQueryContextAttributesFn *QueryContextAttributes; + SpAddCredentialsFn *AddCredentials; + SpSetExtendedInformationFn *SetExtendedInformation; +} +alias SECPKG_FUNCTION_TABLE* PSECPKG_FUNCTION_TABLE; + +struct SECPKG_USER_FUNCTION_TABLE { + SpInstanceInitFn *InstanceInit; + SpInitUserModeContextFn *InitUserModeContext; + SpMakeSignatureFn *MakeSignature; + SpVerifySignatureFn *VerifySignature; + SpSealMessageFn *SealMessage; + SpUnsealMessageFn *UnsealMessage; + SpGetContextTokenFn *GetContextToken; + SpQueryContextAttributesFn *QueryContextAttributes; + SpCompleteAuthTokenFn *CompleteAuthToken; + SpDeleteContextFn *DeleteUserModeContext; + SpFormatCredentialsFn *FormatCredentials; + SpMarshallSupplementalCredsFn *MarshallSupplementalCreds; + SpExportSecurityContextFn *ExportContext; + SpImportSecurityContextFn *ImportContext; +} +alias SECPKG_USER_FUNCTION_TABLE* PSECPKG_USER_FUNCTION_TABLE; + +/* Entry points to SSP/AP */ +alias NTSTATUS function(ULONG, PULONG, + PSECPKG_FUNCTION_TABLE *, PULONG) SpLsaModeInitializeFn; +alias NTSTATUS function(ULONG, PULONG, + PSECPKG_USER_FUNCTION_TABLE *, PULONG) SpUserModeInitializeFn; diff --git a/src/urt/internal/sys/windows/package.d b/src/urt/internal/sys/windows/package.d new file mode 100644 index 0000000..a30b8fa --- /dev/null +++ b/src/urt/internal/sys/windows/package.d @@ -0,0 +1,13 @@ +/// Slim umbrella — re-exports only the vendored Windows modules. +module urt.internal.sys.windows; + +public import urt.internal.sys.windows.w32api; +public import urt.internal.sys.windows.basetsd; +public import urt.internal.sys.windows.basetyps; +public import urt.internal.sys.windows.windef; +public import urt.internal.sys.windows.winnt; +public import urt.internal.sys.windows.winbase; +public import urt.internal.sys.windows.wincon; +public import urt.internal.sys.windows.winerror; +public import urt.internal.sys.windows.winsock2; +public import urt.internal.sys.windows.winuser; diff --git a/src/urt/internal/sys/windows/schannel.d b/src/urt/internal/sys/windows/schannel.d new file mode 100644 index 0000000..10a83e9 --- /dev/null +++ b/src/urt/internal/sys/windows/schannel.d @@ -0,0 +1,106 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * Authors: Stewart Gordon + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_schannel.d) + */ +module urt.internal.sys.windows.schannel; +version (Windows): + +import urt.internal.sys.windows.wincrypt; +import urt.internal.sys.windows.windef; + +enum DWORD SCHANNEL_CRED_VERSION = 4; +enum SCHANNEL_SHUTDOWN = 1; +/* Comment from MinGW + ? Do these belong here or in wincrypt.h + */ +enum : DWORD { + AUTHTYPE_CLIENT = 1, + AUTHTYPE_SERVER = 2 +} + +enum DWORD + SP_PROT_PCT1_SERVER = 0x01, + SP_PROT_PCT1_CLIENT = 0x02, + SP_PROT_SSL2_SERVER = 0x04, + SP_PROT_SSL2_CLIENT = 0x08, + SP_PROT_SSL3_SERVER = 0x10, + SP_PROT_SSL3_CLIENT = 0x20, + SP_PROT_TLS1_SERVER = 0x40, + SP_PROT_TLS1_CLIENT = 0x80, + SP_PROT_PCT1 = SP_PROT_PCT1_CLIENT | SP_PROT_PCT1_SERVER, + SP_PROT_TLS1 = SP_PROT_TLS1_CLIENT | SP_PROT_TLS1_SERVER, + SP_PROT_SSL2 = SP_PROT_SSL2_CLIENT | SP_PROT_SSL2_SERVER, + SP_PROT_SSL3 = SP_PROT_SSL3_CLIENT | SP_PROT_SSL3_SERVER; + +enum DWORD + SCH_CRED_NO_SYSTEM_MAPPER = 0x0002, + SCH_CRED_NO_SERVERNAME_CHECK = 0x0004, + SCH_CRED_MANUAL_CRED_VALIDATION = 0x0008, + SCH_CRED_NO_DEFAULT_CREDS = 0x0010, + SCH_CRED_AUTO_CRED_VALIDATION = 0x0020, + SCH_CRED_USE_DEFAULT_CREDS = 0x0040, + SCH_CRED_REVOCATION_CHECK_END_CERT = 0x0100, + SCH_CRED_REVOCATION_CHECK_CHAIN = 0x0200, + SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT = 0x0400, + SCH_CRED_IGNORE_NO_REVOCATION_CHECK = 0x0800, + SCH_CRED_IGNORE_REVOCATION_OFFLINE = 0x1000; + +// No definition - presumably an opaque structure +struct _HMAPPER; + +struct SCHANNEL_CRED { + DWORD dwVersion = SCHANNEL_CRED_VERSION; + DWORD cCreds; + PCCERT_CONTEXT* paCred; + HCERTSTORE hRootStore; + DWORD cMappers; + _HMAPPER** aphMappers; + DWORD cSupportedAlgs; + ALG_ID* palgSupportedAlgs; + DWORD grbitEnabledProtocols; + DWORD dwMinimumCypherStrength; + DWORD dwMaximumCypherStrength; + DWORD dwSessionLifespan; + DWORD dwFlags; + DWORD reserved; +} +alias SCHANNEL_CRED* PSCHANNEL_CRED; + +struct SecPkgCred_SupportedAlgs { + DWORD cSupportedAlgs; + ALG_ID* palgSupportedAlgs; +} +alias SecPkgCred_SupportedAlgs* PSecPkgCred_SupportedAlgs; + +struct SecPkgCred_CypherStrengths { + DWORD dwMinimumCypherStrength; + DWORD dwMaximumCypherStrength; +} +alias SecPkgCred_CypherStrengths* PSecPkgCred_CypherStrengths; + +struct SecPkgCred_SupportedProtocols { + DWORD grbitProtocol; +} +alias SecPkgCred_SupportedProtocols* PSecPkgCred_SupportedProtocols; + +struct SecPkgContext_IssuerListInfoEx { + PCERT_NAME_BLOB aIssuers; + DWORD cIssuers; +} +alias SecPkgContext_IssuerListInfoEx* PSecPkgContext_IssuerListInfoEx; + +struct SecPkgContext_ConnectionInfo { + DWORD dwProtocol; + ALG_ID aiCipher; + DWORD dwCipherStrength; + ALG_ID aiHash; + DWORD dwHashStrength; + ALG_ID aiExch; + DWORD dwExchStrength; +} +alias SecPkgContext_ConnectionInfo* PSecPkgContext_ConnectionInfo; diff --git a/src/urt/internal/sys/windows/sdkddkver.d b/src/urt/internal/sys/windows/sdkddkver.d new file mode 100644 index 0000000..7cb696a --- /dev/null +++ b/src/urt/internal/sys/windows/sdkddkver.d @@ -0,0 +1,155 @@ +/** + * Windows API header module + * + * Translated from Windows SDK API + * + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/sdkddkver.d) + */ +module urt.internal.sys.windows.sdkddkver; +version (Windows): + +import urt.internal.sys.windows.w32api; + +enum _WIN32_WINNT_NT4 = 0x0400; +enum _WIN32_WINNT_WIN2K = 0x0500; +enum _WIN32_WINNT_WINXP = 0x0501; +enum _WIN32_WINNT_WS03 = 0x0502; +enum _WIN32_WINNT_WIN6 = 0x0600; +enum _WIN32_WINNT_VISTA = 0x0600; +enum _WIN32_WINNT_WS08 = 0x0600; +enum _WIN32_WINNT_LONGHORN = 0x0600; +enum _WIN32_WINNT_WIN7 = 0x0601; +enum _WIN32_WINNT_WIN8 = 0x0602; +enum _WIN32_WINNT_WINBLUE = 0x0603; +enum _WIN32_WINNT_WINTHRESHOLD = 0x0A00; +enum _WIN32_WINNT_WIN10 = 0x0A00; + +enum _WIN32_IE_IE20 = 0x0200; +enum _WIN32_IE_IE30 = 0x0300; +enum _WIN32_IE_IE302 = 0x0302; +enum _WIN32_IE_IE40 = 0x0400; +enum _WIN32_IE_IE401 = 0x0401; +enum _WIN32_IE_IE50 = 0x0500; +enum _WIN32_IE_IE501 = 0x0501; +enum _WIN32_IE_IE55 = 0x0550; +enum _WIN32_IE_IE60 = 0x0600; +enum _WIN32_IE_IE60SP1 = 0x0601; +enum _WIN32_IE_IE60SP2 = 0x0603; +enum _WIN32_IE_IE70 = 0x0700; +enum _WIN32_IE_IE80 = 0x0800; +enum _WIN32_IE_IE90 = 0x0900; +enum _WIN32_IE_IE100 = 0x0A00; +enum _WIN32_IE_IE110 = 0x0A00; + +enum _WIN32_IE_NT4 = _WIN32_IE_IE20; +enum _WIN32_IE_NT4SP1 = _WIN32_IE_IE20; +enum _WIN32_IE_NT4SP2 = _WIN32_IE_IE20; +enum _WIN32_IE_NT4SP3 = _WIN32_IE_IE302; +enum _WIN32_IE_NT4SP4 = _WIN32_IE_IE401; +enum _WIN32_IE_NT4SP5 = _WIN32_IE_IE401; +enum _WIN32_IE_NT4SP6 = _WIN32_IE_IE50; +enum _WIN32_IE_WIN98 = _WIN32_IE_IE401; +enum _WIN32_IE_WIN98SE = _WIN32_IE_IE50; +enum _WIN32_IE_WINME = _WIN32_IE_IE55; +enum _WIN32_IE_WIN2K = _WIN32_IE_IE501; +enum _WIN32_IE_WIN2KSP1 = _WIN32_IE_IE501; +enum _WIN32_IE_WIN2KSP2 = _WIN32_IE_IE501; +enum _WIN32_IE_WIN2KSP3 = _WIN32_IE_IE501; +enum _WIN32_IE_WIN2KSP4 = _WIN32_IE_IE501; +enum _WIN32_IE_XP = _WIN32_IE_IE60; +enum _WIN32_IE_XPSP1 = _WIN32_IE_IE60SP1; +enum _WIN32_IE_XPSP2 = _WIN32_IE_IE60SP2; +enum _WIN32_IE_WS03 = 0x0602; +enum _WIN32_IE_WS03SP1 = _WIN32_IE_IE60SP2; +enum _WIN32_IE_WIN6 = _WIN32_IE_IE70; +enum _WIN32_IE_LONGHORN = _WIN32_IE_IE70; +enum _WIN32_IE_WIN7 = _WIN32_IE_IE80; +enum _WIN32_IE_WIN8 = _WIN32_IE_IE100; +enum _WIN32_IE_WINBLUE = _WIN32_IE_IE100; +enum _WIN32_IE_WINTHRESHOLD = _WIN32_IE_IE110; +enum _WIN32_IE_WIN10 = _WIN32_IE_IE110; + + +enum NTDDI_WIN2K = 0x05000000; +enum NTDDI_WIN2KSP1 = 0x05000100; +enum NTDDI_WIN2KSP2 = 0x05000200; +enum NTDDI_WIN2KSP3 = 0x05000300; +enum NTDDI_WIN2KSP4 = 0x05000400; + +enum NTDDI_WINXP = 0x05010000; +enum NTDDI_WINXPSP1 = 0x05010100; +enum NTDDI_WINXPSP2 = 0x05010200; +enum NTDDI_WINXPSP3 = 0x05010300; +enum NTDDI_WINXPSP4 = 0x05010400; + +enum NTDDI_WS03 = 0x05020000; +enum NTDDI_WS03SP1 = 0x05020100; +enum NTDDI_WS03SP2 = 0x05020200; +enum NTDDI_WS03SP3 = 0x05020300; +enum NTDDI_WS03SP4 = 0x05020400; + +enum NTDDI_WIN6 = 0x06000000; +enum NTDDI_WIN6SP1 = 0x06000100; +enum NTDDI_WIN6SP2 = 0x06000200; +enum NTDDI_WIN6SP3 = 0x06000300; +enum NTDDI_WIN6SP4 = 0x06000400; + +enum NTDDI_VISTA = NTDDI_WIN6; +enum NTDDI_VISTASP1 = NTDDI_WIN6SP1; +enum NTDDI_VISTASP2 = NTDDI_WIN6SP2; +enum NTDDI_VISTASP3 = NTDDI_WIN6SP3; +enum NTDDI_VISTASP4 = NTDDI_WIN6SP4; + +enum NTDDI_LONGHORN = NTDDI_VISTA; + +enum NTDDI_WS08 = NTDDI_WIN6SP1; +enum NTDDI_WS08SP2 = NTDDI_WIN6SP2; +enum NTDDI_WS08SP3 = NTDDI_WIN6SP3; +enum NTDDI_WS08SP4 = NTDDI_WIN6SP4; + +enum NTDDI_WIN7 = 0x06010000; +enum NTDDI_WIN8 = 0x06020000; +enum NTDDI_WINBLUE = 0x06030000; +enum NTDDI_WINTHRESHOLD = 0x0A000000; +enum NTDDI_WIN10 = 0x0A000000; +enum NTDDI_WIN10_TH2 = 0x0A000001; +enum NTDDI_WIN10_RS1 = 0x0A000002; +enum NTDDI_WIN10_RS2 = 0x0A000003; +enum NTDDI_WIN10_RS3 = 0x0A000004; +enum NTDDI_WIN10_RS4 = 0x0A000005; +enum NTDDI_WIN10_RS5 = 0x0A000006; +enum NTDDI_WIN10_19H1 = 0x0A000007; +enum NTDDI_WIN10_VB = 0x0A000008; +enum NTDDI_WIN10_MN = 0x0A000009; +enum NTDDI_WIN10_FE = 0x0A00000A; +enum NTDDI_WIN10_CO = 0x0A00000B; +enum NTDDI_WIN10_NI = 0x0A00000C; +enum NTDDI_WIN10_CU = 0x0A00000D; +enum NTDDI_WIN11_ZN = 0x0A00000E; +enum NTDDI_WIN11_GA = 0x0A00000F; +enum NTDDI_WIN11_GE = 0x0A000010; + +enum WDK_NTDDI_VERSION = NTDDI_WIN11_GE; + +enum OSVERSION_MASK = 0xFFFF0000U; +enum SPVERSION_MASK = 0x0000FF00; +enum SUBVERSION_MASK = 0x000000FF; + +pragma(inline, true) nothrow @nogc pure @safe { + uint OSVER(uint Version) => Version & OSVERSION_MASK; + uint SPVER(uint Version) => (Version & SPVERSION_MASK) >> 8; + uint SUBVER(uint Version) => Version & SUBVERSION_MASK; + + uint NTDDI_VERSION_FROM_WIN32_WINNT2(uint Version) => Version * 0x10000; + alias NTDDI_VERSION_FROM_WIN32_WINNT = NTDDI_VERSION_FROM_WIN32_WINNT2; +} + + +static if (_WIN32_WINNT < _WIN32_WINNT_WIN10) { + enum NTDDI_VERSION = NTDDI_VERSION_FROM_WIN32_WINNT(_WIN32_WINNT); +} else { + enum NTDDI_VERSION = WDK_NTDDI_VERSION; +} + +enum WINVER = _WIN32_WINNT; diff --git a/src/urt/internal/sys/windows/security.d b/src/urt/internal/sys/windows/security.d new file mode 100644 index 0000000..5efa509 --- /dev/null +++ b/src/urt/internal/sys/windows/security.d @@ -0,0 +1,119 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * Authors: Ellery Newcomer, John Colvin + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_security.d) + */ +module urt.internal.sys.windows.security; +version (Windows): + +enum : SECURITY_STATUS +{ + SEC_E_OK = 0x00000000, + SEC_E_INSUFFICIENT_MEMORY = 0x80090300, + SEC_E_INVALID_HANDLE = 0x80090301, + SEC_E_UNSUPPORTED_FUNCTION = 0x80090302, + SEC_E_TARGET_UNKNOWN = 0x80090303, + SEC_E_INTERNAL_ERROR = 0x80090304, + SEC_E_SECPKG_NOT_FOUND = 0x80090305, + SEC_E_NOT_OWNER = 0x80090306, + SEC_E_CANNOT_INSTALL = 0x80090307, + SEC_E_INVALID_TOKEN = 0x80090308, + SEC_E_CANNOT_PACK = 0x80090309, + SEC_E_QOP_NOT_SUPPORTED = 0x8009030A, + SEC_E_NO_IMPERSONATION = 0x8009030B, + SEC_E_LOGON_DENIED = 0x8009030C, + SEC_E_UNKNOWN_CREDENTIALS = 0x8009030D, + SEC_E_NO_CREDENTIALS = 0x8009030E, + SEC_E_MESSAGE_ALTERED = 0x8009030F, + SEC_E_OUT_OF_SEQUENCE = 0x80090310, + SEC_E_NO_AUTHENTICATING_AUTHORITY = 0x80090311, + SEC_E_BAD_PKGID = 0x80090316, + SEC_E_CONTEXT_EXPIRED = 0x80090317, + SEC_E_INCOMPLETE_MESSAGE = 0x80090318, + SEC_E_INCOMPLETE_CREDENTIALS = 0x80090320, + SEC_E_BUFFER_TOO_SMALL = 0x80090321, + SEC_E_WRONG_PRINCIPAL = 0x80090322, + SEC_E_TIME_SKEW = 0x80090324, + SEC_E_UNTRUSTED_ROOT = 0x80090325, + SEC_E_ILLEGAL_MESSAGE = 0x80090326, + SEC_E_CERT_UNKNOWN = 0x80090327, + SEC_E_CERT_EXPIRED = 0x80090328, + SEC_E_ENCRYPT_FAILURE = 0x80090329, + SEC_E_DECRYPT_FAILURE = 0x80090330, + SEC_E_ALGORITHM_MISMATCH = 0x80090331, + SEC_E_SECURITY_QOS_FAILED = 0x80090332, + SEC_E_UNFINISHED_CONTEXT_DELETED = 0x80090333, + SEC_E_NO_TGT_REPLY = 0x80090334, + SEC_E_NO_IP_ADDRESSES = 0x80090335, + SEC_E_WRONG_CREDENTIAL_HANDLE = 0x80090336, + SEC_E_CRYPTO_SYSTEM_INVALID = 0x80090337, + SEC_E_MAX_REFERRALS_EXCEEDED = 0x80090338, + SEC_E_MUST_BE_KDC = 0x80090339, + SEC_E_STRONG_CRYPTO_NOT_SUPPORTED = 0x8009033A, + SEC_E_TOO_MANY_PRINCIPALS = 0x8009033B, + SEC_E_NO_PA_DATA = 0x8009033C, + SEC_E_PKINIT_NAME_MISMATCH = 0x8009033D, + SEC_E_SMARTCARD_LOGON_REQUIRED = 0x8009033E, + SEC_E_SHUTDOWN_IN_PROGRESS = 0x8009033F, + SEC_E_KDC_INVALID_REQUEST = 0x80090340, + SEC_E_KDC_UNABLE_TO_REFER = 0x80090341, + SEC_E_KDC_UNKNOWN_ETYPE = 0x80090342, + SEC_E_UNSUPPORTED_PREAUTH = 0x80090343, + SEC_E_DELEGATION_REQUIRED = 0x80090345, + SEC_E_BAD_BINDINGS = 0x80090346, + SEC_E_MULTIPLE_ACCOUNTS = 0x80090347, + SEC_E_NO_KERB_KEY = 0x80090348, + SEC_E_CERT_WRONG_USAGE = 0x80090349, + SEC_E_DOWNGRADE_DETECTED = 0x80090350, + SEC_E_SMARTCARD_CERT_REVOKED = 0x80090351, + SEC_E_ISSUING_CA_UNTRUSTED = 0x80090352, + SEC_E_REVOCATION_OFFLINE_C = 0x80090353, + SEC_E_PKINIT_CLIENT_FAILURE = 0x80090354, + SEC_E_SMARTCARD_CERT_EXPIRED = 0x80090355, + SEC_E_NO_S4U_PROT_SUPPORT = 0x80090356, + SEC_E_CROSSREALM_DELEGATION_FAILURE = 0x80090357, + SEC_E_REVOCATION_OFFLINE_KDC = 0x80090358, + SEC_E_ISSUING_CA_UNTRUSTED_KDC = 0x80090359, + SEC_E_KDC_CERT_EXPIRED = 0x8009035A, + SEC_E_KDC_CERT_REVOKED = 0x8009035B, + SEC_E_INVALID_PARAMETER = 0x8009035D, + SEC_E_DELEGATION_POLICY = 0x8009035E, + SEC_E_POLICY_NLTM_ONLY = 0x8009035F, + SEC_E_NO_CONTEXT = 0x80090361, + SEC_E_PKU2U_CERT_FAILURE = 0x80090362, + SEC_E_MUTUAL_AUTH_FAILED = 0x80090363, + SEC_E_ONLY_HTTPS_ALLOWED = 0x80090365, + SEC_E_APPLICATION_PROTOCOL_MISMATCH = 0x80090367, + SEC_E_INVALID_UPN_NAME = 0x80090369, + SEC_E_EXT_BUFFER_TOO_SMALL = 0x8009036A, + SEC_E_INSUFFICIENT_BUFFERS = 0x8009036B, + SEC_E_NO_SPM = SEC_E_INTERNAL_ERROR, + SEC_E_NOT_SUPPORTED = SEC_E_UNSUPPORTED_FUNCTION +} +enum : SECURITY_STATUS +{ + SEC_I_CONTINUE_NEEDED = 0x00090312, + SEC_I_COMPLETE_NEEDED = 0x00090313, + SEC_I_COMPLETE_AND_CONTINUE = 0x00090314, + SEC_I_LOCAL_LOGON = 0x00090315, + SEC_I_GENERIC_EXTENSION_RECEIVED = 0x00090316, + SEC_I_CONTEXT_EXPIRED = 0x00090317, + SEC_I_INCOMPLETE_CREDENTIALS = 0x00090320, + SEC_I_RENEGOTIATE = 0x00090321, + SEC_I_NO_LSA_CONTEXT = 0x00090323, + SEC_I_SIGNATURE_NEEDED = 0x0009035C, + SEC_I_NO_RENEGOTIATION = 0x00090360, + SEC_I_MESSAGE_FRAGMENT = 0x00090364, + SEC_I_CONTINUE_NEEDED_MESSAGE_OK = 0x00090366, + SEC_I_ASYNC_CALL_PENDING = 0x00090368, +} + +/* always a char */ +alias SEC_CHAR = char; +alias SEC_WCHAR = wchar; + +alias SECURITY_STATUS = int; diff --git a/src/urt/internal/sys/windows/sspi.d b/src/urt/internal/sys/windows/sspi.d new file mode 100644 index 0000000..d085c4a --- /dev/null +++ b/src/urt/internal/sys/windows/sspi.d @@ -0,0 +1,381 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * Authors: Ellery Newcomer + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_sspi.d) + */ +module urt.internal.sys.windows.sspi; +version (Windows): + +version (ANSI) {} else version = Unicode; + +import urt.internal.sys.windows.windef; +import urt.internal.sys.windows.ntdef; +import urt.internal.sys.windows.w32api; +import urt.internal.sys.windows.security; +import urt.internal.sys.windows.ntsecapi; +import urt.internal.sys.windows.subauth; + +enum :ULONG{ + SECPKG_CRED_INBOUND = 1, + SECPKG_CRED_OUTBOUND = 2, + SECPKG_CRED_BOTH = (SECPKG_CRED_OUTBOUND|SECPKG_CRED_INBOUND), + SECPKG_CRED_ATTR_NAMES = 1, +} + +enum :ULONG{ + SECPKG_FLAG_INTEGRITY = 1, + SECPKG_FLAG_PRIVACY = 2, + SECPKG_FLAG_TOKEN_ONLY = 4, + SECPKG_FLAG_DATAGRAM = 8, + SECPKG_FLAG_CONNECTION = 16, + SECPKG_FLAG_MULTI_REQUIRED = 32, + SECPKG_FLAG_CLIENT_ONLY = 64, + SECPKG_FLAG_EXTENDED_ERROR = 128, + SECPKG_FLAG_IMPERSONATION = 256, + SECPKG_FLAG_ACCEPT_WIN32_NAME = 512, + SECPKG_FLAG_STREAM = 1024, +} + +enum :ULONG{ + SECPKG_ATTR_AUTHORITY = 6, + SECPKG_ATTR_CONNECTION_INFO = 90, + SECPKG_ATTR_ISSUER_LIST = 80, + SECPKG_ATTR_ISSUER_LIST_EX = 89, + SECPKG_ATTR_KEY_INFO = 5, + SECPKG_ATTR_LIFESPAN = 2, + SECPKG_ATTR_LOCAL_CERT_CONTEXT = 84, + SECPKG_ATTR_LOCAL_CRED = 82, + SECPKG_ATTR_NAMES = 1, + SECPKG_ATTR_PROTO_INFO = 7, + SECPKG_ATTR_REMOTE_CERT_CONTEXT = 83, + SECPKG_ATTR_REMOTE_CRED = 81, + SECPKG_ATTR_SIZES = 0, + SECPKG_ATTR_STREAM_SIZES = 4, +} + +enum :ULONG{ + SECBUFFER_EMPTY = 0, + SECBUFFER_DATA = 1, + SECBUFFER_TOKEN = 2, + SECBUFFER_PKG_PARAMS = 3, + SECBUFFER_MISSING = 4, + SECBUFFER_EXTRA = 5, + SECBUFFER_STREAM_TRAILER = 6, + SECBUFFER_STREAM_HEADER = 7, + SECBUFFER_PADDING = 9, + SECBUFFER_STREAM = 10, + SECBUFFER_READONLY = 0x80000000, + SECBUFFER_ATTRMASK = 0xf0000000, +} + +enum UNISP_NAME_A = "Microsoft Unified Security Protocol Provider"; +enum UNISP_NAME_W = "Microsoft Unified Security Protocol Provider"w; +enum SECBUFFER_VERSION = 0; + +alias UNICODE_STRING SECURITY_STRING; +alias UNICODE_STRING* PSECURITY_STRING; + +extern(Windows): + +struct SecHandle { + ULONG_PTR dwLower; + ULONG_PTR dwUpper; +} +alias SecHandle* PSecHandle; +struct SecBuffer { + ULONG cbBuffer; + ULONG BufferType; + PVOID pvBuffer; +} +alias SecBuffer* PSecBuffer; +alias SecHandle CredHandle; +alias PSecHandle PCredHandle; +alias SecHandle CtxtHandle; +alias PSecHandle PCtxtHandle; +struct SECURITY_INTEGER { + uint LowPart; + int HighPart; +} +alias SECURITY_INTEGER TimeStamp; +alias SECURITY_INTEGER* PTimeStamp; +struct SecBufferDesc { + ULONG ulVersion; + ULONG cBuffers; + PSecBuffer pBuffers; +} +alias SecBufferDesc* PSecBufferDesc; +struct SecPkgContext_StreamSizes { + ULONG cbHeader; + ULONG cbTrailer; + ULONG cbMaximumMessage; + ULONG cBuffers; + ULONG cbBlockSize; +} +alias SecPkgContext_StreamSizes* PSecPkgContext_StreamSizes; +struct SecPkgContext_Sizes { + ULONG cbMaxToken; + ULONG cbMaxSignature; + ULONG cbBlockSize; + ULONG cbSecurityTrailer; +} +alias SecPkgContext_Sizes* PSecPkgContext_Sizes; +struct SecPkgContext_AuthorityW { + SEC_WCHAR* sAuthorityName; +} +alias SecPkgContext_AuthorityW* PSecPkgContext_AuthorityW; +struct SecPkgContext_AuthorityA { + SEC_CHAR* sAuthorityName; +} +alias SecPkgContext_AuthorityA* PSecPkgContext_AuthorityA; +struct SecPkgContext_KeyInfoW { + SEC_WCHAR* sSignatureAlgorithmName; + SEC_WCHAR* sEncryptAlgorithmName; + ULONG KeySize; + ULONG SignatureAlgorithm; + ULONG EncryptAlgorithm; +} +alias SecPkgContext_KeyInfoW* PSecPkgContext_KeyInfoW; +struct SecPkgContext_KeyInfoA { + SEC_CHAR* sSignatureAlgorithmName; + SEC_CHAR* sEncryptAlgorithmName; + ULONG KeySize; + ULONG SignatureAlgorithm; + ULONG EncryptAlgorithm; +} +alias SecPkgContext_KeyInfoA* PSecPkgContext_KeyInfoA; +struct SecPkgContext_LifeSpan { + TimeStamp tsStart; + TimeStamp tsExpiry; +} +alias SecPkgContext_LifeSpan* PSecPkgContext_LifeSpan; +struct SecPkgContext_NamesW { + SEC_WCHAR* sUserName; +} +alias SecPkgContext_NamesW* PSecPkgContext_NamesW; +struct SecPkgContext_NamesA { + SEC_CHAR* sUserName; +} +alias SecPkgContext_NamesA* PSecPkgContext_NamesA; +struct SecPkgInfoW { + ULONG fCapabilities; + USHORT wVersion; + USHORT wRPCID; + ULONG cbMaxToken; + SEC_WCHAR* Name; + SEC_WCHAR* Comment; +} +alias SecPkgInfoW* PSecPkgInfoW; +struct SecPkgInfoA { + ULONG fCapabilities; + USHORT wVersion; + USHORT wRPCID; + ULONG cbMaxToken; + SEC_CHAR* Name; + SEC_CHAR* Comment; +} +alias SecPkgInfoA* PSecPkgInfoA; +/* supported only in win2k+, so it should be a PSecPkgInfoW */ +/* PSDK does not say it has ANSI/Unicode versions */ +struct SecPkgContext_PackageInfo { + PSecPkgInfoW PackageInfo; +} +alias SecPkgContext_PackageInfo* PSecPkgContext_PackageInfo; +struct SecPkgCredentials_NamesW { + SEC_WCHAR* sUserName; +} +alias SecPkgCredentials_NamesW* PSecPkgCredentials_NamesW; +struct SecPkgCredentials_NamesA { + SEC_CHAR* sUserName; +} +alias SecPkgCredentials_NamesA* PSecPkgCredentials_NamesA; + +/* TODO: missing type in SDK */ +alias void function() SEC_GET_KEY_FN; + +alias SECURITY_STATUS function(PULONG,PSecPkgInfoW*) ENUMERATE_SECURITY_PACKAGES_FN_W; +alias SECURITY_STATUS function(PULONG,PSecPkgInfoA*) ENUMERATE_SECURITY_PACKAGES_FN_A; +alias SECURITY_STATUS function(PCredHandle,ULONG,PVOID) QUERY_CREDENTIALS_ATTRIBUTES_FN_W; +alias SECURITY_STATUS function(PCredHandle,ULONG,PVOID) QUERY_CREDENTIALS_ATTRIBUTES_FN_A; +alias SECURITY_STATUS function(SEC_WCHAR*,SEC_WCHAR*,ULONG,PLUID,PVOID,SEC_GET_KEY_FN,PVOID,PCredHandle,PTimeStamp) ACQUIRE_CREDENTIALS_HANDLE_FN_W; +alias SECURITY_STATUS function(SEC_CHAR*,SEC_CHAR*,ULONG,PLUID,PVOID,SEC_GET_KEY_FN,PVOID,PCredHandle,PTimeStamp) ACQUIRE_CREDENTIALS_HANDLE_FN_A; +alias SECURITY_STATUS function(PCredHandle) FREE_CREDENTIALS_HANDLE_FN; +alias SECURITY_STATUS function(PCredHandle,PCtxtHandle,SEC_WCHAR*,ULONG,ULONG,ULONG,PSecBufferDesc,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp) INITIALIZE_SECURITY_CONTEXT_FN_W; +alias SECURITY_STATUS function(PCredHandle,PCtxtHandle,SEC_CHAR*,ULONG,ULONG,ULONG,PSecBufferDesc,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp) INITIALIZE_SECURITY_CONTEXT_FN_A; +alias SECURITY_STATUS function(PCredHandle,PCtxtHandle,PSecBufferDesc,ULONG,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp) ACCEPT_SECURITY_CONTEXT_FN; +alias SECURITY_STATUS function(PCtxtHandle,PSecBufferDesc) COMPLETE_AUTH_TOKEN_FN; +alias SECURITY_STATUS function(PCtxtHandle) DELETE_SECURITY_CONTEXT_FN; +alias SECURITY_STATUS function(PCtxtHandle,PSecBufferDesc) APPLY_CONTROL_TOKEN_FN_W; +alias SECURITY_STATUS function(PCtxtHandle,PSecBufferDesc) APPLY_CONTROL_TOKEN_FN_A; +alias SECURITY_STATUS function(PCtxtHandle,ULONG,PVOID) QUERY_CONTEXT_ATTRIBUTES_FN_A; +alias SECURITY_STATUS function(PCtxtHandle,ULONG,PVOID) QUERY_CONTEXT_ATTRIBUTES_FN_W; +alias SECURITY_STATUS function(PCtxtHandle) IMPERSONATE_SECURITY_CONTEXT_FN; +alias SECURITY_STATUS function(PCtxtHandle) REVERT_SECURITY_CONTEXT_FN; +alias SECURITY_STATUS function(PCtxtHandle,ULONG,PSecBufferDesc,ULONG) MAKE_SIGNATURE_FN; +alias SECURITY_STATUS function(PCtxtHandle,PSecBufferDesc,ULONG,PULONG) VERIFY_SIGNATURE_FN; +alias SECURITY_STATUS function(PVOID) FREE_CONTEXT_BUFFER_FN; +alias SECURITY_STATUS function(SEC_CHAR*,PSecPkgInfoA*) QUERY_SECURITY_PACKAGE_INFO_FN_A; +alias SECURITY_STATUS function(PCtxtHandle,HANDLE*) QUERY_SECURITY_CONTEXT_TOKEN_FN; +alias SECURITY_STATUS function(SEC_WCHAR*,PSecPkgInfoW*) QUERY_SECURITY_PACKAGE_INFO_FN_W; +alias SECURITY_STATUS function(PCtxtHandle,ULONG,PSecBufferDesc,ULONG) ENCRYPT_MESSAGE_FN; +alias SECURITY_STATUS function(PCtxtHandle,PSecBufferDesc,ULONG,PULONG) DECRYPT_MESSAGE_FN; + +/* No, it really is FreeCredentialsHandle, see the thread beginning + * http://sourceforge.net/mailarchive/message.php?msg_id=4321080 for a + * discovery discussion. */ +struct SecurityFunctionTableW{ + uint dwVersion; + ENUMERATE_SECURITY_PACKAGES_FN_W EnumerateSecurityPackagesW; + QUERY_CREDENTIALS_ATTRIBUTES_FN_W QueryCredentialsAttributesW; + ACQUIRE_CREDENTIALS_HANDLE_FN_W AcquireCredentialsHandleW; + FREE_CREDENTIALS_HANDLE_FN FreeCredentialsHandle; + void* Reserved2; + INITIALIZE_SECURITY_CONTEXT_FN_W InitializeSecurityContextW; + ACCEPT_SECURITY_CONTEXT_FN AcceptSecurityContext; + COMPLETE_AUTH_TOKEN_FN CompleteAuthToken; + DELETE_SECURITY_CONTEXT_FN DeleteSecurityContext; + APPLY_CONTROL_TOKEN_FN_W ApplyControlTokenW; + QUERY_CONTEXT_ATTRIBUTES_FN_W QueryContextAttributesW; + IMPERSONATE_SECURITY_CONTEXT_FN ImpersonateSecurityContext; + REVERT_SECURITY_CONTEXT_FN RevertSecurityContext; + MAKE_SIGNATURE_FN MakeSignature; + VERIFY_SIGNATURE_FN VerifySignature; + FREE_CONTEXT_BUFFER_FN FreeContextBuffer; + QUERY_SECURITY_PACKAGE_INFO_FN_W QuerySecurityPackageInfoW; + void* Reserved3; + void* Reserved4; + void* Reserved5; + void* Reserved6; + void* Reserved7; + void* Reserved8; + QUERY_SECURITY_CONTEXT_TOKEN_FN QuerySecurityContextToken; + ENCRYPT_MESSAGE_FN EncryptMessage; + DECRYPT_MESSAGE_FN DecryptMessage; +} +alias SecurityFunctionTableW* PSecurityFunctionTableW; +struct SecurityFunctionTableA{ + uint dwVersion; + ENUMERATE_SECURITY_PACKAGES_FN_A EnumerateSecurityPackagesA; + QUERY_CREDENTIALS_ATTRIBUTES_FN_A QueryCredentialsAttributesA; + ACQUIRE_CREDENTIALS_HANDLE_FN_A AcquireCredentialsHandleA; + FREE_CREDENTIALS_HANDLE_FN FreeCredentialsHandle; + void* Reserved2; + INITIALIZE_SECURITY_CONTEXT_FN_A InitializeSecurityContextA; + ACCEPT_SECURITY_CONTEXT_FN AcceptSecurityContext; + COMPLETE_AUTH_TOKEN_FN CompleteAuthToken; + DELETE_SECURITY_CONTEXT_FN DeleteSecurityContext; + APPLY_CONTROL_TOKEN_FN_A ApplyControlTokenA; + QUERY_CONTEXT_ATTRIBUTES_FN_A QueryContextAttributesA; + IMPERSONATE_SECURITY_CONTEXT_FN ImpersonateSecurityContext; + REVERT_SECURITY_CONTEXT_FN RevertSecurityContext; + MAKE_SIGNATURE_FN MakeSignature; + VERIFY_SIGNATURE_FN VerifySignature; + FREE_CONTEXT_BUFFER_FN FreeContextBuffer; + QUERY_SECURITY_PACKAGE_INFO_FN_A QuerySecurityPackageInfoA; + void* Reserved3; + void* Reserved4; + void* Unknown1; + void* Unknown2; + void* Unknown3; + void* Unknown4; + void* Unknown5; + ENCRYPT_MESSAGE_FN EncryptMessage; + DECRYPT_MESSAGE_FN DecryptMessage; +} +alias SecurityFunctionTableA* PSecurityFunctionTableA; +alias PSecurityFunctionTableA function() INIT_SECURITY_INTERFACE_A; +alias PSecurityFunctionTableW function() INIT_SECURITY_INTERFACE_W; + +SECURITY_STATUS FreeCredentialsHandle(PCredHandle); +SECURITY_STATUS EnumerateSecurityPackagesA(PULONG,PSecPkgInfoA*); +SECURITY_STATUS EnumerateSecurityPackagesW(PULONG,PSecPkgInfoW*); +SECURITY_STATUS AcquireCredentialsHandleA(SEC_CHAR*,SEC_CHAR*,ULONG,PLUID,PVOID,SEC_GET_KEY_FN,PVOID,PCredHandle,PTimeStamp); +SECURITY_STATUS AcquireCredentialsHandleW(SEC_WCHAR*,SEC_WCHAR*,ULONG,PLUID,PVOID,SEC_GET_KEY_FN,PVOID,PCredHandle,PTimeStamp); +SECURITY_STATUS AcceptSecurityContext(PCredHandle,PCtxtHandle,PSecBufferDesc,ULONG,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp); +SECURITY_STATUS InitializeSecurityContextA(PCredHandle,PCtxtHandle,SEC_CHAR*,ULONG,ULONG,ULONG,PSecBufferDesc,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp); +SECURITY_STATUS InitializeSecurityContextW(PCredHandle,PCtxtHandle,SEC_WCHAR*,ULONG,ULONG,ULONG,PSecBufferDesc,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp); +SECURITY_STATUS FreeContextBuffer(PVOID); +SECURITY_STATUS QueryContextAttributesA(PCtxtHandle,ULONG,PVOID); +SECURITY_STATUS QueryContextAttributesW(PCtxtHandle,ULONG,PVOID); +SECURITY_STATUS QueryCredentialsAttributesA(PCredHandle,ULONG,PVOID); +SECURITY_STATUS QueryCredentialsAttributesW(PCredHandle,ULONG,PVOID); +static if (_WIN32_WINNT >= 0x500){ + SECURITY_STATUS QuerySecurityContextToken(PCtxtHandle,HANDLE*); +} +SECURITY_STATUS DecryptMessage(PCtxtHandle,PSecBufferDesc,ULONG,PULONG); +SECURITY_STATUS EncryptMessage(PCtxtHandle,ULONG,PSecBufferDesc,ULONG); +SECURITY_STATUS DeleteSecurityContext(PCtxtHandle); +SECURITY_STATUS CompleteAuthToken(PCtxtHandle,PSecBufferDesc); +SECURITY_STATUS ApplyControlTokenA(PCtxtHandle,PSecBufferDesc); +SECURITY_STATUS ApplyControlTokenW(PCtxtHandle,PSecBufferDesc); +SECURITY_STATUS ImpersonateSecurityContext(PCtxtHandle); +SECURITY_STATUS RevertSecurityContext(PCtxtHandle); +SECURITY_STATUS MakeSignature(PCtxtHandle,ULONG,PSecBufferDesc,ULONG); +SECURITY_STATUS VerifySignature(PCtxtHandle,PSecBufferDesc,ULONG,PULONG); +SECURITY_STATUS QuerySecurityPackageInfoA(SEC_CHAR*,PSecPkgInfoA*); +SECURITY_STATUS QuerySecurityPackageInfoW(SEC_WCHAR*,PSecPkgInfoW*); +PSecurityFunctionTableA InitSecurityInterfaceA(); +PSecurityFunctionTableW InitSecurityInterfaceW(); + +version (Unicode) { + alias UNISP_NAME_W UNISP_NAME; + alias SecPkgInfoW SecPkgInfo; + alias PSecPkgInfoW PSecPkgInfo; + alias SecPkgCredentials_NamesW SecPkgCredentials_Names; + alias PSecPkgCredentials_NamesW PSecPkgCredentials_Names; + alias SecPkgContext_AuthorityW SecPkgContext_Authority; + alias PSecPkgContext_AuthorityW PSecPkgContext_Authority; + alias SecPkgContext_KeyInfoW SecPkgContext_KeyInfo; + alias PSecPkgContext_KeyInfoW PSecPkgContext_KeyInfo; + alias SecPkgContext_NamesW SecPkgContext_Names; + alias PSecPkgContext_NamesW PSecPkgContext_Names; + alias SecurityFunctionTableW SecurityFunctionTable; + alias PSecurityFunctionTableW PSecurityFunctionTable; + alias AcquireCredentialsHandleW AcquireCredentialsHandle; + alias EnumerateSecurityPackagesW EnumerateSecurityPackages; + alias InitializeSecurityContextW InitializeSecurityContext; + alias QueryContextAttributesW QueryContextAttributes; + alias QueryCredentialsAttributesW QueryCredentialsAttributes; + alias QuerySecurityPackageInfoW QuerySecurityPackageInfo; + alias ApplyControlTokenW ApplyControlToken; + alias ENUMERATE_SECURITY_PACKAGES_FN_W ENUMERATE_SECURITY_PACKAGES_FN; + alias QUERY_CREDENTIALS_ATTRIBUTES_FN_W QUERY_CREDENTIALS_ATTRIBUTES_FN; + alias ACQUIRE_CREDENTIALS_HANDLE_FN_W ACQUIRE_CREDENTIALS_HANDLE_FN; + alias INITIALIZE_SECURITY_CONTEXT_FN_W INITIALIZE_SECURITY_CONTEXT_FN; + alias APPLY_CONTROL_TOKEN_FN_W APPLY_CONTROL_TOKEN_FN; + alias QUERY_CONTEXT_ATTRIBUTES_FN_W QUERY_CONTEXT_ATTRIBUTES_FN; + alias QUERY_SECURITY_PACKAGE_INFO_FN_W QUERY_SECURITY_PACKAGE_INFO_FN; + alias INIT_SECURITY_INTERFACE_W INIT_SECURITY_INTERFACE; +}else{ + alias UNISP_NAME_A UNISP_NAME; + alias SecPkgInfoA SecPkgInfo; + alias PSecPkgInfoA PSecPkgInfo; + alias SecPkgCredentials_NamesA SecPkgCredentials_Names; + alias PSecPkgCredentials_NamesA PSecPkgCredentials_Names; + alias SecPkgContext_AuthorityA SecPkgContext_Authority; + alias PSecPkgContext_AuthorityA PSecPkgContext_Authority; + alias SecPkgContext_KeyInfoA SecPkgContext_KeyInfo; + alias PSecPkgContext_KeyInfoA PSecPkgContext_KeyInfo; + alias SecPkgContext_NamesA SecPkgContext_Names; + alias PSecPkgContext_NamesA PSecPkgContext_Names; + alias SecurityFunctionTableA SecurityFunctionTable; + alias PSecurityFunctionTableA PSecurityFunctionTable; + alias AcquireCredentialsHandleA AcquireCredentialsHandle; + alias EnumerateSecurityPackagesA EnumerateSecurityPackages; + alias InitializeSecurityContextA InitializeSecurityContext; + alias QueryContextAttributesA QueryContextAttributes; + alias QueryCredentialsAttributesA QueryCredentialsAttributes; + alias QuerySecurityPackageInfoA QuerySecurityPackageInfo; + alias ApplyControlTokenA ApplyControlToken; + alias ENUMERATE_SECURITY_PACKAGES_FN_A ENUMERATE_SECURITY_PACKAGES_FN; + alias QUERY_CREDENTIALS_ATTRIBUTES_FN_A QUERY_CREDENTIALS_ATTRIBUTES_FN; + alias ACQUIRE_CREDENTIALS_HANDLE_FN_A ACQUIRE_CREDENTIALS_HANDLE_FN; + alias INITIALIZE_SECURITY_CONTEXT_FN_A INITIALIZE_SECURITY_CONTEXT_FN; + alias APPLY_CONTROL_TOKEN_FN_A APPLY_CONTROL_TOKEN_FN; + alias QUERY_CONTEXT_ATTRIBUTES_FN_A QUERY_CONTEXT_ATTRIBUTES_FN; + alias QUERY_SECURITY_PACKAGE_INFO_FN_A QUERY_SECURITY_PACKAGE_INFO_FN; + alias INIT_SECURITY_INTERFACE_A INIT_SECURITY_INTERFACE; +} diff --git a/src/urt/internal/sys/windows/subauth.d b/src/urt/internal/sys/windows/subauth.d new file mode 100644 index 0000000..fc43416 --- /dev/null +++ b/src/urt/internal/sys/windows/subauth.d @@ -0,0 +1,275 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_subauth.d) + */ +module urt.internal.sys.windows.subauth; +version (Windows): + +import urt.internal.sys.windows.ntdef, urt.internal.sys.windows.windef; + +/+ +alias LONG NTSTATUS; +alias NTSTATUS* PNTSTATUS; ++/ + +enum : ULONG { + MSV1_0_PASSTHRU = 1, + MSV1_0_GUEST_LOGON = 2 +} + +// USER_ALL_INFORMATION.WhichFields (Undocumented) +enum ULONG + MSV1_0_VALIDATION_LOGOFF_TIME = 1, + MSV1_0_VALIDATION_KICKOFF_TIME = 2, + MSV1_0_VALIDATION_LOGON_SERVER = 4, + MSV1_0_VALIDATION_LOGON_DOMAIN = 8, + MSV1_0_VALIDATION_SESSION_KEY = 16, + MSV1_0_VALIDATION_USER_FLAGS = 32, + MSV1_0_VALIDATION_USER_ID = 64; + +// ?ActionsPerformed? (Undocumented) +enum MSV1_0_SUBAUTH_ACCOUNT_DISABLED = 1; +enum MSV1_0_SUBAUTH_PASSWORD = 2; +enum MSV1_0_SUBAUTH_WORKSTATIONS = 4; +enum MSV1_0_SUBAUTH_LOGON_HOURS = 8; +enum MSV1_0_SUBAUTH_ACCOUNT_EXPIRY = 16; +enum MSV1_0_SUBAUTH_PASSWORD_EXPIRY = 32; +enum MSV1_0_SUBAUTH_ACCOUNT_TYPE = 64; +enum MSV1_0_SUBAUTH_LOCKOUT = 128; + +enum NEXT_FREE_ACCOUNT_CONTROL_BIT = 131072; + +enum SAM_DAYS_PER_WEEK = 7; +enum SAM_HOURS_PER_WEEK = 168; +enum SAM_MINUTES_PER_WEEK = 10080; + +enum : NTSTATUS { + STATUS_SUCCESS = 0, + STATUS_INVALID_INFO_CLASS = 0xC0000003, + STATUS_NO_SUCH_USER = 0xC0000064, + STATUS_WRONG_PASSWORD = 0xC000006A, + STATUS_PASSWORD_RESTRICTION = 0xC000006C, + STATUS_LOGON_FAILURE = 0xC000006D, + STATUS_ACCOUNT_RESTRICTION = 0xC000006E, + STATUS_INVALID_LOGON_HOURS = 0xC000006F, + STATUS_INVALID_WORKSTATION = 0xC0000070, + STATUS_PASSWORD_EXPIRED = 0xC0000071, + STATUS_ACCOUNT_DISABLED = 0xC0000072, + STATUS_INSUFFICIENT_RESOURCES = 0xC000009A, + STATUS_ACCOUNT_EXPIRED = 0xC0000193, + STATUS_PASSWORD_MUST_CHANGE = 0xC0000224, + STATUS_ACCOUNT_LOCKED_OUT = 0xC0000234 +} + +// Note: undocumented in MSDN +// USER_ALL_INFORMATION.UserAccountControl +enum ULONG + USER_ACCOUNT_DISABLED = 1, + USER_HOME_DIRECTORY_REQUIRED = 2, + USER_PASSWORD_NOT_REQUIRED = 4, + USER_TEMP_DUPLICATE_ACCOUNT = 8, + USER_NORMAL_ACCOUNT = 16, + USER_MNS_LOGON_ACCOUNT = 32, + USER_INTERDOMAIN_TRUST_ACCOUNT = 64, + USER_WORKSTATION_TRUST_ACCOUNT = 128, + USER_SERVER_TRUST_ACCOUNT = 256, + USER_DONT_EXPIRE_PASSWORD = 512, + USER_ACCOUNT_AUTO_LOCKED = 1024, + USER_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 2048, + USER_SMARTCARD_REQUIRED = 4096, + USER_TRUSTED_FOR_DELEGATION = 8192, + USER_NOT_DELEGATED = 16384, + USER_USE_DES_KEY_ONLY = 32768, + USER_DONT_REQUIRE_PREAUTH = 65536, + + USER_MACHINE_ACCOUNT_MASK = 448, + USER_ACCOUNT_TYPE_MASK = 472, + USER_ALL_PARAMETERS = 2097152; + +/+ +struct UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} +alias UNICODE_STRING* PUNICODE_STRING; + +struct STRING { + USHORT Length; + USHORT MaximumLength; + PCHAR Buffer; +} +alias STRING* PSTRING; ++/ + +alias SAM_HANDLE = HANDLE; +alias SAM_HANDLE* PSAM_HANDLE; + +struct OLD_LARGE_INTEGER { + ULONG LowPart; + LONG HighPart; +} +alias OLD_LARGE_INTEGER* POLD_LARGE_INTEGER; + +enum NETLOGON_LOGON_INFO_CLASS { + NetlogonInteractiveInformation = 1, + NetlogonNetworkInformation, + NetlogonServiceInformation, + NetlogonGenericInformation, + NetlogonInteractiveTransitiveInformation, + NetlogonNetworkTransitiveInformation, + NetlogonServiceTransitiveInformation +} + + +enum CYPHER_BLOCK_LENGTH = 8; +enum USER_SESSION_KEY_LENGTH = CYPHER_BLOCK_LENGTH * 2; +enum CLEAR_BLOCK_LENGTH = 8; + +struct CYPHER_BLOCK { + CHAR[CYPHER_BLOCK_LENGTH] data = 0; +} +alias CYPHER_BLOCK* PCYPHER_BLOCK; + +struct CLEAR_BLOCK { + CHAR[CLEAR_BLOCK_LENGTH] data = 0; +} +alias CLEAR_BLOCK* PCLEAR_BLOCK; + +struct LM_OWF_PASSWORD { + CYPHER_BLOCK[2] data; +} +alias LM_OWF_PASSWORD* PLM_OWF_PASSWORD; + +struct USER_SESSION_KEY { + CYPHER_BLOCK[2] data; +} +alias USER_SESSION_KEY* PUSER_SESSION_KEY; + +alias CLEAR_BLOCK LM_CHALLENGE; +alias LM_CHALLENGE* PLM_CHALLENGE; + +alias LM_OWF_PASSWORD NT_OWF_PASSWORD; +alias NT_OWF_PASSWORD* PNT_OWF_PASSWORD; +alias LM_CHALLENGE NT_CHALLENGE; +alias NT_CHALLENGE* PNT_CHALLENGE; + +struct LOGON_HOURS { + USHORT UnitsPerWeek; + PUCHAR LogonHours; +} +alias LOGON_HOURS* PLOGON_HOURS; + +struct SR_SECURITY_DESCRIPTOR { + ULONG Length; + PUCHAR SecurityDescriptor; +} +alias SR_SECURITY_DESCRIPTOR* PSR_SECURITY_DESCRIPTOR; + +align(4): +struct USER_ALL_INFORMATION { + LARGE_INTEGER LastLogon; + LARGE_INTEGER LastLogoff; + LARGE_INTEGER PasswordLastSet; + LARGE_INTEGER AccountExpires; + LARGE_INTEGER PasswordCanChange; + LARGE_INTEGER PasswordMustChange; + UNICODE_STRING UserName; + UNICODE_STRING FullName; + UNICODE_STRING HomeDirectory; + UNICODE_STRING HomeDirectoryDrive; + UNICODE_STRING ScriptPath; + UNICODE_STRING ProfilePath; + UNICODE_STRING AdminComment; + UNICODE_STRING WorkStations; + UNICODE_STRING UserComment; + UNICODE_STRING Parameters; + UNICODE_STRING LmPassword; + UNICODE_STRING NtPassword; + UNICODE_STRING PrivateData; + SR_SECURITY_DESCRIPTOR SecurityDescriptor; + ULONG UserId; + ULONG PrimaryGroupId; + ULONG UserAccountControl; + ULONG WhichFields; + LOGON_HOURS LogonHours; + USHORT BadPasswordCount; + USHORT LogonCount; + USHORT CountryCode; + USHORT CodePage; + BOOLEAN LmPasswordPresent; + BOOLEAN NtPasswordPresent; + BOOLEAN PasswordExpired; + BOOLEAN PrivateDataSensitive; +} +alias USER_ALL_INFORMATION* PUSER_ALL_INFORMATION; +align: + +struct MSV1_0_VALIDATION_INFO { + LARGE_INTEGER LogoffTime; + LARGE_INTEGER KickoffTime; + UNICODE_STRING LogonServer; + UNICODE_STRING LogonDomainName; + USER_SESSION_KEY SessionKey; + BOOLEAN Authoritative; + ULONG UserFlags; + ULONG WhichFields; + ULONG UserId; +} +alias MSV1_0_VALIDATION_INFO* PMSV1_0_VALIDATION_INFO; + +struct NETLOGON_LOGON_IDENTITY_INFO { + UNICODE_STRING LogonDomainName; + ULONG ParameterControl; + OLD_LARGE_INTEGER LogonId; + UNICODE_STRING UserName; + UNICODE_STRING Workstation; +} +alias NETLOGON_LOGON_IDENTITY_INFO* PNETLOGON_LOGON_IDENTITY_INFO; + +struct NETLOGON_INTERACTIVE_INFO { + NETLOGON_LOGON_IDENTITY_INFO Identity; + LM_OWF_PASSWORD LmOwfPassword; + NT_OWF_PASSWORD NtOwfPassword; +} +alias NETLOGON_INTERACTIVE_INFO* PNETLOGON_INTERACTIVE_INFO; + +struct NETLOGON_GENERIC_INFO { + NETLOGON_LOGON_IDENTITY_INFO Identity; + UNICODE_STRING PackageName; + ULONG DataLength; + PUCHAR LogonData; +} +alias NETLOGON_GENERIC_INFO* PNETLOGON_GENERIC_INFO; + +struct NETLOGON_NETWORK_INFO { + NETLOGON_LOGON_IDENTITY_INFO Identity; + LM_CHALLENGE LmChallenge; + STRING NtChallengeResponse; + STRING LmChallengeResponse; +} +alias NETLOGON_NETWORK_INFO* PNETLOGON_NETWORK_INFO; + +struct NETLOGON_SERVICE_INFO { + NETLOGON_LOGON_IDENTITY_INFO Identity; + LM_OWF_PASSWORD LmOwfPassword; + NT_OWF_PASSWORD NtOwfPassword; +} +alias NETLOGON_SERVICE_INFO* PNETLOGON_SERVICE_INFO; + +extern (Windows) { +NTSTATUS Msv1_0SubAuthenticationRoutine(NETLOGON_LOGON_INFO_CLASS,PVOID, + ULONG,PUSER_ALL_INFORMATION,PULONG,PULONG, + PBOOLEAN,PLARGE_INTEGER,PLARGE_INTEGER); +NTSTATUS Msv1_0SubAuthenticationFilter(NETLOGON_LOGON_INFO_CLASS,PVOID, + ULONG,PUSER_ALL_INFORMATION,PULONG,PULONG, + PBOOLEAN,PLARGE_INTEGER,PLARGE_INTEGER); +NTSTATUS Msv1_0SubAuthenticationRoutineGeneric(PVOID,ULONG,PULONG,PVOID*); +NTSTATUS Msv1_0SubAuthenticationRoutineEx(NETLOGON_LOGON_INFO_CLASS,PVOID, + ULONG,PUSER_ALL_INFORMATION,SAM_HANDLE, + PMSV1_0_VALIDATION_INFO,PULONG); +} diff --git a/src/urt/internal/sys/windows/w32api.d b/src/urt/internal/sys/windows/w32api.d new file mode 100644 index 0000000..94ed9ac --- /dev/null +++ b/src/urt/internal/sys/windows/w32api.d @@ -0,0 +1,138 @@ +/** + * Windows API header module + * + * Translated from MinGW API for MS-Windows 4.0 + * + * Authors: Stewart Gordon + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_w32api.d) + */ +module urt.internal.sys.windows.w32api; +version (Windows): + +import urt.internal.sys.windows.sdkddkver; + +version (ANSI) {} else version = Unicode; + +enum __W32API_VERSION = 3.17; +enum __W32API_MAJOR_VERSION = 3; +enum __W32API_MINOR_VERSION = 17; + +enum Windows95 = 0x0400; +enum Windows98 = 0x0410; +enum WindowsME = 0x0500; + +enum WindowsNT4 = 0x0400; +enum Windows2000 = 0x0500; +enum WindowsXP = 0x0501; +enum Windows2003 = 0x0502; +enum WindowsVista = 0x0600; +enum Windows7 = 0x0601; +enum Windows8 = 0x0602; + +enum IE3 = 0x0300; +enum IE301 = 0x0300; +enum IE302 = 0x0300; +enum IE4 = 0x0400; +enum IE401 = 0x0401; +enum IE5 = 0x0500; +enum IE5a = 0x0500; +enum IE5b = 0x0500; +enum IE501 = 0x0501; +enum IE55 = 0x0501; +enum IE56 = 0x0560; +enum IE6 = 0x0600; +enum IE601 = 0x0601; +enum IE602 = 0x0603; +enum IE7 = 0x0700; +enum IE8 = 0x0800; +enum IE9 = 0x0900; +enum IE10 = 0x0A00; + +/* These version identifiers are used to specify the minimum version of Windows that an + * application will support. + * + * Previously the minimum Windows 9x and Windows NT versions could be specified. However, since + * Windows 9x is no longer supported, either by Microsoft or by DMD, this distinction has been + * removed in order to simplify the bindings. + */ +version (Windows11) { + enum uint _WIN32_WINNT = _WIN32_WINNT_WIN10; +} else version (Windows10) { + enum uint _WIN32_WINNT = _WIN32_WINNT_WIN10; +} else version (Windows8_1) { // also Windows2012R2 + enum uint _WIN32_WINNT = _WIN32_WINNT_WINBLUE; +} else version (Windows8) { // also Windows2012 + enum uint _WIN32_WINNT = _WIN32_WINNT_WIN8; +} else version (Windows7) { // also Windows2008R2 + enum uint _WIN32_WINNT = _WIN32_WINNT_WIN7; +} else version (WindowsVista) { // also Windows2008 + enum uint _WIN32_WINNT = _WIN32_WINNT_VISTA; +} else version (Windows2003) { // also WindowsHomeServer, WindowsXP64 + enum uint _WIN32_WINNT = _WIN32_WINNT_WS03; +} else version (WindowsXP) { + enum uint _WIN32_WINNT = _WIN32_WINNT_WINXP; +} else version (Windows2000) { + // Current DMD doesn't support any version of Windows older than XP, + // but third-party compilers could use this + enum uint _WIN32_WINNT = _WIN32_WINNT_WIN2K; +} else { + enum uint _WIN32_WINNT = _WIN32_WINNT_WIN7; +} + +version (IE11) { + enum uint _WIN32_IE = _WIN32_IE_IE110; +} else version (IE10) { + enum uint _WIN32_IE = _WIN32_IE_IE100; +} else version (IE9) { + enum uint _WIN32_IE = _WIN32_IE_IE90; +} else version (IE8) { + enum uint _WIN32_IE = _WIN32_IE_IE80; +} else version (IE7) { + enum uint _WIN32_IE = _WIN32_IE_IE70; +} else version (IE602) { + enum uint _WIN32_IE = _WIN32_IE_IE60SP2; +} else version (IE601) { + enum uint _WIN32_IE = _WIN32_IE_IE60SP1; +} else version (IE6) { + enum uint _WIN32_IE = _WIN32_IE_IE60; +} else version (IE56) { + enum uint _WIN32_IE = _WIN32_IE_IE60; +} else version (IE55) { + enum uint _WIN32_IE = _WIN32_IE_IE55; +} else version (IE501) { + enum uint _WIN32_IE = _WIN32_IE_IE501; +} else version (IE5) { + enum uint _WIN32_IE = _WIN32_IE_IE50; +} else version (IE401) { + enum uint _WIN32_IE = _WIN32_IE_IE401; +} else version (IE4) { + enum uint _WIN32_IE = _WIN32_IE_IE40; +} else version (IE3) { + enum uint _WIN32_IE = _WIN32_IE_IE30; +} else static if (_WIN32_WINNT >= _WIN32_WINNT_WIN2K) { + enum uint _WIN32_IE = _WIN32_IE_IE60; +} else static if (_WIN32_WINNT >= Windows98) { //NOTE: _WIN32_WINNT will never be set this low + enum uint _WIN32_IE = _WIN32_IE_IE40; +} else { + enum uint _WIN32_IE = 0; +} + +debug (WindowsUnitTest) { + unittest { + printf("Windows NT version: %03x\n", _WIN32_WINNT); + printf("IE version: %03x\n", _WIN32_IE); + } +} + +version (Unicode) { + enum bool _WIN32_UNICODE = true; + package template DECLARE_AW(string name) { + mixin("alias " ~ name ~ "W " ~ name ~ ";"); + } +} else { + enum bool _WIN32_UNICODE = false; + package template DECLARE_AW(string name) { + mixin("alias " ~ name ~ "A " ~ name ~ ";"); + } +} diff --git a/src/urt/internal/sys/windows/winbase.d b/src/urt/internal/sys/windows/winbase.d new file mode 100644 index 0000000..c379452 --- /dev/null +++ b/src/urt/internal/sys/windows/winbase.d @@ -0,0 +1,2941 @@ +/** + * Windows API header module + * + * Translated from MinGW API for MS-Windows 3.10 + * + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_winbase.d) + */ +module urt.internal.sys.windows.winbase; +version (Windows): + +version (ANSI) {} else version = Unicode; +pragma(lib, "kernel32"); + +/** +Translation Notes: +The following macros are obsolete, and have no effect. + +LockSegment(w), MakeProcInstance(p, i), UnlockResource(h), UnlockSegment(w) +FreeModule(m), FreeProcInstance(p), GetFreeSpace(w), DefineHandleTable(w) +SetSwapAreaSize(w), LimitEmsPages(n), Yield() + +// These are not required for DMD. + +//FIXME: +// #ifndef UNDER_CE + int WinMain(HINSTANCE, HINSTANCE, LPSTR, int); +#else + int WinMain(HINSTANCE, HINSTANCE, LPWSTR, int); +#endif +int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int); + +*/ + +import urt.internal.sys.windows.windef; +import urt.internal.sys.windows.basetyps, urt.internal.sys.windows.w32api, urt.internal.sys.windows.winnt; + +// FIXME: +//alias void va_list; +import urt.internal.stdc : va_list; +import urt.mem : memset, memcpy, memmove; + + +// COMMPROP structure, used by GetCommProperties() +// ----------------------------------------------- + +// Communications provider type +enum : DWORD { + PST_UNSPECIFIED, + PST_RS232, + PST_PARALLELPORT, + PST_RS422, + PST_RS423, + PST_RS449, + PST_MODEM, // = 6 + PST_FAX = 0x0021, + PST_SCANNER = 0x0022, + PST_NETWORK_BRIDGE = 0x0100, + PST_LAT = 0x0101, + PST_TCPIP_TELNET = 0x0102, + PST_X25 = 0x0103 +} + +// Max baud rate +enum : DWORD { + BAUD_075 = 0x00000001, + BAUD_110 = 0x00000002, + BAUD_134_5 = 0x00000004, + BAUD_150 = 0x00000008, + BAUD_300 = 0x00000010, + BAUD_600 = 0x00000020, + BAUD_1200 = 0x00000040, + BAUD_1800 = 0x00000080, + BAUD_2400 = 0x00000100, + BAUD_4800 = 0x00000200, + BAUD_7200 = 0x00000400, + BAUD_9600 = 0x00000800, + BAUD_14400 = 0x00001000, + BAUD_19200 = 0x00002000, + BAUD_38400 = 0x00004000, + BAUD_56K = 0x00008000, + BAUD_128K = 0x00010000, + BAUD_115200 = 0x00020000, + BAUD_57600 = 0x00040000, + BAUD_USER = 0x10000000 +} + +// Comm capabilities +enum : DWORD { + PCF_DTRDSR = 0x0001, + PCF_RTSCTS = 0x0002, + PCF_RLSD = 0x0004, + PCF_PARITY_CHECK = 0x0008, + PCF_XONXOFF = 0x0010, + PCF_SETXCHAR = 0x0020, + PCF_TOTALTIMEOUTS = 0x0040, + PCF_INTTIMEOUTS = 0x0080, + PCF_SPECIALCHARS = 0x0100, + PCF_16BITMODE = 0x0200 +} + +enum : DWORD { + SP_PARITY = 1, + SP_BAUD = 2, + SP_DATABITS = 4, + SP_STOPBITS = 8, + SP_HANDSHAKING = 16, + SP_PARITY_CHECK = 32, + SP_RLSD = 64 +} + +enum : DWORD { + DATABITS_5 = 1, + DATABITS_6 = 2, + DATABITS_7 = 4, + DATABITS_8 = 8, + DATABITS_16 = 16, + DATABITS_16X = 32 +} + +enum : WORD { + STOPBITS_10 = 0x0001, + STOPBITS_15 = 0x0002, + STOPBITS_20 = 0x0004, + PARITY_NONE = 0x0100, + PARITY_ODD = 0x0200, + PARITY_EVEN = 0x0400, + PARITY_MARK = 0x0800, + PARITY_SPACE = 0x1000 +} + +// used by dwServiceMask +enum SP_SERIALCOMM = 1; + +struct COMMPROP { + WORD wPacketLength; + WORD wPacketVersion; + DWORD dwServiceMask; + DWORD dwReserved1; + DWORD dwMaxTxQueue; + DWORD dwMaxRxQueue; + DWORD dwMaxBaud; + DWORD dwProvSubType; + DWORD dwProvCapabilities; + DWORD dwSettableParams; + DWORD dwSettableBaud; + WORD wSettableData; + WORD wSettableStopParity; + DWORD dwCurrentTxQueue; + DWORD dwCurrentRxQueue; + DWORD dwProvSpec1; + DWORD dwProvSpec2; + WCHAR _wcProvChar = 0; + + WCHAR* wcProvChar() return { return &_wcProvChar; } +} +alias COMMPROP* LPCOMMPROP; + +// ---------- + +// for DEBUG_EVENT +enum : DWORD { + EXCEPTION_DEBUG_EVENT = 1, + CREATE_THREAD_DEBUG_EVENT, + CREATE_PROCESS_DEBUG_EVENT, + EXIT_THREAD_DEBUG_EVENT, + EXIT_PROCESS_DEBUG_EVENT, + LOAD_DLL_DEBUG_EVENT, + UNLOAD_DLL_DEBUG_EVENT, + OUTPUT_DEBUG_STRING_EVENT, + RIP_EVENT +} + +enum HFILE HFILE_ERROR = cast(HFILE) (-1); + +// for SetFilePointer() +enum : DWORD { + FILE_BEGIN = 0, + FILE_CURRENT = 1, + FILE_END = 2 +} +enum DWORD INVALID_SET_FILE_POINTER = -1; + + +// for OpenFile() +deprecated enum : UINT { + OF_READ = 0, + OF_WRITE = 0x0001, + OF_READWRITE = 0x0002, + OF_SHARE_COMPAT = 0, + OF_SHARE_EXCLUSIVE = 0x0010, + OF_SHARE_DENY_WRITE = 0x0020, + OF_SHARE_DENY_READ = 0x0030, + OF_SHARE_DENY_NONE = 0x0040, + OF_PARSE = 0x0100, + OF_DELETE = 0x0200, + OF_VERIFY = 0x0400, + OF_CANCEL = 0x0800, + OF_CREATE = 0x1000, + OF_PROMPT = 0x2000, + OF_EXIST = 0x4000, + OF_REOPEN = 0x8000 +} + +enum : DWORD { + NMPWAIT_NOWAIT = 1, + NMPWAIT_WAIT_FOREVER = -1, + NMPWAIT_USE_DEFAULT_WAIT = 0 +} + +// for ClearCommError() +enum DWORD + CE_RXOVER = 0x0001, + CE_OVERRUN = 0x0002, + CE_RXPARITY = 0x0004, + CE_FRAME = 0x0008, + CE_BREAK = 0x0010, + CE_TXFULL = 0x0100, + CE_PTO = 0x0200, + CE_IOE = 0x0400, + CE_DNS = 0x0800, + CE_OOP = 0x1000, + CE_MODE = 0x8000; + +// for CopyProgressRoutine callback. +enum : DWORD { + PROGRESS_CONTINUE = 0, + PROGRESS_CANCEL = 1, + PROGRESS_STOP = 2, + PROGRESS_QUIET = 3 +} + +enum : DWORD { + CALLBACK_CHUNK_FINISHED = 0, + CALLBACK_STREAM_SWITCH = 1 +} + +// CopyFileEx() +enum : DWORD { + COPY_FILE_FAIL_IF_EXISTS = 1, + COPY_FILE_RESTARTABLE = 2 +} + +enum : DWORD { + FILE_MAP_COPY = 1, + FILE_MAP_WRITE = 2, + FILE_MAP_READ = 4, + FILE_MAP_ALL_ACCESS = 0x000F001F +} + +enum : DWORD { + MUTEX_ALL_ACCESS = 0x001f0001, + MUTEX_MODIFY_STATE = 0x00000001, + SEMAPHORE_ALL_ACCESS = 0x001f0003, + SEMAPHORE_MODIFY_STATE = 0x00000002, + EVENT_ALL_ACCESS = 0x001f0003, + EVENT_MODIFY_STATE = 0x00000002 +} + +// CreateNamedPipe() +enum : DWORD { + PIPE_ACCESS_INBOUND = 1, + PIPE_ACCESS_OUTBOUND = 2, + PIPE_ACCESS_DUPLEX = 3 +} + +enum DWORD + PIPE_TYPE_BYTE = 0, + PIPE_TYPE_MESSAGE = 4, + PIPE_READMODE_BYTE = 0, + PIPE_READMODE_MESSAGE = 2, + PIPE_WAIT = 0, + PIPE_NOWAIT = 1; + +// GetNamedPipeInfo() +enum DWORD + PIPE_CLIENT_END = 0, + PIPE_SERVER_END = 1; + +enum DWORD PIPE_UNLIMITED_INSTANCES = 255; + +// dwCreationFlags for CreateProcess() and CreateProcessAsUser() +enum : DWORD { + DEBUG_PROCESS = 0x00000001, + DEBUG_ONLY_THIS_PROCESS = 0x00000002, + CREATE_SUSPENDED = 0x00000004, + DETACHED_PROCESS = 0x00000008, + CREATE_NEW_CONSOLE = 0x00000010, + NORMAL_PRIORITY_CLASS = 0x00000020, + IDLE_PRIORITY_CLASS = 0x00000040, + HIGH_PRIORITY_CLASS = 0x00000080, + REALTIME_PRIORITY_CLASS = 0x00000100, + CREATE_NEW_PROCESS_GROUP = 0x00000200, + CREATE_UNICODE_ENVIRONMENT = 0x00000400, + CREATE_SEPARATE_WOW_VDM = 0x00000800, + CREATE_SHARED_WOW_VDM = 0x00001000, + CREATE_FORCEDOS = 0x00002000, + BELOW_NORMAL_PRIORITY_CLASS = 0x00004000, + ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000, + CREATE_BREAKAWAY_FROM_JOB = 0x01000000, + CREATE_WITH_USERPROFILE = 0x02000000, + CREATE_DEFAULT_ERROR_MODE = 0x04000000, + CREATE_NO_WINDOW = 0x08000000, + PROFILE_USER = 0x10000000, + PROFILE_KERNEL = 0x20000000, + PROFILE_SERVER = 0x40000000 +} + +enum DWORD CONSOLE_TEXTMODE_BUFFER = 1; + +// CreateFile() +enum : DWORD { + CREATE_NEW = 1, + CREATE_ALWAYS, + OPEN_EXISTING, + OPEN_ALWAYS, + TRUNCATE_EXISTING +} + +// CreateFile() +enum DWORD + FILE_FLAG_WRITE_THROUGH = 0x80000000, + FILE_FLAG_OVERLAPPED = 0x40000000, + FILE_FLAG_NO_BUFFERING = 0x20000000, + FILE_FLAG_RANDOM_ACCESS = 0x10000000, + FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000, + FILE_FLAG_DELETE_ON_CLOSE = 0x04000000, + FILE_FLAG_BACKUP_SEMANTICS = 0x02000000, + FILE_FLAG_POSIX_SEMANTICS = 0x01000000, + FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000, + FILE_FLAG_OPEN_NO_RECALL = 0x00100000; + +static if (_WIN32_WINNT >= 0x500) { +enum DWORD FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000; +} + +// for CreateFile() +enum DWORD + SECURITY_ANONYMOUS = SECURITY_IMPERSONATION_LEVEL.SecurityAnonymous<<16, + SECURITY_IDENTIFICATION = SECURITY_IMPERSONATION_LEVEL.SecurityIdentification<<16, + SECURITY_IMPERSONATION = SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation<<16, + SECURITY_DELEGATION = SECURITY_IMPERSONATION_LEVEL.SecurityDelegation<<16, + SECURITY_CONTEXT_TRACKING = 0x00040000, + SECURITY_EFFECTIVE_ONLY = 0x00080000, + SECURITY_SQOS_PRESENT = 0x00100000, + SECURITY_VALID_SQOS_FLAGS = 0x001F0000; + +// for GetFinalPathNameByHandle() +enum DWORD + VOLUME_NAME_DOS = 0x0, + VOLUME_NAME_GUID = 0x1, + VOLUME_NAME_NT = 0x2, + VOLUME_NAME_NONE = 0x4, + FILE_NAME_NORMALIZED = 0x0, + FILE_NAME_OPENED = 0x8; + +// Thread exit code +enum DWORD STILL_ACTIVE = 0x103; + +/* ??? The only documentation of this seems to be about Windows CE and to + * state what _doesn't_ support it. + */ +enum DWORD FIND_FIRST_EX_CASE_SENSITIVE = 1; + +// GetBinaryType() +enum : DWORD { + SCS_32BIT_BINARY = 0, + SCS_DOS_BINARY, + SCS_WOW_BINARY, + SCS_PIF_BINARY, + SCS_POSIX_BINARY, + SCS_OS216_BINARY +} + +enum size_t + MAX_COMPUTERNAME_LENGTH = 15, + HW_PROFILE_GUIDLEN = 39, + MAX_PROFILE_LEN = 80; + +// HW_PROFILE_INFO +enum DWORD + DOCKINFO_UNDOCKED = 1, + DOCKINFO_DOCKED = 2, + DOCKINFO_USER_SUPPLIED = 4, + DOCKINFO_USER_UNDOCKED = DOCKINFO_USER_SUPPLIED | DOCKINFO_UNDOCKED, + DOCKINFO_USER_DOCKED = DOCKINFO_USER_SUPPLIED | DOCKINFO_DOCKED; + +// DriveType(), RealDriveType() +enum : int { + DRIVE_UNKNOWN = 0, + DRIVE_NO_ROOT_DIR, + DRIVE_REMOVABLE, + DRIVE_FIXED, + DRIVE_REMOTE, + DRIVE_CDROM, + DRIVE_RAMDISK +} + +// GetFileType() +enum : DWORD { + FILE_TYPE_UNKNOWN = 0, + FILE_TYPE_DISK, + FILE_TYPE_CHAR, + FILE_TYPE_PIPE, + FILE_TYPE_REMOTE = 0x8000 +} + +// Get/SetHandleInformation() +enum DWORD + HANDLE_FLAG_INHERIT = 0x01, + HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x02; + +enum : DWORD { + STD_INPUT_HANDLE = 0xFFFFFFF6, + STD_OUTPUT_HANDLE = 0xFFFFFFF5, + STD_ERROR_HANDLE = 0xFFFFFFF4 +} + +@trusted enum HANDLE INVALID_HANDLE_VALUE = cast(HANDLE) (-1); + +enum : DWORD { + GET_TAPE_MEDIA_INFORMATION = 0, + GET_TAPE_DRIVE_INFORMATION = 1 +} + +enum : DWORD { + SET_TAPE_MEDIA_INFORMATION = 0, + SET_TAPE_DRIVE_INFORMATION = 1 +} + +// SetThreadPriority()/GetThreadPriority() +enum : int { + THREAD_PRIORITY_IDLE = -15, + THREAD_PRIORITY_LOWEST = -2, + THREAD_PRIORITY_BELOW_NORMAL = -1, + THREAD_PRIORITY_NORMAL = 0, + THREAD_PRIORITY_ABOVE_NORMAL = 1, + THREAD_PRIORITY_HIGHEST = 2, + THREAD_PRIORITY_TIME_CRITICAL = 15, + THREAD_PRIORITY_ERROR_RETURN = 2147483647 +} + +enum : DWORD { + TIME_ZONE_ID_UNKNOWN, + TIME_ZONE_ID_STANDARD, + TIME_ZONE_ID_DAYLIGHT, + TIME_ZONE_ID_INVALID = 0xFFFFFFFF +} + +enum DWORD + FS_CASE_SENSITIVE = 1, + FS_CASE_IS_PRESERVED = 2, + FS_UNICODE_STORED_ON_DISK = 4, + FS_PERSISTENT_ACLS = 8, + FS_FILE_COMPRESSION = 16, + FS_VOL_IS_COMPRESSED = 32768; + +// Flags for GlobalAlloc +enum UINT + GMEM_FIXED = 0, + GMEM_MOVEABLE = 0x0002, + GMEM_ZEROINIT = 0x0040, + GPTR = 0x0040, + GHND = 0x0042, + GMEM_MODIFY = 0x0080, // used only for GlobalRealloc + GMEM_VALID_FLAGS = 0x7F72; + +/+ // Obselete flags (Win16 only) + GMEM_NOCOMPACT=16; + GMEM_NODISCARD=32; + GMEM_DISCARDABLE=256; + GMEM_NOT_BANKED=4096; + GMEM_LOWER=4096; + GMEM_SHARE=8192; + GMEM_DDESHARE=8192; + + GMEM_LOCKCOUNT=255; + +// for GlobalFlags() + GMEM_DISCARDED = 16384; + GMEM_INVALID_HANDLE = 32768; + + GMEM_NOTIFY = 16384; ++/ + +enum UINT + LMEM_FIXED = 0, + LMEM_MOVEABLE = 0x0002, + LMEM_NONZEROLPTR = 0, + NONZEROLPTR = 0, + LMEM_NONZEROLHND = 0x0002, + NONZEROLHND = 0x0002, + LMEM_DISCARDABLE = 0x0F00, + LMEM_NOCOMPACT = 0x0010, + LMEM_NODISCARD = 0x0020, + LMEM_ZEROINIT = 0x0040, + LPTR = 0x0040, + LHND = 0x0042, + LMEM_MODIFY = 0x0080, + LMEM_LOCKCOUNT = 0x00FF, + LMEM_DISCARDED = 0x4000, + LMEM_INVALID_HANDLE = 0x8000; + + + +// used in EXCEPTION_RECORD +enum : DWORD { + STATUS_WAIT_0 = 0, + STATUS_ABANDONED_WAIT_0 = 0x00000080, + STATUS_USER_APC = 0x000000C0, + STATUS_TIMEOUT = 0x00000102, + STATUS_PENDING = 0x00000103, + + STATUS_SEGMENT_NOTIFICATION = 0x40000005, + STATUS_GUARD_PAGE_VIOLATION = 0x80000001, + STATUS_DATATYPE_MISALIGNMENT = 0x80000002, + STATUS_BREAKPOINT = 0x80000003, + STATUS_SINGLE_STEP = 0x80000004, + + STATUS_ACCESS_VIOLATION = 0xC0000005, + STATUS_IN_PAGE_ERROR = 0xC0000006, + STATUS_INVALID_HANDLE = 0xC0000008, + + STATUS_NO_MEMORY = 0xC0000017, + STATUS_ILLEGAL_INSTRUCTION = 0xC000001D, + STATUS_NONCONTINUABLE_EXCEPTION = 0xC0000025, + STATUS_INVALID_DISPOSITION = 0xC0000026, + STATUS_ARRAY_BOUNDS_EXCEEDED = 0xC000008C, + STATUS_FLOAT_DENORMAL_OPERAND = 0xC000008D, + STATUS_FLOAT_DIVIDE_BY_ZERO = 0xC000008E, + STATUS_FLOAT_INEXACT_RESULT = 0xC000008F, + STATUS_FLOAT_INVALID_OPERATION = 0xC0000090, + STATUS_FLOAT_OVERFLOW = 0xC0000091, + STATUS_FLOAT_STACK_CHECK = 0xC0000092, + STATUS_FLOAT_UNDERFLOW = 0xC0000093, + STATUS_INTEGER_DIVIDE_BY_ZERO = 0xC0000094, + STATUS_INTEGER_OVERFLOW = 0xC0000095, + STATUS_PRIVILEGED_INSTRUCTION = 0xC0000096, + STATUS_STACK_OVERFLOW = 0xC00000FD, + STATUS_CONTROL_C_EXIT = 0xC000013A, + STATUS_DLL_INIT_FAILED = 0xC0000142, + STATUS_DLL_INIT_FAILED_LOGOFF = 0xC000026B, + + CONTROL_C_EXIT = STATUS_CONTROL_C_EXIT, + + EXCEPTION_ACCESS_VIOLATION = STATUS_ACCESS_VIOLATION, + EXCEPTION_DATATYPE_MISALIGNMENT = STATUS_DATATYPE_MISALIGNMENT, + EXCEPTION_BREAKPOINT = STATUS_BREAKPOINT, + EXCEPTION_SINGLE_STEP = STATUS_SINGLE_STEP, + EXCEPTION_ARRAY_BOUNDS_EXCEEDED = STATUS_ARRAY_BOUNDS_EXCEEDED, + EXCEPTION_FLT_DENORMAL_OPERAND = STATUS_FLOAT_DENORMAL_OPERAND, + EXCEPTION_FLT_DIVIDE_BY_ZERO = STATUS_FLOAT_DIVIDE_BY_ZERO, + EXCEPTION_FLT_INEXACT_RESULT = STATUS_FLOAT_INEXACT_RESULT, + EXCEPTION_FLT_INVALID_OPERATION = STATUS_FLOAT_INVALID_OPERATION, + EXCEPTION_FLT_OVERFLOW = STATUS_FLOAT_OVERFLOW, + EXCEPTION_FLT_STACK_CHECK = STATUS_FLOAT_STACK_CHECK, + EXCEPTION_FLT_UNDERFLOW = STATUS_FLOAT_UNDERFLOW, + EXCEPTION_INT_DIVIDE_BY_ZERO = STATUS_INTEGER_DIVIDE_BY_ZERO, + EXCEPTION_INT_OVERFLOW = STATUS_INTEGER_OVERFLOW, + EXCEPTION_PRIV_INSTRUCTION = STATUS_PRIVILEGED_INSTRUCTION, + EXCEPTION_IN_PAGE_ERROR = STATUS_IN_PAGE_ERROR, + EXCEPTION_ILLEGAL_INSTRUCTION = STATUS_ILLEGAL_INSTRUCTION, + EXCEPTION_NONCONTINUABLE_EXCEPTION = STATUS_NONCONTINUABLE_EXCEPTION, + EXCEPTION_STACK_OVERFLOW = STATUS_STACK_OVERFLOW, + EXCEPTION_INVALID_DISPOSITION = STATUS_INVALID_DISPOSITION, + EXCEPTION_GUARD_PAGE = STATUS_GUARD_PAGE_VIOLATION, + EXCEPTION_INVALID_HANDLE = STATUS_INVALID_HANDLE +} + +// for PROCESS_HEAP_ENTRY +enum WORD + PROCESS_HEAP_REGION = 1, + PROCESS_HEAP_UNCOMMITTED_RANGE = 2, + PROCESS_HEAP_ENTRY_BUSY = 4, + PROCESS_HEAP_ENTRY_MOVEABLE = 16, + PROCESS_HEAP_ENTRY_DDESHARE = 32; + +// for LoadLibraryEx() +enum DWORD + DONT_RESOLVE_DLL_REFERENCES = 0x01, // not for WinME and earlier + LOAD_LIBRARY_AS_DATAFILE = 0x02, + LOAD_WITH_ALTERED_SEARCH_PATH = 0x08, + LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x10; // only for XP and later + +// for LockFile() +enum DWORD + LOCKFILE_FAIL_IMMEDIATELY = 1, + LOCKFILE_EXCLUSIVE_LOCK = 2; + +enum MAXIMUM_WAIT_OBJECTS = 64; +enum MAXIMUM_SUSPEND_COUNT = 0x7F; + +enum WAIT_OBJECT_0 = 0; +enum WAIT_ABANDONED_0 = 128; + +//const WAIT_TIMEOUT=258; // also in winerror.h + +enum : DWORD { + WAIT_IO_COMPLETION = 0x000000C0, + WAIT_ABANDONED = 0x00000080, + WAIT_FAILED = 0xFFFFFFFF +} + +// PurgeComm() +enum DWORD + PURGE_TXABORT = 1, + PURGE_RXABORT = 2, + PURGE_TXCLEAR = 4, + PURGE_RXCLEAR = 8; + +// ReadEventLog() +enum DWORD + EVENTLOG_SEQUENTIAL_READ = 1, + EVENTLOG_SEEK_READ = 2, + EVENTLOG_FORWARDS_READ = 4, + EVENTLOG_BACKWARDS_READ = 8; + +// ReportEvent() +enum : WORD { + EVENTLOG_SUCCESS = 0, + EVENTLOG_ERROR_TYPE = 1, + EVENTLOG_WARNING_TYPE = 2, + EVENTLOG_INFORMATION_TYPE = 4, + EVENTLOG_AUDIT_SUCCESS = 8, + EVENTLOG_AUDIT_FAILURE = 16 +} + +// FormatMessage() +enum DWORD + FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x0100, + FORMAT_MESSAGE_IGNORE_INSERTS = 0x0200, + FORMAT_MESSAGE_FROM_STRING = 0x0400, + FORMAT_MESSAGE_FROM_HMODULE = 0x0800, + FORMAT_MESSAGE_FROM_SYSTEM = 0x1000, + FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x2000; + +enum DWORD FORMAT_MESSAGE_MAX_WIDTH_MASK = 255; + +// also in ddk/ntapi.h +// To restore default error mode, call SetErrorMode(0) +enum { + SEM_FAILCRITICALERRORS = 0x0001, + SEM_NOGPFAULTERRORBOX = 0x0002, + SEM_NOALIGNMENTFAULTEXCEPT = 0x0004, + SEM_NOOPENFILEERRORBOX = 0x8000 +} +// end ntapi.h + +enum { + SLE_ERROR = 1, + SLE_MINORERROR, + SLE_WARNING +} + +enum SHUTDOWN_NORETRY = 1; + +// Return type for exception filters. +enum : LONG { + EXCEPTION_EXECUTE_HANDLER = 1, + EXCEPTION_CONTINUE_EXECUTION = -1, + EXCEPTION_CONTINUE_SEARCH = 0 +} + +enum : ATOM { + MAXINTATOM = 0xC000, + INVALID_ATOM = 0 +} + +enum IGNORE = 0; +enum INFINITE = 0xFFFFFFFF; + +// EscapeCommFunction() +enum { + SETXOFF = 1, + SETXON, + SETRTS, + CLRRTS, + SETDTR, + CLRDTR, // = 6 + SETBREAK = 8, + CLRBREAK = 9 +} + + +// for SetCommMask() +enum DWORD + EV_RXCHAR = 0x0001, + EV_RXFLAG = 0x0002, + EV_TXEMPTY = 0x0004, + EV_CTS = 0x0008, + EV_DSR = 0x0010, + EV_RLSD = 0x0020, + EV_BREAK = 0x0040, + EV_ERR = 0x0080, + EV_RING = 0x0100, + EV_PERR = 0x0200, + EV_RX80FULL = 0x0400, + EV_EVENT1 = 0x0800, + EV_EVENT2 = 0x1000; + +// GetCommModemStatus() +enum DWORD + MS_CTS_ON = 0x0010, + MS_DSR_ON = 0x0020, + MS_RING_ON = 0x0040, + MS_RLSD_ON = 0x0080; + + +// DCB +enum : BYTE { + NOPARITY = 0, + ODDPARITY, + EVENPARITY, + MARKPARITY, + SPACEPARITY +} +// DCB +enum : BYTE { + ONESTOPBIT = 0, + ONE5STOPBITS, + TWOSTOPBITS +} +// DCB +enum : DWORD { + CBR_110 = 110, + CBR_300 = 300, + CBR_600 = 600, + CBR_1200 = 1200, + CBR_2400 = 2400, + CBR_4800 = 4800, + CBR_9600 = 9600, + CBR_14400 = 14400, + CBR_19200 = 19200, + CBR_38400 = 38400, + CBR_56000 = 56000, + CBR_57600 = 57600, + CBR_115200 = 115200, + CBR_128000 = 128000, + CBR_256000 = 256000 +} +// DCB, 2-bit bitfield +enum { + DTR_CONTROL_DISABLE = 0, + DTR_CONTROL_ENABLE, + DTR_CONTROL_HANDSHAKE +} + +// DCB, 2-bit bitfield +enum { + RTS_CONTROL_DISABLE = 0, + RTS_CONTROL_ENABLE, + RTS_CONTROL_HANDSHAKE, + RTS_CONTROL_TOGGLE, +} + +// WIN32_STREAM_ID +enum : DWORD { + BACKUP_INVALID = 0, + BACKUP_DATA, + BACKUP_EA_DATA, + BACKUP_SECURITY_DATA, + BACKUP_ALTERNATE_DATA, + BACKUP_LINK, + BACKUP_PROPERTY_DATA, + BACKUP_OBJECT_ID, + BACKUP_REPARSE_DATA, + BACKUP_SPARSE_BLOCK +} + +// WIN32_STREAM_ID +enum : DWORD { + STREAM_NORMAL_ATTRIBUTE = 0, + STREAM_MODIFIED_WHEN_READ = 1, + STREAM_CONTAINS_SECURITY = 2, + STREAM_CONTAINS_PROPERTIES = 4 +} + +// STARTUPINFO +enum DWORD + STARTF_USESHOWWINDOW = 0x0001, + STARTF_USESIZE = 0x0002, + STARTF_USEPOSITION = 0x0004, + STARTF_USECOUNTCHARS = 0x0008, + STARTF_USEFILLATTRIBUTE = 0x0010, + STARTF_RUNFULLSCREEN = 0x0020, + STARTF_FORCEONFEEDBACK = 0x0040, + STARTF_FORCEOFFFEEDBACK = 0x0080, + STARTF_USESTDHANDLES = 0x0100, + STARTF_USEHOTKEY = 0x0200; + +// ??? +enum { + TC_NORMAL = 0, + TC_HARDERR = 1, + TC_GP_TRAP = 2, + TC_SIGNAL = 3 +} + +/+ These seem to be Windows CE-specific +enum { + AC_LINE_OFFLINE = 0, + AC_LINE_ONLINE = 1, + AC_LINE_BACKUP_POWER = 2, + AC_LINE_UNKNOWN = 255 +} + +enum { + BATTERY_FLAG_HIGH = 1, + BATTERY_FLAG_LOW = 2, + BATTERY_FLAG_CRITICAL = 4, + BATTERY_FLAG_CHARGING = 8, + BATTERY_FLAG_NO_BATTERY = 128, + BATTERY_FLAG_UNKNOWN = 255, + BATTERY_PERCENTAGE_UNKNOWN = 255, + BATTERY_LIFE_UNKNOWN = 0xFFFFFFFF +} ++/ + +// ??? +enum HINSTANCE_ERROR = 32; + +// returned from GetFileSize() +enum DWORD INVALID_FILE_SIZE = 0xFFFFFFFF; + +enum DWORD TLS_OUT_OF_INDEXES = 0xFFFFFFFF; + +// GetWriteWatch() +enum DWORD WRITE_WATCH_FLAG_RESET = 1; + +// for LogonUser() +enum : DWORD { + LOGON32_LOGON_INTERACTIVE = 2, + LOGON32_LOGON_NETWORK = 3, + LOGON32_LOGON_BATCH = 4, + LOGON32_LOGON_SERVICE = 5, + LOGON32_LOGON_UNLOCK = 7 +} + +// for LogonUser() +enum : DWORD { + LOGON32_PROVIDER_DEFAULT, + LOGON32_PROVIDER_WINNT35, + LOGON32_PROVIDER_WINNT40, + LOGON32_PROVIDER_WINNT50 +} + +// for MoveFileEx() +enum DWORD + MOVEFILE_REPLACE_EXISTING = 1, + MOVEFILE_COPY_ALLOWED = 2, + MOVEFILE_DELAY_UNTIL_REBOOT = 4, + MOVEFILE_WRITE_THROUGH = 8; + +// DefineDosDevice() +enum DWORD + DDD_RAW_TARGET_PATH = 1, + DDD_REMOVE_DEFINITION = 2, + DDD_EXACT_MATCH_ON_REMOVE = 4; + +static if (_WIN32_WINNT >= 0x500) { + enum : DWORD { + LOGON32_LOGON_NETWORK_CLEARTEXT = 8, + LOGON32_LOGON_NEW_CREDENTIALS = 9 + } + + // ReplaceFile() +enum DWORD + REPLACEFILE_WRITE_THROUGH = 1, + REPLACEFILE_IGNORE_MERGE_ERRORS = 2; +} + +static if (_WIN32_WINNT >= 0x501) { +enum DWORD + GET_MODULE_HANDLE_EX_FLAG_PIN = 1, + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 2, + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 4; + + // for ACTCTX +enum DWORD + ACTCTX_FLAG_PROCESSOR_ARCHITECTURE_VALID = 0x01, + ACTCTX_FLAG_LANGID_VALID = 0x02, + ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID = 0x04, + ACTCTX_FLAG_RESOURCE_NAME_VALID = 0x08, + ACTCTX_FLAG_SET_PROCESS_DEFAULT = 0x10, + ACTCTX_FLAG_APPLICATION_NAME_VALID = 0x20, + ACTCTX_FLAG_HMODULE_VALID = 0x80; + + // DeactivateActCtx() +enum DWORD DEACTIVATE_ACTCTX_FLAG_FORCE_EARLY_DEACTIVATION = 1; + // FindActCtxSectionString() +enum DWORD FIND_ACTCTX_SECTION_KEY_RETURN_HACTCTX = 1; + // QueryActCtxW() +enum DWORD + QUERY_ACTCTX_FLAG_USE_ACTIVE_ACTCTX = 0x04, + QUERY_ACTCTX_FLAG_ACTCTX_IS_HMODULE = 0x08, + QUERY_ACTCTX_FLAG_ACTCTX_IS_ADDRESS = 0x10; + + enum { + LOGON_WITH_PROFILE = 1, + LOGON_NETCREDENTIALS_ONLY + } +} + +// ---- + +struct FILETIME { + DWORD dwLowDateTime; + DWORD dwHighDateTime; +} +alias FILETIME* PFILETIME, LPFILETIME; + +struct BY_HANDLE_FILE_INFORMATION { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD dwVolumeSerialNumber; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD nNumberOfLinks; + DWORD nFileIndexHigh; + DWORD nFileIndexLow; +} +alias BY_HANDLE_FILE_INFORMATION* LPBY_HANDLE_FILE_INFORMATION; + +struct DCB { + DWORD DCBlength = DCB.sizeof; + DWORD BaudRate; +/+ + DWORD fBinary:1; // Binary Mode (skip EOF check) + DWORD fParity:1; // Enable parity checking + DWORD fOutxCtsFlow:1; // CTS handshaking on output + DWORD fOutxDsrFlow:1; // DSR handshaking on output + DWORD fDtrControl:2; // DTR Flow control + DWORD fDsrSensitivity:1; // DSR Sensitivity + DWORD fTXContinueOnXoff:1; // Continue TX when Xoff sent + DWORD fOutX:1; // Enable output X-ON/X-OFF + DWORD fInX:1; // Enable input X-ON/X-OFF + DWORD fErrorChar:1; // Enable Err Replacement + DWORD fNull:1; // Enable Null stripping + DWORD fRtsControl:2; // Rts Flow control + DWORD fAbortOnError:1; // Abort all reads and writes on Error + DWORD fDummy2:17; // Reserved ++/ + uint _bf; + bool fBinary(bool f) { _bf = (_bf & ~0x0001) | f; return f; } + bool fParity(bool f) { _bf = (_bf & ~0x0002) | (f<<1); return f; } + bool fOutxCtsFlow(bool f) { _bf = (_bf & ~0x0004) | (f<<2); return f; } + bool fOutxDsrFlow(bool f) { _bf = (_bf & ~0x0008) | (f<<3); return f; } + byte fDtrControl(byte x) { _bf = (_bf & ~0x0030) | (x<<4); return cast(byte)(x & 3); } + bool fDsrSensitivity(bool f) { _bf = (_bf & ~0x0040) | (f<<6); return f; } + bool fTXContinueOnXoff(bool f) { _bf = (_bf & ~0x0080) | (f<<7); return f; } + bool fOutX(bool f) { _bf = (_bf & ~0x0100) | (f<<8); return f; } + bool fInX(bool f) { _bf = (_bf & ~0x0200) | (f<<9); return f; } + bool fErrorChar(bool f) { _bf = (_bf & ~0x0400) | (f<<10); return f; } + bool fNull(bool f) { _bf = (_bf & ~0x0800) | (f<<11); return f; } + byte fRtsControl(byte x) { _bf = (_bf & ~0x3000) | (x<<12); return cast(byte)(x & 3); } + bool fAbortOnError(bool f) { _bf = (_bf & ~0x4000) | (f<<14); return f; } + + bool fBinary() { return cast(bool) (_bf & 1); } + bool fParity() { return cast(bool) (_bf & 2); } + bool fOutxCtsFlow() { return cast(bool) (_bf & 4); } + bool fOutxDsrFlow() { return cast(bool) (_bf & 8); } + byte fDtrControl() { return cast(byte) ((_bf & (32+16))>>4); } + bool fDsrSensitivity() { return cast(bool) (_bf & 64); } + bool fTXContinueOnXoff()() { return cast(bool) (_bf & 128); } + bool fOutX() { return cast(bool) (_bf & 256); } + bool fInX() { return cast(bool) (_bf & 512); } + bool fErrorChar() { return cast(bool) (_bf & 1024); } + bool fNull() { return cast(bool) (_bf & 2048); } + byte fRtsControl() { return cast(byte) ((_bf & (4096+8192))>>12); } + bool fAbortOnError() { return cast(bool) (_bf & 16384); } + + WORD wReserved; + WORD XonLim; + WORD XoffLim; + BYTE ByteSize; + BYTE Parity; + BYTE StopBits; + char XonChar = 0; + char XoffChar = 0; + char ErrorChar = 0; + char EofChar = 0; + char EvtChar = 0; + WORD wReserved1; +} +alias DCB* LPDCB; + +struct COMMCONFIG { + DWORD dwSize = COMMCONFIG.sizeof; + WORD wVersion; + WORD wReserved; + DCB dcb; + DWORD dwProviderSubType; + DWORD dwProviderOffset; + DWORD dwProviderSize; + WCHAR _wcProviderData = 0; + + WCHAR* wcProviderData() return { return &_wcProviderData; } +} +alias COMMCONFIG* LPCOMMCONFIG; + +struct COMMTIMEOUTS { + DWORD ReadIntervalTimeout; + DWORD ReadTotalTimeoutMultiplier; + DWORD ReadTotalTimeoutConstant; + DWORD WriteTotalTimeoutMultiplier; + DWORD WriteTotalTimeoutConstant; +} +alias COMMTIMEOUTS* LPCOMMTIMEOUTS; + +struct COMSTAT { +/+ + DWORD fCtsHold:1; + DWORD fDsrHold:1; + DWORD fRlsdHold:1; + DWORD fXoffHold:1; + DWORD fXoffSent:1; + DWORD fEof:1; + DWORD fTxim:1; + DWORD fReserved:25; ++/ + DWORD _bf; + bool fCtsHold(bool f) { _bf = (_bf & ~1) | f; return f; } + bool fDsrHold(bool f) { _bf = (_bf & ~2) | (f<<1); return f; } + bool fRlsdHold(bool f) { _bf = (_bf & ~4) | (f<<2); return f; } + bool fXoffHold(bool f) { _bf = (_bf & ~8) | (f<<3); return f; } + bool fXoffSent(bool f) { _bf = (_bf & ~16) | (f<<4); return f; } + bool fEof(bool f) { _bf = (_bf & ~32) | (f<<5); return f; } + bool fTxim(bool f) { _bf = (_bf & ~64) | (f<<6); return f; } + + bool fCtsHold() { return cast(bool) (_bf & 1); } + bool fDsrHold() { return cast(bool) (_bf & 2); } + bool fRlsdHold()() { return cast(bool) (_bf & 4); } + bool fXoffHold()() { return cast(bool) (_bf & 8); } + bool fXoffSent()() { return cast(bool) (_bf & 16); } + bool fEof() { return cast(bool) (_bf & 32); } + bool fTxim() { return cast(bool) (_bf & 64); } + + DWORD cbInQue; + DWORD cbOutQue; +} +alias COMSTAT* LPCOMSTAT; + +struct CREATE_PROCESS_DEBUG_INFO { + HANDLE hFile; + HANDLE hProcess; + HANDLE hThread; + LPVOID lpBaseOfImage; + DWORD dwDebugInfoFileOffset; + DWORD nDebugInfoSize; + LPVOID lpThreadLocalBase; + LPTHREAD_START_ROUTINE lpStartAddress; + LPVOID lpImageName; + WORD fUnicode; +} +alias CREATE_PROCESS_DEBUG_INFO* LPCREATE_PROCESS_DEBUG_INFO; + +struct CREATE_THREAD_DEBUG_INFO { + HANDLE hThread; + LPVOID lpThreadLocalBase; + LPTHREAD_START_ROUTINE lpStartAddress; +} +alias CREATE_THREAD_DEBUG_INFO* LPCREATE_THREAD_DEBUG_INFO; + +struct EXCEPTION_DEBUG_INFO { + EXCEPTION_RECORD ExceptionRecord; + DWORD dwFirstChance; +} +alias EXCEPTION_DEBUG_INFO* LPEXCEPTION_DEBUG_INFO; + +struct EXIT_THREAD_DEBUG_INFO { + DWORD dwExitCode; +} +alias EXIT_THREAD_DEBUG_INFO* LPEXIT_THREAD_DEBUG_INFO; + +struct EXIT_PROCESS_DEBUG_INFO { + DWORD dwExitCode; +} +alias EXIT_PROCESS_DEBUG_INFO* LPEXIT_PROCESS_DEBUG_INFO; + +struct LOAD_DLL_DEBUG_INFO { + HANDLE hFile; + LPVOID lpBaseOfDll; + DWORD dwDebugInfoFileOffset; + DWORD nDebugInfoSize; + LPVOID lpImageName; + WORD fUnicode; +} +alias LOAD_DLL_DEBUG_INFO* LPLOAD_DLL_DEBUG_INFO; + +struct UNLOAD_DLL_DEBUG_INFO { + LPVOID lpBaseOfDll; +} +alias UNLOAD_DLL_DEBUG_INFO* LPUNLOAD_DLL_DEBUG_INFO; + +struct OUTPUT_DEBUG_STRING_INFO { + LPSTR lpDebugStringData; + WORD fUnicode; + WORD nDebugStringLength; +} +alias OUTPUT_DEBUG_STRING_INFO* LPOUTPUT_DEBUG_STRING_INFO; + +struct RIP_INFO { + DWORD dwError; + DWORD dwType; +} +alias RIP_INFO* LPRIP_INFO; + +struct DEBUG_EVENT { + DWORD dwDebugEventCode; + DWORD dwProcessId; + DWORD dwThreadId; + union { + EXCEPTION_DEBUG_INFO Exception; + CREATE_THREAD_DEBUG_INFO CreateThread; + CREATE_PROCESS_DEBUG_INFO CreateProcessInfo; + EXIT_THREAD_DEBUG_INFO ExitThread; + EXIT_PROCESS_DEBUG_INFO ExitProcess; + LOAD_DLL_DEBUG_INFO LoadDll; + UNLOAD_DLL_DEBUG_INFO UnloadDll; + OUTPUT_DEBUG_STRING_INFO DebugString; + RIP_INFO RipInfo; + } +} +alias DEBUG_EVENT* LPDEBUG_EVENT; + +struct OVERLAPPED { + ULONG_PTR Internal; + ULONG_PTR InternalHigh; + union { + struct { + DWORD Offset; + DWORD OffsetHigh; + } + PVOID Pointer; + } + HANDLE hEvent; +} +alias OVERLAPPED* POVERLAPPED, LPOVERLAPPED; + +struct STARTUPINFOA { + DWORD cb = STARTUPINFOA.sizeof; + LPSTR lpReserved; + LPSTR lpDesktop; + LPSTR lpTitle; + DWORD dwX; + DWORD dwY; + DWORD dwXSize; + DWORD dwYSize; + DWORD dwXCountChars; + DWORD dwYCountChars; + DWORD dwFillAttribute; + DWORD dwFlags; + WORD wShowWindow; + WORD cbReserved2; + PBYTE lpReserved2; + HANDLE hStdInput; + HANDLE hStdOutput; + HANDLE hStdError; +} +alias STARTUPINFOA* LPSTARTUPINFOA; + +struct STARTUPINFOW { + DWORD cb = STARTUPINFOW.sizeof; + LPWSTR lpReserved; + LPWSTR lpDesktop; + LPWSTR lpTitle; + DWORD dwX; + DWORD dwY; + DWORD dwXSize; + DWORD dwYSize; + DWORD dwXCountChars; + DWORD dwYCountChars; + DWORD dwFillAttribute; + DWORD dwFlags; + WORD wShowWindow; + WORD cbReserved2; + PBYTE lpReserved2; + HANDLE hStdInput; + HANDLE hStdOutput; + HANDLE hStdError; +} +alias STARTUPINFOW STARTUPINFO_W; +alias STARTUPINFOW* LPSTARTUPINFOW, LPSTARTUPINFO_W; + +struct PROCESS_INFORMATION { + HANDLE hProcess; + HANDLE hThread; + DWORD dwProcessId; + DWORD dwThreadId; +} +alias PROCESS_INFORMATION* PPROCESS_INFORMATION, LPPROCESS_INFORMATION; + +/* +struct CRITICAL_SECTION_DEBUG { + WORD Type; + WORD CreatorBackTraceIndex; + CRITICAL_SECTION* CriticalSection; + LIST_ENTRY ProcessLocksList; + DWORD EntryCount; + DWORD ContentionCount; + DWORD[2] Spare; +} +alias CRITICAL_SECTION_DEBUG* PCRITICAL_SECTION_DEBUG; + +struct CRITICAL_SECTION { + PCRITICAL_SECTION_DEBUG DebugInfo; + LONG LockCount; + LONG RecursionCount; + HANDLE OwningThread; + HANDLE LockSemaphore; + DWORD SpinCount; +} +alias CRITICAL_SECTION* PCRITICAL_SECTION, LPCRITICAL_SECTION; +*/ + +alias CRITICAL_SECTION_DEBUG = RTL_CRITICAL_SECTION_DEBUG; +alias CRITICAL_SECTION_DEBUG* PCRITICAL_SECTION_DEBUG; + +alias CRITICAL_SECTION = RTL_CRITICAL_SECTION; +alias CRITICAL_SECTION* PCRITICAL_SECTION, LPCRITICAL_SECTION; + +struct SYSTEMTIME { + WORD wYear; + WORD wMonth; + WORD wDayOfWeek; + WORD wDay; + WORD wHour; + WORD wMinute; + WORD wSecond; + WORD wMilliseconds; +} +alias SYSTEMTIME* LPSYSTEMTIME; + +struct WIN32_FILE_ATTRIBUTE_DATA { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; +} +alias WIN32_FILE_ATTRIBUTE_DATA* LPWIN32_FILE_ATTRIBUTE_DATA; + +struct WIN32_FIND_DATAA { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; +// #ifdef _WIN32_WCE +// DWORD dwOID; +// #else + DWORD dwReserved0; + DWORD dwReserved1; +// #endif + CHAR[MAX_PATH] cFileName = 0; +// #ifndef _WIN32_WCE + CHAR[14] cAlternateFileName = 0; +// #endif +} +alias WIN32_FIND_DATAA* PWIN32_FIND_DATAA, LPWIN32_FIND_DATAA; + +struct WIN32_FIND_DATAW { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; +// #ifdef _WIN32_WCE +// DWORD dwOID; +// #else + DWORD dwReserved0; + DWORD dwReserved1; +// #endif + WCHAR[MAX_PATH] cFileName = 0; +// #ifndef _WIN32_WCE + WCHAR[14] cAlternateFileName = 0; +// #endif +} +alias WIN32_FIND_DATAW* PWIN32_FIND_DATAW, LPWIN32_FIND_DATAW; + +struct WIN32_STREAM_ID { + DWORD dwStreamId; + DWORD dwStreamAttributes; + LARGE_INTEGER Size; + DWORD dwStreamNameSize; + WCHAR _cStreamName = 0; + + WCHAR* cStreamName() return { return &_cStreamName; } +} +alias WIN32_STREAM_ID* LPWIN32_STREAM_ID; + +static if (_WIN32_WINNT >= 0x601) { + enum FINDEX_INFO_LEVELS { + FindExInfoStandard, + FindExInfoBasic, + FindExInfoMaxInfoLevel, + } +} else { + enum FINDEX_INFO_LEVELS { + FindExInfoStandard, + FindExInfoMaxInfoLevel, + } +} + +enum FINDEX_SEARCH_OPS { + FindExSearchNameMatch, + FindExSearchLimitToDirectories, + FindExSearchLimitToDevices, + FindExSearchMaxSearchOp +} + +enum ACL_INFORMATION_CLASS { + AclRevisionInformation = 1, + AclSizeInformation +} + +struct HW_PROFILE_INFOA { + DWORD dwDockInfo; + CHAR[HW_PROFILE_GUIDLEN] szHwProfileGuid = 0; + CHAR[MAX_PROFILE_LEN] szHwProfileName = 0; +} +alias HW_PROFILE_INFOA* LPHW_PROFILE_INFOA; + +struct HW_PROFILE_INFOW { + DWORD dwDockInfo; + WCHAR[HW_PROFILE_GUIDLEN] szHwProfileGuid = 0; + WCHAR[MAX_PROFILE_LEN] szHwProfileName = 0; +} +alias HW_PROFILE_INFOW* LPHW_PROFILE_INFOW; + +/* ??? MSDN documents this only for Windows CE/Mobile, but it's used by + * GetFileAttributesEx, which is in desktop Windows. + */ +enum GET_FILEEX_INFO_LEVELS { + GetFileExInfoStandard, + GetFileExMaxInfoLevel +} + +import urt.internal.sys.windows.sdkddkver : NTDDI_VERSION, NTDDI_LONGHORN; + +static if (NTDDI_VERSION >= NTDDI_LONGHORN) +{ + enum FILE_INFO_BY_HANDLE_CLASS + { + FileBasicInfo, + FileStandardInfo, + FileNameInfo, + FileRenameInfo, + FileDispositionInfo, + FileAllocationInfo, + FileEndOfFileInfo, + FileStreamInfo, + FileCompressionInfo, + FileAttributeTagInfo, + FileIdBothDirectoryInfo, + FileIdBothDirectoryRestartInfo, + FileIoPriorityHintInfo, + FileRemoteProtocolInfo, + FileFullDirectoryInfo, + FileFullDirectoryRestartInfo, +// static if (NTDDI_VERSION >= NTDDI_WIN8) +// { + FileStorageInfo, + FileAlignmentInfo, + FileIdInfo, + FileIdExtdDirectoryInfo, + FileIdExtdDirectoryRestartInfo, +// } +// static if (NTDDI_VERSION >= NTDDI_WIN10_RS1) +// { + FileDispositionInfoEx, + FileRenameInfoEx, +// } +// static if (NTDDI_VERSION >= NTDDI_WIN10_19H1) +// { + FileCaseSensitiveInfo, + FileNormalizedNameInfo, +// } + MaximumFileInfoByHandleClass + } + alias PFILE_INFO_BY_HANDLE_CLASS = FILE_INFO_BY_HANDLE_CLASS*; +} + +struct SYSTEM_INFO { + union { + DWORD dwOemId; + struct { + WORD wProcessorArchitecture; + WORD wReserved; + } + } + DWORD dwPageSize; + PVOID lpMinimumApplicationAddress; + PVOID lpMaximumApplicationAddress; + DWORD_PTR dwActiveProcessorMask; + DWORD dwNumberOfProcessors; + DWORD dwProcessorType; + DWORD dwAllocationGranularity; + WORD wProcessorLevel; + WORD wProcessorRevision; +} +alias SYSTEM_INFO* LPSYSTEM_INFO; + +static if (_WIN32_WINNT >= 0x500) { + struct SYSTEM_POWER_STATUS { + BYTE ACLineStatus; + BYTE BatteryFlag; + BYTE BatteryLifePercent; + BYTE Reserved1; + DWORD BatteryLifeTime; + DWORD BatteryFullLifeTime; + } + alias SYSTEM_POWER_STATUS* LPSYSTEM_POWER_STATUS; +} + +struct TIME_ZONE_INFORMATION { + LONG Bias; + WCHAR[32] StandardName = 0; + SYSTEMTIME StandardDate; + LONG StandardBias; + WCHAR[32] DaylightName = 0; + SYSTEMTIME DaylightDate; + LONG DaylightBias; +} +alias TIME_ZONE_INFORMATION* LPTIME_ZONE_INFORMATION; + +// Does not exist in Windows headers, only MSDN +// documentation (for TIME_ZONE_INFORMATION). +// Provided solely for compatibility with the old +// urt.internal.sys.windows.windows +struct REG_TZI_FORMAT { + LONG Bias; + LONG StandardBias; + LONG DaylightBias; + SYSTEMTIME StandardDate; + SYSTEMTIME DaylightDate; +} + +// MSDN documents this, possibly erroneously, as Win2000+. +struct MEMORYSTATUS { + DWORD dwLength; + DWORD dwMemoryLoad; + SIZE_T dwTotalPhys; + SIZE_T dwAvailPhys; + SIZE_T dwTotalPageFile; + SIZE_T dwAvailPageFile; + SIZE_T dwTotalVirtual; + SIZE_T dwAvailVirtual; +} +alias MEMORYSTATUS* LPMEMORYSTATUS; + +static if (_WIN32_WINNT >= 0x500) { + struct MEMORYSTATUSEX { + DWORD dwLength; + DWORD dwMemoryLoad; + DWORDLONG ullTotalPhys; + DWORDLONG ullAvailPhys; + DWORDLONG ullTotalPageFile; + DWORDLONG ullAvailPageFile; + DWORDLONG ullTotalVirtual; + DWORDLONG ullAvailVirtual; + DWORDLONG ullAvailExtendedVirtual; + } + alias MEMORYSTATUSEX* LPMEMORYSTATUSEX; +} + +struct LDT_ENTRY { + WORD LimitLow; + WORD BaseLow; + struct { + BYTE BaseMid; + BYTE Flags1; + BYTE Flags2; + BYTE BaseHi; + + byte Type(byte f) { Flags1 = cast(BYTE) ((Flags1 & 0xE0) | f); return cast(byte)(f & 0x1F); } + byte Dpl(byte f) { Flags1 = cast(BYTE) ((Flags1 & 0x9F) | (f<<5)); return cast(byte)(f & 3); } + bool Pres(bool f) { Flags1 = cast(BYTE) ((Flags1 & 0x7F) | (f<<7)); return f; } + + byte LimitHi(byte f) { Flags2 = cast(BYTE) ((Flags2 & 0xF0) | (f&0x0F)); return cast(byte)(f & 0x0F); } + bool Sys(bool f) { Flags2 = cast(BYTE) ((Flags2 & 0xEF) | (f<<4)); return f; } + // Next bit is reserved + bool Default_Big(bool f) { Flags2 = cast(BYTE) ((Flags2 & 0xBF) | (f<<6)); return f; } + bool Granularity(bool f) { Flags2 = cast(BYTE) ((Flags2 & 0x7F) | (f<<7)); return f; } + + byte Type() { return cast(byte) (Flags1 & 0x1F); } + byte Dpl() { return cast(byte) ((Flags1 & 0x60)>>5); } + bool Pres() { return cast(bool) (Flags1 & 0x80); } + + byte LimitHi() { return cast(byte) (Flags2 & 0x0F); } + bool Sys() { return cast(bool) (Flags2 & 0x10); } + bool Default_Big()() { return cast(bool) (Flags2 & 0x40); } + bool Granularity()() { return cast(bool) (Flags2 & 0x80); } + } +/+ + union HighWord { + struct Bytes { + BYTE BaseMid; + BYTE Flags1; + BYTE Flags2; + BYTE BaseHi; + } + struct Bits { + DWORD BaseMid:8; + DWORD Type:5; + DWORD Dpl:2; + DWORD Pres:1; + DWORD LimitHi:4; + DWORD Sys:1; + DWORD Reserved_0:1; + DWORD Default_Big:1; + DWORD Granularity:1; + DWORD BaseHi:8; + } + } ++/ +} +alias LDT_ENTRY* PLDT_ENTRY, LPLDT_ENTRY; + +/* As with the other memory management functions and structures, MSDN's + * Windows version info shall be taken with a cup of salt. + */ +struct PROCESS_HEAP_ENTRY { + PVOID lpData; + DWORD cbData; + BYTE cbOverhead; + BYTE iRegionIndex; + WORD wFlags; + union { + struct _Block { + HANDLE hMem; + DWORD[3] dwReserved; + } + _Block Block; + struct _Region { + DWORD dwCommittedSize; + DWORD dwUnCommittedSize; + LPVOID lpFirstBlock; + LPVOID lpLastBlock; + } + _Region Region; + } +} +alias PROCESS_HEAP_ENTRY* LPPROCESS_HEAP_ENTRY; + +struct OFSTRUCT { + BYTE cBytes = OFSTRUCT.sizeof; + BYTE fFixedDisk; + WORD nErrCode; + WORD Reserved1; + WORD Reserved2; + CHAR[128] szPathName = 0; // const OFS_MAXPATHNAME = 128; +} +alias OFSTRUCT* LPOFSTRUCT, POFSTRUCT; + +/* ??? MSDN documents this only for Windows CE, but it's used by + * ImageGetCertificateData, which is in desktop Windows. + */ +struct WIN_CERTIFICATE { + DWORD dwLength; + WORD wRevision; + WORD wCertificateType; + BYTE _bCertificate; + + BYTE* bCertificate() return { return &_bCertificate; } +} +alias WIN_CERTIFICATE* LPWIN_CERTIFICATE; + +static if (_WIN32_WINNT >= 0x500) { + enum COMPUTER_NAME_FORMAT { + ComputerNameNetBIOS, + ComputerNameDnsHostname, + ComputerNameDnsDomain, + ComputerNameDnsFullyQualified, + ComputerNamePhysicalNetBIOS, + ComputerNamePhysicalDnsHostname, + ComputerNamePhysicalDnsDomain, + ComputerNamePhysicalDnsFullyQualified, + ComputerNameMax + } +} + +static if (_WIN32_WINNT >= 0x501) { + struct ACTCTXA { + ULONG cbSize = this.sizeof; + DWORD dwFlags; + LPCSTR lpSource; + USHORT wProcessorArchitecture; + LANGID wLangId; + LPCSTR lpAssemblyDirectory; + LPCSTR lpResourceName; + LPCSTR lpApplicationName; + HMODULE hModule; + } + alias ACTCTXA* PACTCTXA; + alias const(ACTCTXA)* PCACTCTXA; + + struct ACTCTXW { + ULONG cbSize = this.sizeof; + DWORD dwFlags; + LPCWSTR lpSource; + USHORT wProcessorArchitecture; + LANGID wLangId; + LPCWSTR lpAssemblyDirectory; + LPCWSTR lpResourceName; + LPCWSTR lpApplicationName; + HMODULE hModule; + } + alias ACTCTXW* PACTCTXW; + alias const(ACTCTXW)* PCACTCTXW; + + struct ACTCTX_SECTION_KEYED_DATA { + ULONG cbSize = this.sizeof; + ULONG ulDataFormatVersion; + PVOID lpData; + ULONG ulLength; + PVOID lpSectionGlobalData; + ULONG ulSectionGlobalDataLength; + PVOID lpSectionBase; + ULONG ulSectionTotalLength; + HANDLE hActCtx; + HANDLE ulAssemblyRosterIndex; + } + alias ACTCTX_SECTION_KEYED_DATA* PACTCTX_SECTION_KEYED_DATA; + alias const(ACTCTX_SECTION_KEYED_DATA)* PCACTCTX_SECTION_KEYED_DATA; + + enum MEMORY_RESOURCE_NOTIFICATION_TYPE { + LowMemoryResourceNotification, + HighMemoryResourceNotification + } + +} // (_WIN32_WINNT >= 0x501) + +static if (_WIN32_WINNT >= 0x410) { + /* apparently used only by SetThreadExecutionState (Win2000+) + * and DDK functions (version compatibility not established) + */ + alias DWORD EXECUTION_STATE; +} + +// CreateSymbolicLink, GetFileInformationByHandleEx +static if (_WIN32_WINNT >= 0x600) { + enum { + SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1, + SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2 + } + + struct FILE_BASIC_INFO + { + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + DWORD FileAttributes; + } + alias PFILE_BASIC_INFO = FILE_BASIC_INFO*; + + struct FILE_STANDARD_INFO + { + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + DWORD NumberOfLinks; + BOOLEAN DeletePending; + BOOLEAN Directory; + } + alias PFILE_STANDARD_INFO = FILE_STANDARD_INFO*; +} + +// Callbacks +extern (Windows) { + alias DWORD function(LPVOID) LPTHREAD_START_ROUTINE; + alias DWORD function(LARGE_INTEGER, LARGE_INTEGER, LARGE_INTEGER, LARGE_INTEGER, + DWORD, DWORD, HANDLE, HANDLE, LPVOID) LPPROGRESS_ROUTINE; + alias void function(PVOID) LPFIBER_START_ROUTINE; + + alias BOOL function(HMODULE, LPCSTR, LPCSTR, WORD, LONG_PTR) ENUMRESLANGPROCA; + alias BOOL function(HMODULE, LPCWSTR, LPCWSTR, WORD, LONG_PTR) ENUMRESLANGPROCW; + alias BOOL function(HMODULE, LPCSTR, LPSTR, LONG_PTR) ENUMRESNAMEPROCA; + alias BOOL function(HMODULE, LPCWSTR, LPWSTR, LONG_PTR) ENUMRESNAMEPROCW; + alias BOOL function(HMODULE, LPSTR, LONG_PTR) ENUMRESTYPEPROCA; + alias BOOL function(HMODULE, LPWSTR, LONG_PTR) ENUMRESTYPEPROCW; + alias void function(DWORD, DWORD, LPOVERLAPPED) LPOVERLAPPED_COMPLETION_ROUTINE; + alias LONG function(LPEXCEPTION_POINTERS) PTOP_LEVEL_EXCEPTION_FILTER; + alias PTOP_LEVEL_EXCEPTION_FILTER LPTOP_LEVEL_EXCEPTION_FILTER; + + alias void function(ULONG_PTR) PAPCFUNC; + alias void function(PVOID, DWORD, DWORD) PTIMERAPCROUTINE; + + static if (_WIN32_WINNT >= 0x500) { + alias void function(PVOID, BOOLEAN) WAITORTIMERCALLBACK; + } +} + +LPTSTR MAKEINTATOM()(ushort i) { + return cast(LPTSTR) cast(size_t) i; +} + +extern (Windows) nothrow @nogc { + // The following Win16 functions are obselete in Win32. + int _hread(HFILE, LPVOID, int); + int _hwrite(HFILE, LPCSTR, int); + HFILE _lclose(HFILE); + HFILE _lcreat(LPCSTR, int); + LONG _llseek(HFILE, LONG, int); + HFILE _lopen(LPCSTR, int); + UINT _lread(HFILE, LPVOID, UINT); + UINT _lwrite(HFILE, LPCSTR, UINT); + SIZE_T GlobalCompact(DWORD); + VOID GlobalFix(HGLOBAL); + + // MSDN contradicts itself on GlobalFlags: + // "This function is provided only for compatibility with 16-bit versions of Windows." + // but also requires Windows 2000 or above + UINT GlobalFlags(HGLOBAL); + VOID GlobalUnfix(HGLOBAL); + BOOL GlobalUnWire(HGLOBAL); + PVOID GlobalWire(HGLOBAL); + SIZE_T LocalCompact(UINT); + UINT LocalFlags(HLOCAL); + SIZE_T LocalShrink(HLOCAL, UINT); + + /+ + //-------------------------------------- + // These functions are problematic + + version (UseNtoSKernel) {}else { + /* CAREFUL: These are exported from ntoskrnl.exe and declared in winddk.h + as __fastcall functions, but are exported from kernel32.dll as __stdcall */ + static if (_WIN32_WINNT >= 0x501) { + VOID InitializeSListHead(PSLIST_HEADER); + } + LONG InterlockedCompareExchange(LPLONG, LONG, LONG); + // PVOID WINAPI InterlockedCompareExchangePointer(PVOID*, PVOID, PVOID); + (PVOID)InterlockedCompareExchange((LPLONG)(d) (PVOID)InterlockedCompareExchange((LPLONG)(d), (LONG)(e), (LONG)(c)) + LONG InterlockedDecrement(LPLONG); + LONG InterlockedExchange(LPLONG, LONG); + // PVOID WINAPI InterlockedExchangePointer(PVOID*, PVOID); + (PVOID)InterlockedExchange((LPLONG)((PVOID)InterlockedExchange((LPLONG)(t), (LONG)(v)) + LONG InterlockedExchangeAdd(LPLONG, LONG); + + static if (_WIN32_WINNT >= 0x501) { + PSLIST_ENTRY InterlockedFlushSList(PSLIST_HEADER); + } + LONG InterlockedIncrement(LPLONG); + static if (_WIN32_WINNT >= 0x501) { + PSLIST_ENTRY InterlockedPopEntrySList(PSLIST_HEADER); + PSLIST_ENTRY InterlockedPushEntrySList(PSLIST_HEADER, PSLIST_ENTRY); + } + } // #endif // __USE_NTOSKRNL__ + //-------------------------------------- + +/ + + LONG InterlockedIncrement(LPLONG lpAddend); + LONG InterlockedDecrement(LPLONG lpAddend); + LONG InterlockedExchange(LPLONG Target, LONG Value); + LONG InterlockedExchangeAdd(LPLONG Addend, LONG Value); + LONG InterlockedCompareExchange(LONG *Destination, LONG Exchange, LONG Comperand); + + ATOM AddAtomA(LPCSTR); + ATOM AddAtomW(LPCWSTR); + BOOL AreFileApisANSI(); + BOOL Beep(DWORD, DWORD); + HANDLE BeginUpdateResourceA(LPCSTR, BOOL); + HANDLE BeginUpdateResourceW(LPCWSTR, BOOL); + BOOL BuildCommDCBA(LPCSTR, LPDCB); + BOOL BuildCommDCBW(LPCWSTR, LPDCB); + BOOL BuildCommDCBAndTimeoutsA(LPCSTR, LPDCB, LPCOMMTIMEOUTS); + BOOL BuildCommDCBAndTimeoutsW(LPCWSTR, LPDCB, LPCOMMTIMEOUTS); + BOOL CallNamedPipeA(LPCSTR, PVOID, DWORD, PVOID, DWORD, PDWORD, DWORD); + BOOL CallNamedPipeW(LPCWSTR, PVOID, DWORD, PVOID, DWORD, PDWORD, DWORD); + BOOL CancelDeviceWakeupRequest(HANDLE); + BOOL CheckTokenMembership(HANDLE, PSID, PBOOL); + BOOL ClearCommBreak(HANDLE); + BOOL ClearCommError(HANDLE, PDWORD, LPCOMSTAT); + BOOL CloseHandle(HANDLE) @trusted; + BOOL CommConfigDialogA(LPCSTR, HWND, LPCOMMCONFIG); + BOOL CommConfigDialogW(LPCWSTR, HWND, LPCOMMCONFIG); + LONG CompareFileTime(const(FILETIME)*, const(FILETIME)*); + BOOL ContinueDebugEvent(DWORD, DWORD, DWORD); + BOOL CopyFileA(LPCSTR, LPCSTR, BOOL); + BOOL CopyFileW(LPCWSTR, LPCWSTR, BOOL); + BOOL CopyFileExA(LPCSTR, LPCSTR, LPPROGRESS_ROUTINE, LPVOID, LPBOOL, DWORD); + BOOL CopyFileExW(LPCWSTR, LPCWSTR, LPPROGRESS_ROUTINE, LPVOID, LPBOOL, DWORD); + + alias RtlMoveMemory = memmove; + alias RtlCopyMemory = memcpy; + pragma(inline, true) void RtlFillMemory(PVOID Destination, SIZE_T Length, BYTE Fill) pure { memset(Destination, Fill, Length); } + pragma(inline, true) void RtlZeroMemory(PVOID Destination, SIZE_T Length) pure { memset(Destination, 0, Length); } + alias MoveMemory = RtlMoveMemory; + alias CopyMemory = RtlCopyMemory; + alias FillMemory = RtlFillMemory; + alias ZeroMemory = RtlZeroMemory; + + BOOL CreateDirectoryA(LPCSTR, LPSECURITY_ATTRIBUTES); + BOOL CreateDirectoryW(LPCWSTR, LPSECURITY_ATTRIBUTES); + BOOL CreateDirectoryExA(LPCSTR, LPCSTR, LPSECURITY_ATTRIBUTES); + BOOL CreateDirectoryExW(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); + HANDLE CreateEventA(LPSECURITY_ATTRIBUTES, BOOL, BOOL, LPCSTR); + HANDLE CreateEventW(LPSECURITY_ATTRIBUTES, BOOL, BOOL, LPCWSTR); + HANDLE CreateFileA(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE); + HANDLE CreateFileW(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE); + HANDLE CreateIoCompletionPort(HANDLE, HANDLE, ULONG_PTR, DWORD); + HANDLE CreateMailslotA(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES); + HANDLE CreateMailslotW(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES); + HANDLE CreateMutexA(LPSECURITY_ATTRIBUTES, BOOL, LPCSTR); + HANDLE CreateMutexW(LPSECURITY_ATTRIBUTES, BOOL, LPCWSTR); + BOOL CreatePipe(PHANDLE, PHANDLE, LPSECURITY_ATTRIBUTES, DWORD); + BOOL CreateProcessA(LPCSTR, LPSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, PVOID, LPCSTR, LPSTARTUPINFOA, LPPROCESS_INFORMATION); + BOOL CreateProcessW(LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, PVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION); + HANDLE CreateSemaphoreA(LPSECURITY_ATTRIBUTES, LONG, LONG, LPCSTR) @trusted; + HANDLE CreateSemaphoreW(LPSECURITY_ATTRIBUTES, LONG, LONG, LPCWSTR) @trusted; + HANDLE CreateThread(LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, PVOID, DWORD, PDWORD); + BOOL DebugActiveProcess(DWORD); + void DebugBreak(); + ATOM DeleteAtom(ATOM); + void DeleteCriticalSection(PCRITICAL_SECTION); + BOOL DeleteFileA(LPCSTR); + BOOL DeleteFileW(LPCWSTR); + BOOL DisableThreadLibraryCalls(HMODULE); + BOOL DosDateTimeToFileTime(WORD, WORD, LPFILETIME); + BOOL DuplicateHandle(HANDLE, HANDLE, HANDLE, PHANDLE, DWORD, BOOL, DWORD); + BOOL EndUpdateResourceA(HANDLE, BOOL); + BOOL EndUpdateResourceW(HANDLE, BOOL); + void EnterCriticalSection(LPCRITICAL_SECTION); + void EnterCriticalSection(shared(CRITICAL_SECTION)*); + BOOL EnumResourceLanguagesA(HMODULE, LPCSTR, LPCSTR, ENUMRESLANGPROC, LONG_PTR); + BOOL EnumResourceLanguagesW(HMODULE, LPCWSTR, LPCWSTR, ENUMRESLANGPROC, LONG_PTR); + BOOL EnumResourceNamesA(HMODULE, LPCSTR, ENUMRESNAMEPROC, LONG_PTR); + BOOL EnumResourceNamesW(HMODULE, LPCWSTR, ENUMRESNAMEPROC, LONG_PTR); + BOOL EnumResourceTypesA(HMODULE, ENUMRESTYPEPROC, LONG_PTR); + BOOL EnumResourceTypesW(HMODULE, ENUMRESTYPEPROC, LONG_PTR); + BOOL EscapeCommFunction(HANDLE, DWORD); + void ExitProcess(UINT); // Never returns + void ExitThread(DWORD); // Never returns + DWORD ExpandEnvironmentStringsA(LPCSTR, LPSTR, DWORD); + DWORD ExpandEnvironmentStringsW(LPCWSTR, LPWSTR, DWORD); + void FatalAppExitA(UINT, LPCSTR); + void FatalAppExitW(UINT, LPCWSTR); + void FatalExit(int); + BOOL FileTimeToDosDateTime(const(FILETIME)*, LPWORD, LPWORD); + BOOL FileTimeToLocalFileTime(const(FILETIME)*, LPFILETIME); + BOOL FileTimeToSystemTime(const(FILETIME)*, LPSYSTEMTIME); + ATOM FindAtomA(LPCSTR); + ATOM FindAtomW(LPCWSTR); + BOOL FindClose(HANDLE); + BOOL FindCloseChangeNotification(HANDLE); + HANDLE FindFirstChangeNotificationA(LPCSTR, BOOL, DWORD); + HANDLE FindFirstChangeNotificationW(LPCWSTR, BOOL, DWORD); + HANDLE FindFirstFileA(LPCSTR, LPWIN32_FIND_DATAA); + HANDLE FindFirstFileW(LPCWSTR, LPWIN32_FIND_DATAW); + BOOL FindNextChangeNotification(HANDLE); + BOOL FindNextFileA(HANDLE, LPWIN32_FIND_DATAA); + BOOL FindNextFileW(HANDLE, LPWIN32_FIND_DATAW); + HRSRC FindResourceA(HMODULE, LPCSTR, LPCSTR); + HRSRC FindResourceW(HINSTANCE, LPCWSTR, LPCWSTR); + HRSRC FindResourceExA(HINSTANCE, LPCSTR, LPCSTR, WORD); + HRSRC FindResourceExW(HINSTANCE, LPCWSTR, LPCWSTR, WORD); + BOOL FlushFileBuffers(HANDLE); + BOOL FlushInstructionCache(HANDLE, PCVOID, SIZE_T); + DWORD FormatMessageA(DWORD, PCVOID, DWORD, DWORD, LPSTR, DWORD, va_list*); + DWORD FormatMessageW(DWORD, PCVOID, DWORD, DWORD, LPWSTR, DWORD, va_list*); + BOOL FreeEnvironmentStringsA(LPSTR); + BOOL FreeEnvironmentStringsW(LPWSTR); + BOOL FreeLibrary(HMODULE); + void FreeLibraryAndExitThread(HMODULE, DWORD); // never returns + BOOL FreeResource(HGLOBAL); + UINT GetAtomNameA(ATOM, LPSTR, int); + UINT GetAtomNameW(ATOM, LPWSTR, int); + LPSTR GetCommandLineA(); + LPWSTR GetCommandLineW(); + BOOL GetCommConfig(HANDLE, LPCOMMCONFIG, PDWORD); + BOOL GetCommMask(HANDLE, PDWORD); + BOOL GetCommModemStatus(HANDLE, PDWORD); + BOOL GetCommProperties(HANDLE, LPCOMMPROP); + BOOL GetCommState(HANDLE, LPDCB); + BOOL GetCommTimeouts(HANDLE, LPCOMMTIMEOUTS); + BOOL GetComputerNameA(LPSTR, PDWORD); + BOOL GetComputerNameW(LPWSTR, PDWORD); + DWORD GetCurrentDirectoryA(DWORD, LPSTR); + DWORD GetCurrentDirectoryW(DWORD, LPWSTR); + HANDLE GetCurrentProcess(); + DWORD GetCurrentProcessId(); + HANDLE GetCurrentThread(); +/* In MinGW: +#ifdef _WIN32_WCE +extern DWORD GetCurrentThreadId(void); +#else +WINBASEAPI DWORD WINAPI GetCurrentThreadId(void); +#endif +*/ + DWORD GetCurrentThreadId(); + + alias GetTickCount GetCurrentTime; + + BOOL GetDefaultCommConfigA(LPCSTR, LPCOMMCONFIG, PDWORD); + BOOL GetDefaultCommConfigW(LPCWSTR, LPCOMMCONFIG, PDWORD); + BOOL GetDiskFreeSpaceA(LPCSTR, PDWORD, PDWORD, PDWORD, PDWORD); + BOOL GetDiskFreeSpaceW(LPCWSTR, PDWORD, PDWORD, PDWORD, PDWORD); + BOOL GetDiskFreeSpaceExA(LPCSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER); + BOOL GetDiskFreeSpaceExW(LPCWSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER); + UINT GetDriveTypeA(LPCSTR); + UINT GetDriveTypeW(LPCWSTR); + LPSTR GetEnvironmentStringsA(); + LPWSTR GetEnvironmentStringsW(); + DWORD GetEnvironmentVariableA(LPCSTR, LPSTR, DWORD); + DWORD GetEnvironmentVariableW(LPCWSTR, LPWSTR, DWORD); + BOOL GetExitCodeProcess(HANDLE, PDWORD); + BOOL GetExitCodeThread(HANDLE, PDWORD); + DWORD GetFileAttributesA(LPCSTR); + DWORD GetFileAttributesW(LPCWSTR); + BOOL GetFileInformationByHandle(HANDLE, LPBY_HANDLE_FILE_INFORMATION); + DWORD GetFileSize(HANDLE, PDWORD); + BOOL GetFileTime(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME); + DWORD GetFileType(HANDLE); + DWORD GetFinalPathNameByHandleA(HANDLE, LPSTR, DWORD, DWORD); + DWORD GetFinalPathNameByHandleW(HANDLE, LPWSTR, DWORD, DWORD); + DWORD GetFullPathNameA(LPCSTR, DWORD, LPSTR, LPSTR*); + DWORD GetFullPathNameW(LPCWSTR, DWORD, LPWSTR, LPWSTR*); + DWORD GetLastError() @trusted; + void GetLocalTime(LPSYSTEMTIME); + DWORD GetLogicalDrives(); + DWORD GetLogicalDriveStringsA(DWORD, LPSTR); + DWORD GetLogicalDriveStringsW(DWORD, LPWSTR); + BOOL GetMailslotInfo(HANDLE, PDWORD, PDWORD, PDWORD, PDWORD); + DWORD GetModuleFileNameA(HINSTANCE, LPSTR, DWORD); + DWORD GetModuleFileNameW(HINSTANCE, LPWSTR, DWORD); + HMODULE GetModuleHandleA(LPCSTR); + HMODULE GetModuleHandleW(LPCWSTR); + BOOL GetNamedPipeHandleStateA(HANDLE, PDWORD, PDWORD, PDWORD, PDWORD, LPSTR, DWORD); + BOOL GetNamedPipeHandleStateW(HANDLE, PDWORD, PDWORD, PDWORD, PDWORD, LPWSTR, DWORD); + BOOL GetNamedPipeInfo(HANDLE, PDWORD, PDWORD, PDWORD, PDWORD); + BOOL GetOverlappedResult(HANDLE, LPOVERLAPPED, PDWORD, BOOL); + DWORD GetPriorityClass(HANDLE); + UINT GetPrivateProfileIntA(LPCSTR, LPCSTR, INT, LPCSTR); + UINT GetPrivateProfileIntW(LPCWSTR, LPCWSTR, INT, LPCWSTR); + DWORD GetPrivateProfileSectionA(LPCSTR, LPSTR, DWORD, LPCSTR); + DWORD GetPrivateProfileSectionW(LPCWSTR, LPWSTR, DWORD, LPCWSTR); + DWORD GetPrivateProfileSectionNamesA(LPSTR, DWORD, LPCSTR); + DWORD GetPrivateProfileSectionNamesW(LPWSTR, DWORD, LPCWSTR); + DWORD GetPrivateProfileStringA(LPCSTR, LPCSTR, LPCSTR, LPSTR, DWORD, LPCSTR); + DWORD GetPrivateProfileStringW(LPCWSTR, LPCWSTR, LPCWSTR, LPWSTR, DWORD, LPCWSTR); + BOOL GetPrivateProfileStructA(LPCSTR, LPCSTR, LPVOID, UINT, LPCSTR); + BOOL GetPrivateProfileStructW(LPCWSTR, LPCWSTR, LPVOID, UINT, LPCWSTR); + FARPROC GetProcAddress(HMODULE, LPCSTR); // 1st param wrongly HINSTANCE in MinGW + BOOL GetProcessAffinityMask(HANDLE, PDWORD_PTR, PDWORD_PTR); + DWORD GetProcessVersion(DWORD); + UINT GetProfileIntA(LPCSTR, LPCSTR, INT); + UINT GetProfileIntW(LPCWSTR, LPCWSTR, INT); + DWORD GetProfileSectionA(LPCSTR, LPSTR, DWORD); + DWORD GetProfileSectionW(LPCWSTR, LPWSTR, DWORD); + DWORD GetProfileStringA(LPCSTR, LPCSTR, LPCSTR, LPSTR, DWORD); + DWORD GetProfileStringW(LPCWSTR, LPCWSTR, LPCWSTR, LPWSTR, DWORD); + DWORD GetShortPathNameA(LPCSTR, LPSTR, DWORD); + DWORD GetShortPathNameW(LPCWSTR, LPWSTR, DWORD); + VOID GetStartupInfoA(LPSTARTUPINFOA); + VOID GetStartupInfoW(LPSTARTUPINFOW); + HANDLE GetStdHandle(DWORD); + UINT GetSystemDirectoryA(LPSTR, UINT); + UINT GetSystemDirectoryW(LPWSTR, UINT); + VOID GetSystemInfo(LPSYSTEM_INFO); + VOID GetSystemTime(LPSYSTEMTIME); + BOOL GetSystemTimeAdjustment(PDWORD, PDWORD, PBOOL); + void GetSystemTimeAsFileTime(LPFILETIME); + UINT GetTempFileNameA(LPCSTR, LPCSTR, UINT, LPSTR); + UINT GetTempFileNameW(LPCWSTR, LPCWSTR, UINT, LPWSTR); + DWORD GetTempPathA(DWORD, LPSTR); + DWORD GetTempPathW(DWORD, LPWSTR); + BOOL GetThreadContext(HANDLE, LPCONTEXT); + int GetThreadPriority(HANDLE); + BOOL GetThreadSelectorEntry(HANDLE, DWORD, LPLDT_ENTRY); + DWORD GetTickCount(); + DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION); + BOOL GetUserNameA (LPSTR, PDWORD); + BOOL GetUserNameW(LPWSTR, PDWORD); + DWORD GetVersion(); + BOOL GetVersionExA(LPOSVERSIONINFOA); + BOOL GetVersionExW(LPOSVERSIONINFOW); + BOOL GetVolumeInformationA(LPCSTR, LPSTR, DWORD, PDWORD, PDWORD, PDWORD, LPSTR, DWORD); + BOOL GetVolumeInformationW(LPCWSTR, LPWSTR, DWORD, PDWORD, PDWORD, PDWORD, LPWSTR, DWORD); + UINT GetWindowsDirectoryA(LPSTR, UINT); + UINT GetWindowsDirectoryW(LPWSTR, UINT); + DWORD GetWindowThreadProcessId(HWND, PDWORD); + ATOM GlobalAddAtomA(LPCSTR); + ATOM GlobalAddAtomW(LPCWSTR); + ATOM GlobalDeleteAtom(ATOM); + ATOM GlobalFindAtomA(LPCSTR); + ATOM GlobalFindAtomW(LPCWSTR); + UINT GlobalGetAtomNameA(ATOM, LPSTR, int); + UINT GlobalGetAtomNameW(ATOM, LPWSTR, int); + + bool HasOverlappedIoCompleted(LPOVERLAPPED lpOverlapped) { + return lpOverlapped.Internal != STATUS_PENDING; + } + + BOOL InitAtomTable(DWORD); + VOID InitializeCriticalSection(LPCRITICAL_SECTION) @trusted; + /* ??? The next two are allegedly obsolete and "supported only for + * backward compatibility with the 16-bit Windows API". Yet the + * replacements IsBadReadPtr and IsBadWritePtr are apparently Win2000+ + * only. Where's the mistake? + */ + BOOL IsBadHugeReadPtr(PCVOID, UINT_PTR); + BOOL IsBadHugeWritePtr(PVOID, UINT_PTR); + BOOL IsBadReadPtr(PCVOID, UINT_PTR); + BOOL IsBadStringPtrA(LPCSTR, UINT_PTR); + BOOL IsBadStringPtrW(LPCWSTR, UINT_PTR); + BOOL IsBadWritePtr(PVOID, UINT_PTR); + void LeaveCriticalSection(LPCRITICAL_SECTION); + void LeaveCriticalSection(shared(CRITICAL_SECTION)*); + HINSTANCE LoadLibraryA(LPCSTR); + HINSTANCE LoadLibraryW(LPCWSTR); + HINSTANCE LoadLibraryExA(LPCSTR, HANDLE, DWORD); + HINSTANCE LoadLibraryExW(LPCWSTR, HANDLE, DWORD); + DWORD LoadModule(LPCSTR, PVOID); + HGLOBAL LoadResource(HINSTANCE, HRSRC); + BOOL LocalFileTimeToFileTime(const(FILETIME)*, LPFILETIME); + BOOL LockFile(HANDLE, DWORD, DWORD, DWORD, DWORD); + PVOID LockResource(HGLOBAL); + + LPSTR lstrcatA(LPSTR, LPCSTR); + LPWSTR lstrcatW(LPWSTR, LPCWSTR); + int lstrcmpA(LPCSTR, LPCSTR); + int lstrcmpiA(LPCSTR, LPCSTR); + int lstrcmpiW(LPCWSTR, LPCWSTR); + int lstrcmpW(LPCWSTR, LPCWSTR); + LPSTR lstrcpyA(LPSTR, LPCSTR); + LPSTR lstrcpynA(LPSTR, LPCSTR, int); + LPWSTR lstrcpynW(LPWSTR, LPCWSTR, int); + LPWSTR lstrcpyW(LPWSTR, LPCWSTR); + int lstrlenA(LPCSTR); + int lstrlenW(LPCWSTR); + + BOOL MoveFileA(LPCSTR, LPCSTR); + BOOL MoveFileW(LPCWSTR, LPCWSTR); + int MulDiv(int, int, int); + HANDLE OpenEventA(DWORD, BOOL, LPCSTR); + HANDLE OpenEventW(DWORD, BOOL, LPCWSTR); + deprecated HFILE OpenFile(LPCSTR, LPOFSTRUCT, UINT); + HANDLE OpenMutexA(DWORD, BOOL, LPCSTR); + HANDLE OpenMutexW(DWORD, BOOL, LPCWSTR); + HANDLE OpenProcess(DWORD, BOOL, DWORD); + HANDLE OpenSemaphoreA(DWORD, BOOL, LPCSTR); + HANDLE OpenSemaphoreW(DWORD, BOOL, LPCWSTR); + void OutputDebugStringA(LPCSTR); + void OutputDebugStringW(LPCWSTR); + BOOL PeekNamedPipe(HANDLE, PVOID, DWORD, PDWORD, PDWORD, PDWORD); + BOOL PulseEvent(HANDLE); + BOOL PurgeComm(HANDLE, DWORD); + BOOL QueryPerformanceCounter(PLARGE_INTEGER); + BOOL QueryPerformanceFrequency(PLARGE_INTEGER); + DWORD QueueUserAPC(PAPCFUNC, HANDLE, ULONG_PTR); + void RaiseException(DWORD, DWORD, DWORD, const(ULONG_PTR)*); + BOOL ReadFile(HANDLE, PVOID, DWORD, PDWORD, LPOVERLAPPED); + BOOL ReadFileEx(HANDLE, PVOID, DWORD, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); + BOOL ReadProcessMemory(HANDLE, PCVOID, PVOID, SIZE_T, SIZE_T*); + BOOL ReleaseMutex(HANDLE); + BOOL ReleaseSemaphore(HANDLE, LONG, LPLONG); + BOOL RemoveDirectoryA(LPCSTR); + BOOL RemoveDirectoryW(LPCWSTR); +/* In MinGW: +#ifdef _WIN32_WCE +extern BOOL ResetEvent(HANDLE); +#else +WINBASEAPI BOOL WINAPI ResetEvent(HANDLE); +#endif +*/ + BOOL ResetEvent(HANDLE); + DWORD ResumeThread(HANDLE); + DWORD SearchPathA(LPCSTR, LPCSTR, LPCSTR, DWORD, LPSTR, LPSTR*); + DWORD SearchPathW(LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPWSTR, LPWSTR*); + BOOL SetCommBreak(HANDLE); + BOOL SetCommConfig(HANDLE, LPCOMMCONFIG, DWORD); + BOOL SetCommMask(HANDLE, DWORD); + BOOL SetCommState(HANDLE, LPDCB); + BOOL SetCommTimeouts(HANDLE, LPCOMMTIMEOUTS); + BOOL SetComputerNameA(LPCSTR); + BOOL SetComputerNameW(LPCWSTR); + BOOL SetCurrentDirectoryA(LPCSTR); + BOOL SetCurrentDirectoryW(LPCWSTR); + BOOL SetDefaultCommConfigA(LPCSTR, LPCOMMCONFIG, DWORD); + BOOL SetDefaultCommConfigW(LPCWSTR, LPCOMMCONFIG, DWORD); + BOOL SetEndOfFile(HANDLE); + BOOL SetEnvironmentVariableA(LPCSTR, LPCSTR); + BOOL SetEnvironmentVariableW(LPCWSTR, LPCWSTR); + UINT SetErrorMode(UINT); +/* In MinGW: +#ifdef _WIN32_WCE +extern BOOL SetEvent(HANDLE); +#else +WINBASEAPI BOOL WINAPI SetEvent(HANDLE); +#endif +*/ + BOOL SetEvent(HANDLE); + VOID SetFileApisToANSI(); + VOID SetFileApisToOEM(); + BOOL SetFileAttributesA(LPCSTR, DWORD); + BOOL SetFileAttributesW(LPCWSTR, DWORD); + DWORD SetFilePointer(HANDLE, LONG, PLONG, DWORD); + BOOL SetFileTime(HANDLE, const(FILETIME)*, const(FILETIME)*, const(FILETIME)*); + deprecated UINT SetHandleCount(UINT); + void SetLastError(DWORD); + void SetLastErrorEx(DWORD, DWORD); + BOOL SetLocalTime(const(SYSTEMTIME)*); + BOOL SetMailslotInfo(HANDLE, DWORD); + BOOL SetNamedPipeHandleState(HANDLE, PDWORD, PDWORD, PDWORD); + BOOL SetPriorityClass(HANDLE, DWORD); + BOOL SetStdHandle(DWORD, HANDLE); + BOOL SetSystemTime(const(SYSTEMTIME)*); + DWORD_PTR SetThreadAffinityMask(HANDLE, DWORD_PTR); + BOOL SetThreadContext(HANDLE, const(CONTEXT)*); + BOOL SetThreadPriority(HANDLE, int); + BOOL SetTimeZoneInformation(const(TIME_ZONE_INFORMATION)*); + LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER); + BOOL SetupComm(HANDLE, DWORD, DWORD); + BOOL SetVolumeLabelA(LPCSTR, LPCSTR); + BOOL SetVolumeLabelW(LPCWSTR, LPCWSTR); + + DWORD SizeofResource(HINSTANCE, HRSRC); + void Sleep(DWORD); + DWORD SleepEx(DWORD, BOOL); + DWORD SuspendThread(HANDLE); + BOOL SystemTimeToFileTime(const(SYSTEMTIME)*, LPFILETIME); + BOOL TerminateProcess(HANDLE, UINT); + BOOL TerminateThread(HANDLE, DWORD); + DWORD TlsAlloc(); + BOOL TlsFree(DWORD); + PVOID TlsGetValue(DWORD); + BOOL TlsSetValue(DWORD, PVOID); + BOOL TransactNamedPipe(HANDLE, PVOID, DWORD, PVOID, DWORD, PDWORD, LPOVERLAPPED); + BOOL TransmitCommChar(HANDLE, char); + LONG UnhandledExceptionFilter(LPEXCEPTION_POINTERS); + BOOL UnlockFile(HANDLE, DWORD, DWORD, DWORD, DWORD); + BOOL WaitCommEvent(HANDLE, PDWORD, LPOVERLAPPED); + BOOL WaitForDebugEvent(LPDEBUG_EVENT, DWORD); + DWORD WaitForMultipleObjects(DWORD, const(HANDLE)*, BOOL, DWORD); + DWORD WaitForMultipleObjectsEx(DWORD, const(HANDLE)*, BOOL, DWORD, BOOL); + DWORD WaitForSingleObject(HANDLE, DWORD); + DWORD WaitForSingleObjectEx(HANDLE, DWORD, BOOL); + BOOL WaitNamedPipeA(LPCSTR, DWORD); + BOOL WaitNamedPipeW(LPCWSTR, DWORD); + // undocumented on MSDN + BOOL WinLoadTrustProvider(GUID*); + BOOL WriteFile(HANDLE, PCVOID, DWORD, PDWORD, LPOVERLAPPED); + BOOL WriteFileEx(HANDLE, PCVOID, DWORD, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); + BOOL WritePrivateProfileSectionA(LPCSTR, LPCSTR, LPCSTR); + BOOL WritePrivateProfileSectionW(LPCWSTR, LPCWSTR, LPCWSTR); + BOOL WritePrivateProfileStringA(LPCSTR, LPCSTR, LPCSTR, LPCSTR); + BOOL WritePrivateProfileStringW(LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR); + BOOL WritePrivateProfileStructA(LPCSTR, LPCSTR, LPVOID, UINT, LPCSTR); + BOOL WritePrivateProfileStructW(LPCWSTR, LPCWSTR, LPVOID, UINT, LPCWSTR); + BOOL WriteProcessMemory(HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T*); + BOOL WriteProfileSectionA(LPCSTR, LPCSTR); + BOOL WriteProfileSectionW(LPCWSTR, LPCWSTR); + BOOL WriteProfileStringA(LPCSTR, LPCSTR, LPCSTR); + BOOL WriteProfileStringW(LPCWSTR, LPCWSTR, LPCWSTR); + + /* Memory allocation functions. + * MSDN documents these erroneously as Win2000+; thus it is uncertain what + * version compatibility they really have. + */ + HGLOBAL GlobalAlloc(UINT, SIZE_T); + HGLOBAL GlobalDiscard(HGLOBAL); + HGLOBAL GlobalFree(HGLOBAL); + HGLOBAL GlobalHandle(PCVOID); + LPVOID GlobalLock(HGLOBAL); + VOID GlobalMemoryStatus(LPMEMORYSTATUS); + HGLOBAL GlobalReAlloc(HGLOBAL, SIZE_T, UINT); + SIZE_T GlobalSize(HGLOBAL); + BOOL GlobalUnlock(HGLOBAL); + PVOID HeapAlloc(HANDLE, DWORD, SIZE_T); + SIZE_T HeapCompact(HANDLE, DWORD); + HANDLE HeapCreate(DWORD, SIZE_T, SIZE_T); + BOOL HeapDestroy(HANDLE); + BOOL HeapFree(HANDLE, DWORD, PVOID); + BOOL HeapLock(HANDLE); + PVOID HeapReAlloc(HANDLE, DWORD, PVOID, SIZE_T); + SIZE_T HeapSize(HANDLE, DWORD, PCVOID); + BOOL HeapUnlock(HANDLE); + BOOL HeapValidate(HANDLE, DWORD, PCVOID); + BOOL HeapWalk(HANDLE, LPPROCESS_HEAP_ENTRY); + HLOCAL LocalAlloc(UINT, SIZE_T); + HLOCAL LocalDiscard(HLOCAL); + HLOCAL LocalFree(HLOCAL); + HLOCAL LocalHandle(LPCVOID); + PVOID LocalLock(HLOCAL); + HLOCAL LocalReAlloc(HLOCAL, SIZE_T, UINT); + SIZE_T LocalSize(HLOCAL); + BOOL LocalUnlock(HLOCAL); + PVOID VirtualAlloc(PVOID, SIZE_T, DWORD, DWORD); + PVOID VirtualAllocEx(HANDLE, PVOID, SIZE_T, DWORD, DWORD); + BOOL VirtualFree(PVOID, SIZE_T, DWORD); + BOOL VirtualFreeEx(HANDLE, PVOID, SIZE_T, DWORD); + BOOL VirtualLock(PVOID, SIZE_T); + BOOL VirtualProtect(PVOID, SIZE_T, DWORD, PDWORD); + BOOL VirtualProtectEx(HANDLE, PVOID, SIZE_T, DWORD, PDWORD); + SIZE_T VirtualQuery(LPCVOID, PMEMORY_BASIC_INFORMATION, SIZE_T); + SIZE_T VirtualQueryEx(HANDLE, LPCVOID, PMEMORY_BASIC_INFORMATION, SIZE_T); + BOOL VirtualUnlock(PVOID, SIZE_T); +// not in MinGW 4.0 - ??? + static if (_WIN32_WINNT >= 0x600) { + BOOL CancelIoEx(HANDLE, LPOVERLAPPED); + } + + BOOL CancelIo(HANDLE); + BOOL CancelWaitableTimer(HANDLE); + PVOID ConvertThreadToFiber(PVOID); + LPVOID CreateFiber(SIZE_T, LPFIBER_START_ROUTINE, LPVOID); + HANDLE CreateWaitableTimerA(LPSECURITY_ATTRIBUTES, BOOL, LPCSTR); + HANDLE CreateWaitableTimerW(LPSECURITY_ATTRIBUTES, BOOL, LPCWSTR); + void DeleteFiber(PVOID); + BOOL GetFileAttributesExA(LPCSTR, GET_FILEEX_INFO_LEVELS, PVOID); + BOOL GetFileAttributesExW(LPCWSTR, GET_FILEEX_INFO_LEVELS, PVOID); + DWORD GetLongPathNameA(LPCSTR, LPSTR, DWORD); + DWORD GetLongPathNameW(LPCWSTR, LPWSTR, DWORD); + BOOL InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION, DWORD); + BOOL IsDebuggerPresent(); + HANDLE OpenWaitableTimerA(DWORD, BOOL, LPCSTR); + HANDLE OpenWaitableTimerW(DWORD, BOOL, LPCWSTR); + DWORD QueryDosDeviceA(LPCSTR, LPSTR, DWORD); + DWORD QueryDosDeviceW(LPCWSTR, LPWSTR, DWORD); + BOOL SetWaitableTimer(HANDLE, const(LARGE_INTEGER)*, LONG, PTIMERAPCROUTINE, PVOID, BOOL); + void SwitchToFiber(PVOID); + + static if (_WIN32_WINNT >= 0x500) { + HANDLE OpenThread(DWORD, BOOL, DWORD); + } + + BOOL AccessCheck(PSECURITY_DESCRIPTOR, HANDLE, DWORD, PGENERIC_MAPPING, PPRIVILEGE_SET, PDWORD, PDWORD, PBOOL); + BOOL AccessCheckAndAuditAlarmA(LPCSTR, LPVOID, LPSTR, LPSTR, PSECURITY_DESCRIPTOR, DWORD, PGENERIC_MAPPING, BOOL, PDWORD, PBOOL, PBOOL); + BOOL AccessCheckAndAuditAlarmW(LPCWSTR, LPVOID, LPWSTR, LPWSTR, PSECURITY_DESCRIPTOR, DWORD, PGENERIC_MAPPING, BOOL, PDWORD, PBOOL, PBOOL); + BOOL AddAccessAllowedAce(PACL, DWORD, DWORD, PSID); + BOOL AddAccessDeniedAce(PACL, DWORD, DWORD, PSID); + BOOL AddAce(PACL, DWORD, DWORD, PVOID, DWORD); + BOOL AddAuditAccessAce(PACL, DWORD, DWORD, PSID, BOOL, BOOL); + BOOL AdjustTokenGroups(HANDLE, BOOL, PTOKEN_GROUPS, DWORD, PTOKEN_GROUPS, PDWORD); + BOOL AdjustTokenPrivileges(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); + BOOL AllocateAndInitializeSid(PSID_IDENTIFIER_AUTHORITY, BYTE, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, PSID*); + BOOL AllocateLocallyUniqueId(PLUID); + BOOL AreAllAccessesGranted(DWORD, DWORD); + BOOL AreAnyAccessesGranted(DWORD, DWORD); + BOOL BackupEventLogA(HANDLE, LPCSTR); + BOOL BackupEventLogW(HANDLE, LPCWSTR); + BOOL BackupRead(HANDLE, LPBYTE, DWORD, LPDWORD, BOOL, BOOL, LPVOID*); + BOOL BackupSeek(HANDLE, DWORD, DWORD, LPDWORD, LPDWORD, LPVOID*); + BOOL BackupWrite(HANDLE, LPBYTE, DWORD, LPDWORD, BOOL, BOOL, LPVOID*); + BOOL ClearEventLogA(HANDLE, LPCSTR); + BOOL ClearEventLogW(HANDLE, LPCWSTR); + BOOL CloseEventLog(HANDLE); + BOOL ConnectNamedPipe(HANDLE, LPOVERLAPPED); + BOOL CopySid(DWORD, PSID, PSID); + HANDLE CreateNamedPipeA(LPCSTR, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, LPSECURITY_ATTRIBUTES); + HANDLE CreateNamedPipeW(LPCWSTR, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, LPSECURITY_ATTRIBUTES); + BOOL CreatePrivateObjectSecurity(PSECURITY_DESCRIPTOR, PSECURITY_DESCRIPTOR, PSECURITY_DESCRIPTOR*, BOOL, HANDLE, PGENERIC_MAPPING); + BOOL CreateProcessAsUserA(HANDLE, LPCSTR, LPSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, PVOID, LPCSTR, LPSTARTUPINFOA, LPPROCESS_INFORMATION); + BOOL CreateProcessAsUserW(HANDLE, LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, PVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION); + HANDLE CreateRemoteThread(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD); + DWORD CreateTapePartition(HANDLE, DWORD, DWORD, DWORD); + BOOL DefineDosDeviceA(DWORD, LPCSTR, LPCSTR); + BOOL DefineDosDeviceW(DWORD, LPCWSTR, LPCWSTR); + BOOL DeleteAce(PACL, DWORD); + BOOL DeregisterEventSource(HANDLE); + BOOL DestroyPrivateObjectSecurity(PSECURITY_DESCRIPTOR*); + BOOL DeviceIoControl(HANDLE, DWORD, PVOID, DWORD, PVOID, DWORD, PDWORD, POVERLAPPED); + BOOL DisconnectNamedPipe(HANDLE); + BOOL DuplicateToken(HANDLE, SECURITY_IMPERSONATION_LEVEL, PHANDLE); + BOOL DuplicateTokenEx(HANDLE, DWORD, LPSECURITY_ATTRIBUTES, SECURITY_IMPERSONATION_LEVEL, TOKEN_TYPE, PHANDLE); + BOOL EqualPrefixSid(PSID, PSID); + BOOL EqualSid(PSID, PSID); + DWORD EraseTape(HANDLE, DWORD, BOOL); + HANDLE FindFirstFileExA(LPCSTR, FINDEX_INFO_LEVELS, PVOID, FINDEX_SEARCH_OPS, PVOID, DWORD); + HANDLE FindFirstFileExW(LPCWSTR, FINDEX_INFO_LEVELS, PVOID, FINDEX_SEARCH_OPS, PVOID, DWORD); + BOOL FindFirstFreeAce(PACL, PVOID*); + PVOID FreeSid(PSID); + BOOL GetAce(PACL, DWORD, LPVOID*); + BOOL GetAclInformation(PACL, PVOID, DWORD, ACL_INFORMATION_CLASS); + BOOL GetBinaryTypeA(LPCSTR, PDWORD); + BOOL GetBinaryTypeW(LPCWSTR, PDWORD); + DWORD GetCompressedFileSizeA(LPCSTR, PDWORD); + DWORD GetCompressedFileSizeW(LPCWSTR, PDWORD); + BOOL GetCurrentHwProfileA(LPHW_PROFILE_INFOA); + BOOL GetCurrentHwProfileW(LPHW_PROFILE_INFOW); + BOOL GetFileSecurityA(LPCSTR, SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, DWORD, PDWORD); + BOOL GetFileSecurityW(LPCWSTR, SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, DWORD, PDWORD); + BOOL GetHandleInformation(HANDLE, PDWORD); + BOOL GetKernelObjectSecurity(HANDLE, SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, DWORD, PDWORD); + DWORD GetLengthSid(PSID); + BOOL GetNumberOfEventLogRecords(HANDLE, PDWORD); + BOOL GetOldestEventLogRecord(HANDLE, PDWORD); + BOOL GetPrivateObjectSecurity(PSECURITY_DESCRIPTOR, SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, DWORD, PDWORD); + BOOL GetProcessPriorityBoost(HANDLE, PBOOL); + BOOL GetProcessShutdownParameters(PDWORD, PDWORD); + BOOL GetProcessTimes(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME, LPFILETIME); + HWINSTA GetProcessWindowStation(); + BOOL GetProcessWorkingSetSize(HANDLE, PSIZE_T, PSIZE_T); + BOOL GetQueuedCompletionStatus(HANDLE, PDWORD, PULONG_PTR, LPOVERLAPPED*, DWORD); + BOOL GetSecurityDescriptorControl(PSECURITY_DESCRIPTOR, PSECURITY_DESCRIPTOR_CONTROL, PDWORD); + BOOL GetSecurityDescriptorDacl(PSECURITY_DESCRIPTOR, LPBOOL, PACL*, LPBOOL); + BOOL GetSecurityDescriptorGroup(PSECURITY_DESCRIPTOR, PSID*, LPBOOL); + DWORD GetSecurityDescriptorLength(PSECURITY_DESCRIPTOR); + BOOL GetSecurityDescriptorOwner(PSECURITY_DESCRIPTOR, PSID*, LPBOOL); + BOOL GetSecurityDescriptorSacl(PSECURITY_DESCRIPTOR, LPBOOL, PACL*, LPBOOL); + PSID_IDENTIFIER_AUTHORITY GetSidIdentifierAuthority(PSID); + DWORD GetSidLengthRequired(UCHAR); + PDWORD GetSidSubAuthority(PSID, DWORD); + PUCHAR GetSidSubAuthorityCount(PSID); + DWORD GetTapeParameters(HANDLE, DWORD, PDWORD, PVOID); + DWORD GetTapePosition(HANDLE, DWORD, PDWORD, PDWORD, PDWORD); + DWORD GetTapeStatus(HANDLE); + BOOL GetThreadPriorityBoost(HANDLE, PBOOL); + BOOL GetThreadTimes(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME, LPFILETIME); + BOOL GetTokenInformation(HANDLE, TOKEN_INFORMATION_CLASS, PVOID, DWORD, PDWORD); + BOOL ImpersonateLoggedOnUser(HANDLE); + BOOL ImpersonateNamedPipeClient(HANDLE); + BOOL ImpersonateSelf(SECURITY_IMPERSONATION_LEVEL); + BOOL InitializeAcl(PACL, DWORD, DWORD); + DWORD SetCriticalSectionSpinCount(LPCRITICAL_SECTION, DWORD); + BOOL InitializeSecurityDescriptor(PSECURITY_DESCRIPTOR, DWORD); + BOOL InitializeSid(PSID, PSID_IDENTIFIER_AUTHORITY, BYTE); + BOOL IsProcessorFeaturePresent(DWORD); + BOOL IsTextUnicode(PCVOID, int, LPINT); + BOOL IsValidAcl(PACL); + BOOL IsValidSecurityDescriptor(PSECURITY_DESCRIPTOR); + BOOL IsValidSid(PSID); + BOOL CreateWellKnownSid(WELL_KNOWN_SID_TYPE, PSID, PSID, PDWORD); + BOOL LockFileEx(HANDLE, DWORD, DWORD, DWORD, DWORD, LPOVERLAPPED); + BOOL LogonUserA(LPSTR, LPSTR, LPSTR, DWORD, DWORD, PHANDLE); + BOOL LogonUserW(LPWSTR, LPWSTR, LPWSTR, DWORD, DWORD, PHANDLE); + BOOL LookupAccountNameA(LPCSTR, LPCSTR, PSID, PDWORD, LPSTR, PDWORD, PSID_NAME_USE); + BOOL LookupAccountNameW(LPCWSTR, LPCWSTR, PSID, PDWORD, LPWSTR, PDWORD, PSID_NAME_USE); + BOOL LookupAccountSidA(LPCSTR, PSID, LPSTR, PDWORD, LPSTR, PDWORD, PSID_NAME_USE); + BOOL LookupAccountSidW(LPCWSTR, PSID, LPWSTR, PDWORD, LPWSTR, PDWORD, PSID_NAME_USE); + BOOL LookupPrivilegeDisplayNameA(LPCSTR, LPCSTR, LPSTR, PDWORD, PDWORD); + BOOL LookupPrivilegeDisplayNameW(LPCWSTR, LPCWSTR, LPWSTR, PDWORD, PDWORD); + BOOL LookupPrivilegeNameA(LPCSTR, PLUID, LPSTR, PDWORD); + BOOL LookupPrivilegeNameW(LPCWSTR, PLUID, LPWSTR, PDWORD); + BOOL LookupPrivilegeValueA(LPCSTR, LPCSTR, PLUID); + BOOL LookupPrivilegeValueW(LPCWSTR, LPCWSTR, PLUID); + BOOL MakeAbsoluteSD(PSECURITY_DESCRIPTOR, PSECURITY_DESCRIPTOR, PDWORD, PACL, PDWORD, PACL, PDWORD, PSID, PDWORD, PSID, PDWORD); + BOOL MakeSelfRelativeSD(PSECURITY_DESCRIPTOR, PSECURITY_DESCRIPTOR, PDWORD); + VOID MapGenericMask(PDWORD, PGENERIC_MAPPING); + BOOL MoveFileExA(LPCSTR, LPCSTR, DWORD); + BOOL MoveFileExW(LPCWSTR, LPCWSTR, DWORD); + BOOL NotifyChangeEventLog(HANDLE, HANDLE); + BOOL ObjectCloseAuditAlarmA(LPCSTR, PVOID, BOOL); + BOOL ObjectCloseAuditAlarmW(LPCWSTR, PVOID, BOOL); + BOOL ObjectDeleteAuditAlarmA(LPCSTR, PVOID, BOOL); + BOOL ObjectDeleteAuditAlarmW(LPCWSTR, PVOID, BOOL); + BOOL ObjectOpenAuditAlarmA(LPCSTR, PVOID, LPSTR, LPSTR, PSECURITY_DESCRIPTOR, HANDLE, DWORD, DWORD, PPRIVILEGE_SET, BOOL, BOOL, PBOOL); + BOOL ObjectOpenAuditAlarmW(LPCWSTR, PVOID, LPWSTR, LPWSTR, PSECURITY_DESCRIPTOR, HANDLE, DWORD, DWORD, PPRIVILEGE_SET, BOOL, BOOL, PBOOL); + BOOL ObjectPrivilegeAuditAlarmA(LPCSTR, PVOID, HANDLE, DWORD, PPRIVILEGE_SET, BOOL); + BOOL ObjectPrivilegeAuditAlarmW(LPCWSTR, PVOID, HANDLE, DWORD, PPRIVILEGE_SET, BOOL); + HANDLE OpenBackupEventLogA(LPCSTR, LPCSTR); + HANDLE OpenBackupEventLogW(LPCWSTR, LPCWSTR); + HANDLE OpenEventLogA(LPCSTR, LPCSTR); + HANDLE OpenEventLogW(LPCWSTR, LPCWSTR); + BOOL OpenProcessToken(HANDLE, DWORD, PHANDLE); + BOOL OpenThreadToken(HANDLE, DWORD, BOOL, PHANDLE); + BOOL PostQueuedCompletionStatus(HANDLE, DWORD, ULONG_PTR, LPOVERLAPPED); + DWORD PrepareTape(HANDLE, DWORD, BOOL); + BOOL PrivilegeCheck(HANDLE, PPRIVILEGE_SET, PBOOL); + BOOL PrivilegedServiceAuditAlarmA(LPCSTR, LPCSTR, HANDLE, PPRIVILEGE_SET, BOOL); + BOOL PrivilegedServiceAuditAlarmW(LPCWSTR, LPCWSTR, HANDLE, PPRIVILEGE_SET, BOOL); + BOOL ReadDirectoryChangesW(HANDLE, PVOID, DWORD, BOOL, DWORD, PDWORD, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); + BOOL ReadEventLogA(HANDLE, DWORD, DWORD, PVOID, DWORD, DWORD*, DWORD*); + BOOL ReadEventLogW(HANDLE, DWORD, DWORD, PVOID, DWORD, DWORD*, DWORD*); + BOOL ReadFileScatter(HANDLE, FILE_SEGMENT_ELEMENT*, DWORD, LPDWORD, LPOVERLAPPED); + HANDLE RegisterEventSourceA (LPCSTR, LPCSTR); + HANDLE RegisterEventSourceW(LPCWSTR, LPCWSTR); + BOOL ReportEventA(HANDLE, WORD, WORD, DWORD, PSID, WORD, DWORD, LPCSTR*, PVOID); + BOOL ReportEventW(HANDLE, WORD, WORD, DWORD, PSID, WORD, DWORD, LPCWSTR*, PVOID); + BOOL RevertToSelf(); + BOOL SetAclInformation(PACL, PVOID, DWORD, ACL_INFORMATION_CLASS); + BOOL SetFileSecurityA(LPCSTR, SECURITY_INFORMATION, PSECURITY_DESCRIPTOR); + BOOL SetFileSecurityW(LPCWSTR, SECURITY_INFORMATION, PSECURITY_DESCRIPTOR); + BOOL SetHandleInformation(HANDLE, DWORD, DWORD); + BOOL SetKernelObjectSecurity(HANDLE, SECURITY_INFORMATION, PSECURITY_DESCRIPTOR); + BOOL SetPrivateObjectSecurity(SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, PSECURITY_DESCRIPTOR*, PGENERIC_MAPPING, HANDLE); + BOOL SetProcessAffinityMask(HANDLE, DWORD_PTR); + BOOL SetProcessPriorityBoost(HANDLE, BOOL); + BOOL SetProcessShutdownParameters(DWORD, DWORD); + BOOL SetProcessWorkingSetSize(HANDLE, SIZE_T, SIZE_T); + BOOL SetSecurityDescriptorDacl(PSECURITY_DESCRIPTOR, BOOL, PACL, BOOL); + BOOL SetSecurityDescriptorGroup(PSECURITY_DESCRIPTOR, PSID, BOOL); + BOOL SetSecurityDescriptorOwner(PSECURITY_DESCRIPTOR, PSID, BOOL); + BOOL SetSecurityDescriptorSacl(PSECURITY_DESCRIPTOR, BOOL, PACL, BOOL); + BOOL SetSystemTimeAdjustment(DWORD, BOOL); + DWORD SetTapeParameters(HANDLE, DWORD, PVOID); + DWORD SetTapePosition(HANDLE, DWORD, DWORD, DWORD, DWORD, BOOL); + BOOL SetThreadPriorityBoost(HANDLE, BOOL); + BOOL SetThreadToken(PHANDLE, HANDLE); + BOOL SetTokenInformation(HANDLE, TOKEN_INFORMATION_CLASS, PVOID, DWORD); + DWORD SignalObjectAndWait(HANDLE, HANDLE, DWORD, BOOL); + BOOL SwitchToThread(); + BOOL SystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION, LPSYSTEMTIME, LPSYSTEMTIME); + BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION, LPSYSTEMTIME, LPSYSTEMTIME); + BOOL TryEnterCriticalSection(LPCRITICAL_SECTION); + BOOL TryEnterCriticalSection(shared(CRITICAL_SECTION)*); + BOOL UnlockFileEx(HANDLE, DWORD, DWORD, DWORD, LPOVERLAPPED); + BOOL UpdateResourceA(HANDLE, LPCSTR, LPCSTR, WORD, PVOID, DWORD); + BOOL UpdateResourceW(HANDLE, LPCWSTR, LPCWSTR, WORD, PVOID, DWORD); + BOOL WriteFileGather(HANDLE, FILE_SEGMENT_ELEMENT*, DWORD, LPDWORD, LPOVERLAPPED); + DWORD WriteTapemark(HANDLE, DWORD, DWORD, BOOL); + + static if (_WIN32_WINNT >= 0x500) { + BOOL AddAccessAllowedAceEx(PACL, DWORD, DWORD, DWORD, PSID); + BOOL AddAccessDeniedAceEx(PACL, DWORD, DWORD, DWORD, PSID); + PVOID AddVectoredExceptionHandler(ULONG, PVECTORED_EXCEPTION_HANDLER); + BOOL AllocateUserPhysicalPages(HANDLE, PULONG_PTR, PULONG_PTR); + BOOL AssignProcessToJobObject(HANDLE, HANDLE); + BOOL ChangeTimerQueueTimer(HANDLE,HANDLE,ULONG,ULONG); + LPVOID CreateFiberEx(SIZE_T, SIZE_T, DWORD, LPFIBER_START_ROUTINE, LPVOID); + HANDLE CreateFileMappingA(HANDLE, LPSECURITY_ATTRIBUTES, DWORD, DWORD, DWORD, LPCSTR); + HANDLE CreateFileMappingW(HANDLE, LPSECURITY_ATTRIBUTES, DWORD, DWORD, DWORD, LPCWSTR); + BOOL CreateHardLinkA(LPCSTR, LPCSTR, LPSECURITY_ATTRIBUTES); + BOOL CreateHardLinkW(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); + HANDLE CreateJobObjectA(LPSECURITY_ATTRIBUTES, LPCSTR); + HANDLE CreateJobObjectW(LPSECURITY_ATTRIBUTES, LPCWSTR); + BOOL CreateProcessWithLogonW(LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPCWSTR, LPWSTR, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION); + HANDLE CreateTimerQueue(); + BOOL CreateTimerQueueTimer(PHANDLE, HANDLE, WAITORTIMERCALLBACK, PVOID, DWORD, DWORD, ULONG); + BOOL DeleteTimerQueue(HANDLE); + BOOL DeleteTimerQueueEx(HANDLE, HANDLE); + BOOL DeleteTimerQueueTimer(HANDLE, HANDLE, HANDLE); + BOOL DeleteVolumeMountPointA(LPCSTR); + BOOL DeleteVolumeMountPointW(LPCWSTR); + BOOL DnsHostnameToComputerNameA(LPCSTR, LPSTR, LPDWORD); + BOOL DnsHostnameToComputerNameW(LPCWSTR, LPWSTR, LPDWORD); + BOOL EncryptFileA(LPCSTR); + BOOL EncryptFileW(LPCWSTR); + BOOL FileEncryptionStatusA(LPCSTR, LPDWORD); + BOOL FileEncryptionStatusW(LPCWSTR, LPDWORD); + HANDLE FindFirstVolumeA(LPCSTR, DWORD); + HANDLE FindFirstVolumeMountPointA(LPSTR, LPSTR, DWORD); + HANDLE FindFirstVolumeMountPointW(LPWSTR, LPWSTR, DWORD); + HANDLE FindFirstVolumeW(LPCWSTR, DWORD); + BOOL FindNextVolumeA(HANDLE, LPCSTR, DWORD); + BOOL FindNextVolumeW(HANDLE, LPWSTR, DWORD); + BOOL FindNextVolumeMountPointA(HANDLE, LPSTR, DWORD); + BOOL FindNextVolumeMountPointW(HANDLE, LPWSTR, DWORD); + BOOL FindVolumeClose(HANDLE); + BOOL FindVolumeMountPointClose(HANDLE); + BOOL FlushViewOfFile(PCVOID, SIZE_T); + BOOL FreeUserPhysicalPages(HANDLE, PULONG_PTR, PULONG_PTR); + BOOL GetComputerNameExA(COMPUTER_NAME_FORMAT, LPSTR, LPDWORD); + BOOL GetComputerNameExW(COMPUTER_NAME_FORMAT, LPWSTR, LPDWORD); + BOOL GetFileSizeEx(HANDLE, PLARGE_INTEGER); + BOOL GetModuleHandleExA(DWORD, LPCSTR, HMODULE*); + BOOL GetModuleHandleExW(DWORD, LPCWSTR, HMODULE*); + HANDLE GetProcessHeap(); + DWORD GetProcessHeaps(DWORD, PHANDLE); + BOOL GetProcessIoCounters(HANDLE, PIO_COUNTERS); + BOOL GetSystemPowerStatus(LPSYSTEM_POWER_STATUS); + UINT GetSystemWindowsDirectoryA(LPSTR, UINT); + UINT GetSystemWindowsDirectoryW(LPWSTR, UINT); + BOOL GetVolumeNameForVolumeMountPointA(LPCSTR, LPSTR, DWORD); + BOOL GetVolumeNameForVolumeMountPointW(LPCWSTR, LPWSTR, DWORD); + BOOL GetVolumePathNameA(LPCSTR, LPSTR, DWORD); + BOOL GetVolumePathNameW(LPCWSTR, LPWSTR, DWORD); + BOOL GlobalMemoryStatusEx(LPMEMORYSTATUSEX); + BOOL IsBadCodePtr(FARPROC); + BOOL IsSystemResumeAutomatic(); + BOOL MapUserPhysicalPages(PVOID, ULONG_PTR, PULONG_PTR); + BOOL MapUserPhysicalPagesScatter(PVOID*, ULONG_PTR, PULONG_PTR); + PVOID MapViewOfFile(HANDLE, DWORD, DWORD, DWORD, SIZE_T); + PVOID MapViewOfFileEx(HANDLE, DWORD, DWORD, DWORD, SIZE_T, PVOID); + HANDLE OpenFileMappingA(DWORD, BOOL, LPCSTR); + HANDLE OpenFileMappingW(DWORD, BOOL, LPCWSTR); + BOOL ProcessIdToSessionId(DWORD, DWORD*); + BOOL QueryInformationJobObject(HANDLE, JOBOBJECTINFOCLASS, LPVOID, DWORD, LPDWORD); + ULONG RemoveVectoredExceptionHandler(PVOID); + BOOL ReplaceFileA(LPCSTR, LPCSTR, LPCSTR, DWORD, LPVOID, LPVOID); + BOOL ReplaceFileW(LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPVOID, LPVOID); + BOOL SetComputerNameExA(COMPUTER_NAME_FORMAT, LPCSTR); + BOOL SetComputerNameExW(COMPUTER_NAME_FORMAT, LPCWSTR); + BOOL SetFilePointerEx(HANDLE, LARGE_INTEGER, PLARGE_INTEGER, DWORD); + BOOL SetInformationJobObject(HANDLE, JOBOBJECTINFOCLASS, LPVOID, DWORD); + BOOL SetSecurityDescriptorControl(PSECURITY_DESCRIPTOR, SECURITY_DESCRIPTOR_CONTROL, SECURITY_DESCRIPTOR_CONTROL); + BOOL SetSystemPowerState(BOOL, BOOL); + EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE); + DWORD SetThreadIdealProcessor(HANDLE, DWORD); + BOOL SetVolumeMountPointA(LPCSTR, LPCSTR); + BOOL SetVolumeMountPointW(LPCWSTR, LPCWSTR); + BOOL TerminateJobObject(HANDLE, UINT); + BOOL UnmapViewOfFile(PCVOID); + BOOL UnregisterWait(HANDLE); + BOOL UnregisterWaitEx(HANDLE, HANDLE); + BOOL VerifyVersionInfoA(LPOSVERSIONINFOEXA, DWORD, DWORDLONG); + BOOL VerifyVersionInfoW(LPOSVERSIONINFOEXW, DWORD, DWORDLONG); + } + + static if (_WIN32_WINNT >= 0x501) { + BOOL ActivateActCtx(HANDLE, ULONG_PTR*); + void AddRefActCtx(HANDLE); + BOOL CheckNameLegalDOS8Dot3A(LPCSTR, LPSTR, DWORD, PBOOL, PBOOL); + BOOL CheckNameLegalDOS8Dot3W(LPCWSTR, LPSTR, DWORD, PBOOL, PBOOL); + BOOL CheckRemoteDebuggerPresent(HANDLE, PBOOL); + BOOL ConvertFiberToThread(); + HANDLE CreateActCtxA(PCACTCTXA); + HANDLE CreateActCtxW(PCACTCTXW); + HANDLE CreateMemoryResourceNotification(MEMORY_RESOURCE_NOTIFICATION_TYPE); + BOOL DeactivateActCtx(DWORD, ULONG_PTR); + BOOL DebugActiveProcessStop(DWORD); + BOOL DebugBreakProcess(HANDLE); + BOOL DebugSetProcessKillOnExit(BOOL); + BOOL FindActCtxSectionGuid(DWORD, const(GUID)*, ULONG, const(GUID)*, + PACTCTX_SECTION_KEYED_DATA); + BOOL FindActCtxSectionStringA(DWORD, const(GUID)*, ULONG, LPCSTR, + PACTCTX_SECTION_KEYED_DATA); + BOOL FindActCtxSectionStringW(DWORD, const(GUID)*, ULONG, LPCWSTR, + PACTCTX_SECTION_KEYED_DATA); + BOOL GetCurrentActCtx(HANDLE*); + VOID GetNativeSystemInfo(LPSYSTEM_INFO); + BOOL GetProcessHandleCount(HANDLE, PDWORD); + BOOL GetSystemRegistryQuota(PDWORD, PDWORD); + BOOL GetSystemTimes(LPFILETIME, LPFILETIME, LPFILETIME); + UINT GetSystemWow64DirectoryA(LPSTR, UINT); + UINT GetSystemWow64DirectoryW(LPWSTR, UINT); + BOOL GetThreadIOPendingFlag(HANDLE, PBOOL); + BOOL GetVolumePathNamesForVolumeNameA(LPCSTR, LPSTR, DWORD, PDWORD); + BOOL GetVolumePathNamesForVolumeNameW(LPCWSTR, LPWSTR, DWORD, PDWORD); + UINT GetWriteWatch(DWORD, PVOID, SIZE_T, PVOID*, PULONG_PTR, PULONG); + BOOL HeapQueryInformation(HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T, PSIZE_T); + BOOL HeapSetInformation(HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T); + BOOL IsProcessInJob(HANDLE, HANDLE, PBOOL); + BOOL IsWow64Process(HANDLE, PBOOL); + BOOL QueryActCtxW(DWORD, HANDLE, PVOID, ULONG, PVOID, SIZE_T, SIZE_T*); + BOOL QueryMemoryResourceNotification(HANDLE, PBOOL); + void ReleaseActCtx(HANDLE); + UINT ResetWriteWatch(LPVOID, SIZE_T); + BOOL SetFileShortNameA(HANDLE, LPCSTR); + BOOL SetFileShortNameW(HANDLE, LPCWSTR); + BOOL SetFileValidData(HANDLE, LONGLONG); + BOOL ZombifyActCtx(HANDLE); + } + + static if (_WIN32_WINNT >= 0x502) { + DWORD GetFirmwareEnvironmentVariableA(LPCSTR, LPCSTR, PVOID, DWORD); + DWORD GetFirmwareEnvironmentVariableW(LPCWSTR, LPCWSTR, PVOID, DWORD); + DWORD GetDllDirectoryA(DWORD, LPSTR); + DWORD GetDllDirectoryW(DWORD, LPWSTR); + DWORD GetThreadId(HANDLE); + DWORD GetProcessId(HANDLE); + HANDLE ReOpenFile(HANDLE, DWORD, DWORD, DWORD); + BOOL SetDllDirectoryA(LPCSTR); + BOOL SetDllDirectoryW(LPCWSTR); + BOOL SetFirmwareEnvironmentVariableA(LPCSTR, LPCSTR, PVOID, DWORD); + BOOL SetFirmwareEnvironmentVariableW(LPCWSTR, LPCWSTR, PVOID, DWORD); + } + + // ??? + static if (_WIN32_WINNT >= 0x510) { + VOID RestoreLastError(DWORD); + } + + static if (_WIN32_WINNT >= 0x600) { + BOOL CreateSymbolicLinkA(LPCSTR, LPCSTR, DWORD); + BOOL CreateSymbolicLinkW(LPCWSTR, LPCWSTR, DWORD); + BOOL GetFileInformationByHandleEx(HANDLE, FILE_INFO_BY_HANDLE_CLASS, LPVOID, DWORD); + } +} + +// For compatibility with old urt.internal.sys.windows.windows: +version (LittleEndian) nothrow @nogc +{ + BOOL QueryPerformanceCounter()(long* lpPerformanceCount) { return QueryPerformanceCounter(cast(PLARGE_INTEGER)lpPerformanceCount); } + BOOL QueryPerformanceFrequency()(long* lpFrequency) { return QueryPerformanceFrequency(cast(PLARGE_INTEGER)lpFrequency); } +} + +mixin DECLARE_AW!("STARTUPINFO"); +version (Unicode) { + //alias STARTUPINFOW STARTUPINFO; + alias WIN32_FIND_DATAW WIN32_FIND_DATA; + alias ENUMRESLANGPROCW ENUMRESLANGPROC; + alias ENUMRESNAMEPROCW ENUMRESNAMEPROC; + alias ENUMRESTYPEPROCW ENUMRESTYPEPROC; + alias AddAtomW AddAtom; + alias BeginUpdateResourceW BeginUpdateResource; + alias BuildCommDCBW BuildCommDCB; + alias BuildCommDCBAndTimeoutsW BuildCommDCBAndTimeouts; + alias CallNamedPipeW CallNamedPipe; + alias CommConfigDialogW CommConfigDialog; + alias CopyFileW CopyFile; + alias CopyFileExW CopyFileEx; + alias CreateDirectoryW CreateDirectory; + alias CreateDirectoryExW CreateDirectoryEx; + alias CreateEventW CreateEvent; + alias CreateFileW CreateFile; + alias CreateMailslotW CreateMailslot; + alias CreateMutexW CreateMutex; + alias CreateProcessW CreateProcess; + alias CreateSemaphoreW CreateSemaphore; + alias DeleteFileW DeleteFile; + alias EndUpdateResourceW EndUpdateResource; + alias EnumResourceLanguagesW EnumResourceLanguages; + alias EnumResourceNamesW EnumResourceNames; + alias EnumResourceTypesW EnumResourceTypes; + alias ExpandEnvironmentStringsW ExpandEnvironmentStrings; + alias FatalAppExitW FatalAppExit; + alias FindAtomW FindAtom; + alias FindFirstChangeNotificationW FindFirstChangeNotification; + alias FindFirstFileW FindFirstFile; + alias FindNextFileW FindNextFile; + alias FindResourceW FindResource; + alias FindResourceExW FindResourceEx; + alias FormatMessageW FormatMessage; + alias FreeEnvironmentStringsW FreeEnvironmentStrings; + alias GetAtomNameW GetAtomName; + alias GetCommandLineW GetCommandLine; + alias GetComputerNameW GetComputerName; + alias GetCurrentDirectoryW GetCurrentDirectory; + alias GetDefaultCommConfigW GetDefaultCommConfig; + alias GetDiskFreeSpaceW GetDiskFreeSpace; + alias GetDiskFreeSpaceExW GetDiskFreeSpaceEx; + alias GetDriveTypeW GetDriveType; + alias GetEnvironmentStringsW GetEnvironmentStrings; + alias GetEnvironmentVariableW GetEnvironmentVariable; + alias GetFileAttributesW GetFileAttributes; + alias GetFullPathNameW GetFullPathName; + alias GetLogicalDriveStringsW GetLogicalDriveStrings; + alias GetModuleFileNameW GetModuleFileName; + alias GetModuleHandleW GetModuleHandle; + alias GetNamedPipeHandleStateW GetNamedPipeHandleState; + alias GetPrivateProfileIntW GetPrivateProfileInt; + alias GetPrivateProfileSectionW GetPrivateProfileSection; + alias GetPrivateProfileSectionNamesW GetPrivateProfileSectionNames; + alias GetPrivateProfileStringW GetPrivateProfileString; + alias GetPrivateProfileStructW GetPrivateProfileStruct; + alias GetProfileIntW GetProfileInt; + alias GetProfileSectionW GetProfileSection; + alias GetProfileStringW GetProfileString; + alias GetShortPathNameW GetShortPathName; + alias GetStartupInfoW GetStartupInfo; + alias GetSystemDirectoryW GetSystemDirectory; + alias GetTempFileNameW GetTempFileName; + alias GetTempPathW GetTempPath; + alias GetUserNameW GetUserName; + alias GetVersionExW GetVersionEx; + alias GetVolumeInformationW GetVolumeInformation; + alias GetWindowsDirectoryW GetWindowsDirectory; + alias GlobalAddAtomW GlobalAddAtom; + alias GlobalFindAtomW GlobalFindAtom; + alias GlobalGetAtomNameW GlobalGetAtomName; + alias IsBadStringPtrW IsBadStringPtr; + alias LoadLibraryW LoadLibrary; + alias LoadLibraryExW LoadLibraryEx; + alias lstrcatW lstrcat; + alias lstrcmpW lstrcmp; + alias lstrcmpiW lstrcmpi; + alias lstrcpyW lstrcpy; + alias lstrcpynW lstrcpyn; + alias lstrlenW lstrlen; + alias MoveFileW MoveFile; + alias OpenEventW OpenEvent; + alias OpenMutexW OpenMutex; + alias OpenSemaphoreW OpenSemaphore; + alias OutputDebugStringW OutputDebugString; + alias RemoveDirectoryW RemoveDirectory; + alias SearchPathW SearchPath; + alias SetComputerNameW SetComputerName; + alias SetCurrentDirectoryW SetCurrentDirectory; + alias SetDefaultCommConfigW SetDefaultCommConfig; + alias SetEnvironmentVariableW SetEnvironmentVariable; + alias SetFileAttributesW SetFileAttributes; + alias SetVolumeLabelW SetVolumeLabel; + alias WaitNamedPipeW WaitNamedPipe; + alias WritePrivateProfileSectionW WritePrivateProfileSection; + alias WritePrivateProfileStringW WritePrivateProfileString; + alias WritePrivateProfileStructW WritePrivateProfileStruct; + alias WriteProfileSectionW WriteProfileSection; + alias WriteProfileStringW WriteProfileString; + alias CreateWaitableTimerW CreateWaitableTimer; + alias GetFileAttributesExW GetFileAttributesEx; + alias GetLongPathNameW GetLongPathName; + alias QueryDosDeviceW QueryDosDevice; + + alias HW_PROFILE_INFOW HW_PROFILE_INFO; + alias AccessCheckAndAuditAlarmW AccessCheckAndAuditAlarm; + alias BackupEventLogW BackupEventLog; + alias ClearEventLogW ClearEventLog; + alias CreateNamedPipeW CreateNamedPipe; + alias CreateProcessAsUserW CreateProcessAsUser; + alias DefineDosDeviceW DefineDosDevice; + alias FindFirstFileExW FindFirstFileEx; + alias GetBinaryTypeW GetBinaryType; + alias GetCompressedFileSizeW GetCompressedFileSize; + alias GetFileSecurityW GetFileSecurity; + alias LogonUserW LogonUser; + alias LookupAccountNameW LookupAccountName; + alias LookupAccountSidW LookupAccountSid; + alias LookupPrivilegeDisplayNameW LookupPrivilegeDisplayName; + alias LookupPrivilegeNameW LookupPrivilegeName; + alias LookupPrivilegeValueW LookupPrivilegeValue; + alias MoveFileExW MoveFileEx; + alias ObjectCloseAuditAlarmW ObjectCloseAuditAlarm; + alias ObjectDeleteAuditAlarmW ObjectDeleteAuditAlarm; + alias ObjectOpenAuditAlarmW ObjectOpenAuditAlarm; + alias ObjectPrivilegeAuditAlarmW ObjectPrivilegeAuditAlarm; + alias OpenBackupEventLogW OpenBackupEventLog; + alias OpenEventLogW OpenEventLog; + alias PrivilegedServiceAuditAlarmW PrivilegedServiceAuditAlarm; + alias ReadEventLogW ReadEventLog; + alias RegisterEventSourceW RegisterEventSource; + alias ReportEventW ReportEvent; + alias SetFileSecurityW SetFileSecurity; + alias UpdateResourceW UpdateResource; + + static if (_WIN32_WINNT >= 0x500) { + alias CreateFileMappingW CreateFileMapping; + alias CreateHardLinkW CreateHardLink; + alias CreateJobObjectW CreateJobObject; + alias DeleteVolumeMountPointW DeleteVolumeMountPoint; + alias DnsHostnameToComputerNameW DnsHostnameToComputerName; + alias EncryptFileW EncryptFile; + alias FileEncryptionStatusW FileEncryptionStatus; + alias FindFirstVolumeW FindFirstVolume; + alias FindFirstVolumeMountPointW FindFirstVolumeMountPoint; + alias FindNextVolumeW FindNextVolume; + alias FindNextVolumeMountPointW FindNextVolumeMountPoint; + alias GetModuleHandleExW GetModuleHandleEx; + alias GetSystemWindowsDirectoryW GetSystemWindowsDirectory; + alias GetVolumeNameForVolumeMountPointW GetVolumeNameForVolumeMountPoint; + alias GetVolumePathNameW GetVolumePathName; + alias OpenFileMappingW OpenFileMapping; + alias ReplaceFileW ReplaceFile; + alias SetVolumeMountPointW SetVolumeMountPoint; + alias VerifyVersionInfoW VerifyVersionInfo; + } + + static if (_WIN32_WINNT >= 0x501) { + alias ACTCTXW ACTCTX; + alias CheckNameLegalDOS8Dot3W CheckNameLegalDOS8Dot3; + alias CreateActCtxW CreateActCtx; + alias FindActCtxSectionStringW FindActCtxSectionString; + alias GetSystemWow64DirectoryW GetSystemWow64Directory; + alias GetVolumePathNamesForVolumeNameW GetVolumePathNamesForVolumeName; + alias SetFileShortNameW SetFileShortName; + } + + static if (_WIN32_WINNT >= 0x502) { + alias SetFirmwareEnvironmentVariableW SetFirmwareEnvironmentVariable; + alias SetDllDirectoryW SetDllDirectory; + alias GetDllDirectoryW GetDllDirectory; + } + + static if (_WIN32_WINNT >= 0x600) { + alias CreateSymbolicLinkW CreateSymbolicLink; + } + +} else { + //alias STARTUPINFOA STARTUPINFO; + alias WIN32_FIND_DATAA WIN32_FIND_DATA; + alias ENUMRESLANGPROCW ENUMRESLANGPROC; + alias ENUMRESNAMEPROCW ENUMRESNAMEPROC; + alias ENUMRESTYPEPROCW ENUMRESTYPEPROC; + alias AddAtomA AddAtom; + alias BeginUpdateResourceA BeginUpdateResource; + alias BuildCommDCBA BuildCommDCB; + alias BuildCommDCBAndTimeoutsA BuildCommDCBAndTimeouts; + alias CallNamedPipeA CallNamedPipe; + alias CommConfigDialogA CommConfigDialog; + alias CopyFileA CopyFile; + alias CopyFileExA CopyFileEx; + alias CreateDirectoryA CreateDirectory; + alias CreateDirectoryExA CreateDirectoryEx; + alias CreateEventA CreateEvent; + alias CreateFileA CreateFile; + alias CreateMailslotA CreateMailslot; + alias CreateMutexA CreateMutex; + alias CreateProcessA CreateProcess; + alias CreateSemaphoreA CreateSemaphore; + alias DeleteFileA DeleteFile; + alias EndUpdateResourceA EndUpdateResource; + alias EnumResourceLanguagesA EnumResourceLanguages; + alias EnumResourceNamesA EnumResourceNames; + alias EnumResourceTypesA EnumResourceTypes; + alias ExpandEnvironmentStringsA ExpandEnvironmentStrings; + alias FatalAppExitA FatalAppExit; + alias FindAtomA FindAtom; + alias FindFirstChangeNotificationA FindFirstChangeNotification; + alias FindFirstFileA FindFirstFile; + alias FindNextFileA FindNextFile; + alias FindResourceA FindResource; + alias FindResourceExA FindResourceEx; + alias FormatMessageA FormatMessage; + alias FreeEnvironmentStringsA FreeEnvironmentStrings; + alias GetAtomNameA GetAtomName; + alias GetCommandLineA GetCommandLine; + alias GetComputerNameA GetComputerName; + alias GetCurrentDirectoryA GetCurrentDirectory; + alias GetDefaultCommConfigA GetDefaultCommConfig; + alias GetDiskFreeSpaceA GetDiskFreeSpace; + alias GetDiskFreeSpaceExA GetDiskFreeSpaceEx; + alias GetDriveTypeA GetDriveType; + alias GetEnvironmentStringsA GetEnvironmentStrings; + alias GetEnvironmentVariableA GetEnvironmentVariable; + alias GetFileAttributesA GetFileAttributes; + alias GetFullPathNameA GetFullPathName; + alias GetLogicalDriveStringsA GetLogicalDriveStrings; + alias GetNamedPipeHandleStateA GetNamedPipeHandleState; + alias GetModuleHandleA GetModuleHandle; + alias GetModuleFileNameA GetModuleFileName; + alias GetPrivateProfileIntA GetPrivateProfileInt; + alias GetPrivateProfileSectionA GetPrivateProfileSection; + alias GetPrivateProfileSectionNamesA GetPrivateProfileSectionNames; + alias GetPrivateProfileStringA GetPrivateProfileString; + alias GetPrivateProfileStructA GetPrivateProfileStruct; + alias GetProfileIntA GetProfileInt; + alias GetProfileSectionA GetProfileSection; + alias GetProfileStringA GetProfileString; + alias GetShortPathNameA GetShortPathName; + alias GetStartupInfoA GetStartupInfo; + alias GetSystemDirectoryA GetSystemDirectory; + alias GetTempFileNameA GetTempFileName; + alias GetTempPathA GetTempPath; + alias GetUserNameA GetUserName; + alias GetVersionExA GetVersionEx; + alias GetVolumeInformationA GetVolumeInformation; + alias GetWindowsDirectoryA GetWindowsDirectory; + alias GlobalAddAtomA GlobalAddAtom; + alias GlobalFindAtomA GlobalFindAtom; + alias GlobalGetAtomNameA GlobalGetAtomName; + alias IsBadStringPtrA IsBadStringPtr; + alias LoadLibraryA LoadLibrary; + alias LoadLibraryExA LoadLibraryEx; + alias lstrcatA lstrcat; + alias lstrcmpA lstrcmp; + alias lstrcmpiA lstrcmpi; + alias lstrcpyA lstrcpy; + alias lstrcpynA lstrcpyn; + alias lstrlenA lstrlen; + alias MoveFileA MoveFile; + alias OpenEventA OpenEvent; + alias OpenMutexA OpenMutex; + alias OpenSemaphoreA OpenSemaphore; + alias OutputDebugStringA OutputDebugString; + alias RemoveDirectoryA RemoveDirectory; + alias SearchPathA SearchPath; + alias SetComputerNameA SetComputerName; + alias SetCurrentDirectoryA SetCurrentDirectory; + alias SetDefaultCommConfigA SetDefaultCommConfig; + alias SetEnvironmentVariableA SetEnvironmentVariable; + alias SetFileAttributesA SetFileAttributes; + alias SetVolumeLabelA SetVolumeLabel; + alias WaitNamedPipeA WaitNamedPipe; + alias WritePrivateProfileSectionA WritePrivateProfileSection; + alias WritePrivateProfileStringA WritePrivateProfileString; + alias WritePrivateProfileStructA WritePrivateProfileStruct; + alias WriteProfileSectionA WriteProfileSection; + alias WriteProfileStringA WriteProfileString; + alias CreateWaitableTimerA CreateWaitableTimer; + alias GetFileAttributesExA GetFileAttributesEx; + alias GetLongPathNameA GetLongPathName; + alias QueryDosDeviceA QueryDosDevice; + + alias HW_PROFILE_INFOA HW_PROFILE_INFO; + alias AccessCheckAndAuditAlarmA AccessCheckAndAuditAlarm; + alias BackupEventLogA BackupEventLog; + alias ClearEventLogA ClearEventLog; + alias CreateNamedPipeA CreateNamedPipe; + alias CreateProcessAsUserA CreateProcessAsUser; + alias DefineDosDeviceA DefineDosDevice; + alias FindFirstFileExA FindFirstFileEx; + alias GetBinaryTypeA GetBinaryType; + alias GetCompressedFileSizeA GetCompressedFileSize; + alias GetFileSecurityA GetFileSecurity; + alias LogonUserA LogonUser; + alias LookupAccountNameA LookupAccountName; + alias LookupAccountSidA LookupAccountSid; + alias LookupPrivilegeDisplayNameA LookupPrivilegeDisplayName; + alias LookupPrivilegeNameA LookupPrivilegeName; + alias LookupPrivilegeValueA LookupPrivilegeValue; + alias MoveFileExA MoveFileEx; + alias ObjectCloseAuditAlarmA ObjectCloseAuditAlarm; + alias ObjectDeleteAuditAlarmA ObjectDeleteAuditAlarm; + alias ObjectOpenAuditAlarmA ObjectOpenAuditAlarm; + alias ObjectPrivilegeAuditAlarmA ObjectPrivilegeAuditAlarm; + alias OpenBackupEventLogA OpenBackupEventLog; + alias OpenEventLogA OpenEventLog; + alias PrivilegedServiceAuditAlarmA PrivilegedServiceAuditAlarm; + alias ReadEventLogA ReadEventLog; + alias RegisterEventSourceA RegisterEventSource; + alias ReportEventA ReportEvent; + alias SetFileSecurityA SetFileSecurity; + alias UpdateResourceA UpdateResource; + + static if (_WIN32_WINNT >= 0x500) { + alias CreateFileMappingA CreateFileMapping; + alias CreateHardLinkA CreateHardLink; + alias CreateJobObjectA CreateJobObject; + alias DeleteVolumeMountPointA DeleteVolumeMountPoint; + alias DnsHostnameToComputerNameA DnsHostnameToComputerName; + alias EncryptFileA EncryptFile; + alias FileEncryptionStatusA FileEncryptionStatus; + alias FindFirstVolumeA FindFirstVolume; + alias FindFirstVolumeMountPointA FindFirstVolumeMountPoint; + alias FindNextVolumeA FindNextVolume; + alias FindNextVolumeMountPointA FindNextVolumeMountPoint; + alias GetModuleHandleExA GetModuleHandleEx; + alias GetSystemWindowsDirectoryA GetSystemWindowsDirectory; + alias GetVolumeNameForVolumeMountPointA GetVolumeNameForVolumeMountPoint; + alias GetVolumePathNameA GetVolumePathName; + alias OpenFileMappingA OpenFileMapping; + alias ReplaceFileA ReplaceFile; + alias SetVolumeMountPointA SetVolumeMountPoint; + alias VerifyVersionInfoA VerifyVersionInfo; + } + + static if (_WIN32_WINNT >= 0x501) { + alias ACTCTXA ACTCTX; + alias CheckNameLegalDOS8Dot3A CheckNameLegalDOS8Dot3; + alias CreateActCtxA CreateActCtx; + alias FindActCtxSectionStringA FindActCtxSectionString; + alias GetSystemWow64DirectoryA GetSystemWow64Directory; + alias GetVolumePathNamesForVolumeNameA GetVolumePathNamesForVolumeName; + alias SetFileShortNameA SetFileShortName; + } + + static if (_WIN32_WINNT >= 0x502) { + alias GetDllDirectoryA GetDllDirectory; + alias SetDllDirectoryA SetDllDirectory; + alias SetFirmwareEnvironmentVariableA SetFirmwareEnvironmentVariable; + } + + static if (_WIN32_WINNT >= 0x600) { + alias CreateSymbolicLinkA CreateSymbolicLink; + } +} + +alias STARTUPINFO* LPSTARTUPINFO; +alias WIN32_FIND_DATA* LPWIN32_FIND_DATA; + +alias HW_PROFILE_INFO* LPHW_PROFILE_INFO; + +static if (_WIN32_WINNT >= 0x501) { + alias ACTCTX* PACTCTX, PCACTCTX; +} diff --git a/src/urt/internal/sys/windows/wincon.d b/src/urt/internal/sys/windows/wincon.d new file mode 100644 index 0000000..d378237 --- /dev/null +++ b/src/urt/internal/sys/windows/wincon.d @@ -0,0 +1,316 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_wincon.d) + */ +module urt.internal.sys.windows.wincon; +version (Windows): + +version (ANSI) {} else version = Unicode; +pragma(lib, "kernel32"); + +import urt.internal.sys.windows.w32api, urt.internal.sys.windows.windef; + +// FIXME: clean up Windows version support + +enum { + FOREGROUND_BLUE = 0x0001, + FOREGROUND_GREEN = 0x0002, + FOREGROUND_RED = 0x0004, + FOREGROUND_INTENSITY = 0x0008, + BACKGROUND_BLUE = 0x0010, + BACKGROUND_GREEN = 0x0020, + BACKGROUND_RED = 0x0040, + BACKGROUND_INTENSITY = 0x0080, + + COMMON_LVB_LEADING_BYTE = 0x0100, + COMMON_LVB_TRAILING_BYTE = 0x0200, + COMMON_LVB_GRID_HORIZONTAL = 0x0400, + COMMON_LVB_GRID_LVERTICAL = 0x0800, + COMMON_LVB_GRID_RVERTICAL = 0x1000, + COMMON_LVB_REVERSE_VIDEO = 0x4000, + COMMON_LVB_UNDERSCORE = 0x8000, + + COMMON_LVB_SBCSDBCS = 0x0300, +} + +static if (_WIN32_WINNT >= 0x501) { + enum { + CONSOLE_FULLSCREEN_MODE = 1, + CONSOLE_WINDOWED_MODE = 0 + } +} + +enum { + CTRL_C_EVENT = 0, + CTRL_BREAK_EVENT = 1, + CTRL_CLOSE_EVENT = 2, + CTRL_LOGOFF_EVENT = 5, + CTRL_SHUTDOWN_EVENT = 6 +} + +enum { + ENABLE_PROCESSED_INPUT = 1, + ENABLE_LINE_INPUT = 2, + ENABLE_ECHO_INPUT = 4, + ENABLE_WINDOW_INPUT = 8, + ENABLE_MOUSE_INPUT = 16, + ENABLE_INSERT_MODE = 32, + ENABLE_QUICK_EDIT_MODE = 64, + ENABLE_EXTENDED_FLAGS = 128, + ENABLE_AUTO_POSITION = 256, + ENABLE_VIRTUAL_TERMINAL_INPUT = 512 +} + +enum { + ENABLE_PROCESSED_OUTPUT = 1, + ENABLE_WRAP_AT_EOL_OUTPUT = 2, + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4, + DISABLE_NEWLINE_AUTO_RETURN = 8, + ENABLE_LVB_GRID_WORLDWIDE = 16 +} + +enum { + KEY_EVENT = 1, + MOUSE_EVENT = 2, + WINDOW_BUFFER_SIZE_EVENT = 4, + MENU_EVENT = 8, + FOCUS_EVENT = 16 +} +enum { + RIGHT_ALT_PRESSED = 1, + LEFT_ALT_PRESSED = 2, + RIGHT_CTRL_PRESSED = 4, + LEFT_CTRL_PRESSED = 8, + SHIFT_PRESSED = 16, + NUMLOCK_ON = 32, + SCROLLLOCK_ON = 64, + CAPSLOCK_ON = 128, + ENHANCED_KEY = 256 +} +enum { + FROM_LEFT_1ST_BUTTON_PRESSED = 1, + RIGHTMOST_BUTTON_PRESSED = 2, + FROM_LEFT_2ND_BUTTON_PRESSED = 4, + FROM_LEFT_3RD_BUTTON_PRESSED = 8, + FROM_LEFT_4TH_BUTTON_PRESSED = 16 +} + +enum { + MOUSE_MOVED = 1, + DOUBLE_CLICK = 2, + MOUSE_WHEELED = 4 +} + +struct CHAR_INFO { + union _Char { + WCHAR UnicodeChar = 0; + CHAR AsciiChar; + } + union { + _Char Char; + WCHAR UnicodeChar; + CHAR AsciiChar; + } + WORD Attributes; +} +alias CHAR_INFO* PCHAR_INFO; + +struct SMALL_RECT { + SHORT Left; + SHORT Top; + SHORT Right; + SHORT Bottom; +} +alias SMALL_RECT* PSMALL_RECT; + +struct CONSOLE_CURSOR_INFO { + DWORD dwSize; + BOOL bVisible; +} +alias CONSOLE_CURSOR_INFO* PCONSOLE_CURSOR_INFO; + +struct COORD { + SHORT X; + SHORT Y; +} +alias COORD* PCOORD; + +struct CONSOLE_FONT_INFO { + DWORD nFont; + COORD dwFontSize; +} +alias CONSOLE_FONT_INFO* PCONSOLE_FONT_INFO; + +struct CONSOLE_SCREEN_BUFFER_INFO { + COORD dwSize; + COORD dwCursorPosition; + WORD wAttributes; + SMALL_RECT srWindow; + COORD dwMaximumWindowSize; +} +alias CONSOLE_SCREEN_BUFFER_INFO* PCONSOLE_SCREEN_BUFFER_INFO; + +alias extern(Windows) BOOL function(DWORD) nothrow PHANDLER_ROUTINE; + +struct KEY_EVENT_RECORD { + BOOL bKeyDown; + WORD wRepeatCount; + WORD wVirtualKeyCode; + WORD wVirtualScanCode; + union _uChar { + WCHAR UnicodeChar = 0; + CHAR AsciiChar; + } + union { + WCHAR UnicodeChar = 0; + CHAR AsciiChar; + _uChar uChar; + } + DWORD dwControlKeyState; +} +alias KEY_EVENT_RECORD* PKEY_EVENT_RECORD; + +struct MOUSE_EVENT_RECORD { + COORD dwMousePosition; + DWORD dwButtonState; + DWORD dwControlKeyState; + DWORD dwEventFlags; +} +alias MOUSE_EVENT_RECORD* PMOUSE_EVENT_RECORD; + +struct WINDOW_BUFFER_SIZE_RECORD { + COORD dwSize; +} +alias WINDOW_BUFFER_SIZE_RECORD* PWINDOW_BUFFER_SIZE_RECORD; + +struct MENU_EVENT_RECORD { + UINT dwCommandId; +} +alias MENU_EVENT_RECORD* PMENU_EVENT_RECORD; + +struct FOCUS_EVENT_RECORD { + BOOL bSetFocus; +} +alias FOCUS_EVENT_RECORD* PFOCUS_EVENT_RECORD; + +struct INPUT_RECORD { + WORD EventType; + union _Event { + KEY_EVENT_RECORD KeyEvent; + MOUSE_EVENT_RECORD MouseEvent; + WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; + MENU_EVENT_RECORD MenuEvent; + FOCUS_EVENT_RECORD FocusEvent; + } + union { + KEY_EVENT_RECORD KeyEvent; + MOUSE_EVENT_RECORD MouseEvent; + WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; + MENU_EVENT_RECORD MenuEvent; + FOCUS_EVENT_RECORD FocusEvent; + _Event Event; + } +} +alias INPUT_RECORD* PINPUT_RECORD; + +extern (Windows) nothrow @nogc: + +BOOL AllocConsole(); +HANDLE CreateConsoleScreenBuffer(DWORD, DWORD, const(SECURITY_ATTRIBUTES)*, DWORD, LPVOID); +BOOL FillConsoleOutputAttribute(HANDLE, WORD, DWORD, COORD, PDWORD); +BOOL FillConsoleOutputCharacterA(HANDLE, CHAR, DWORD, COORD, PDWORD); +BOOL FillConsoleOutputCharacterW(HANDLE, WCHAR, DWORD, COORD, PDWORD); +BOOL FlushConsoleInputBuffer(HANDLE); +BOOL FreeConsole(); +BOOL GenerateConsoleCtrlEvent(DWORD, DWORD); +UINT GetConsoleCP(); +BOOL GetConsoleCursorInfo(HANDLE, PCONSOLE_CURSOR_INFO); +BOOL GetConsoleMode(HANDLE,PDWORD); +UINT GetConsoleOutputCP(); +BOOL GetConsoleScreenBufferInfo(HANDLE, PCONSOLE_SCREEN_BUFFER_INFO); +DWORD GetConsoleTitleA(LPSTR, DWORD); +DWORD GetConsoleTitleW(LPWSTR, DWORD); +COORD GetLargestConsoleWindowSize(HANDLE); +BOOL GetNumberOfConsoleInputEvents(HANDLE, PDWORD); +BOOL GetNumberOfConsoleMouseButtons(PDWORD); +BOOL PeekConsoleInputA(HANDLE, PINPUT_RECORD, DWORD, PDWORD); +BOOL PeekConsoleInputW(HANDLE, PINPUT_RECORD, DWORD, PDWORD); +BOOL ReadConsoleA(HANDLE, PVOID, DWORD, PDWORD, PVOID); +BOOL ReadConsoleW(HANDLE, PVOID, DWORD, PDWORD, PVOID); +BOOL ReadConsoleInputA(HANDLE, PINPUT_RECORD, DWORD, PDWORD); +BOOL ReadConsoleInputW(HANDLE, PINPUT_RECORD, DWORD, PDWORD); +BOOL ReadConsoleOutputAttribute(HANDLE, LPWORD, DWORD, COORD, LPDWORD); +BOOL ReadConsoleOutputCharacterA(HANDLE, LPSTR, DWORD, COORD, PDWORD); +BOOL ReadConsoleOutputCharacterW(HANDLE, LPWSTR, DWORD, COORD, PDWORD); +BOOL ReadConsoleOutputA(HANDLE, PCHAR_INFO, COORD, COORD, PSMALL_RECT); +BOOL ReadConsoleOutputW(HANDLE, PCHAR_INFO, COORD, COORD, PSMALL_RECT); +BOOL ScrollConsoleScreenBufferA(HANDLE, const(SMALL_RECT)*, const(SMALL_RECT)*, COORD, const(CHAR_INFO)*); +BOOL ScrollConsoleScreenBufferW(HANDLE, const(SMALL_RECT)*, const(SMALL_RECT)*, COORD, const(CHAR_INFO)*); +BOOL SetConsoleActiveScreenBuffer(HANDLE); +BOOL SetConsoleCP(UINT); +BOOL SetConsoleCtrlHandler(PHANDLER_ROUTINE, BOOL); +BOOL SetConsoleCursorInfo(HANDLE, const(CONSOLE_CURSOR_INFO)*); +BOOL SetConsoleCursorPosition(HANDLE, COORD); + + +static if (_WIN32_WINNT >= 0x500) { +BOOL GetConsoleDisplayMode(LPDWORD); +HWND GetConsoleWindow(); +} + +static if (_WIN32_WINNT >= 0x501) { +BOOL AttachConsole(DWORD); +BOOL SetConsoleDisplayMode(HANDLE, DWORD, PCOORD); +enum DWORD ATTACH_PARENT_PROCESS = cast(DWORD)-1; +} + +BOOL SetConsoleMode(HANDLE, DWORD); +BOOL SetConsoleOutputCP(UINT); +BOOL SetConsoleScreenBufferSize(HANDLE, COORD); +BOOL SetConsoleTextAttribute(HANDLE, WORD); +BOOL SetConsoleTitleA(LPCSTR); +BOOL SetConsoleTitleW(LPCWSTR); +BOOL SetConsoleWindowInfo(HANDLE, BOOL, const(SMALL_RECT)*); +BOOL WriteConsoleA(HANDLE, PCVOID, DWORD, PDWORD, PVOID); +BOOL WriteConsoleW(HANDLE, PCVOID, DWORD, PDWORD, PVOID); +BOOL WriteConsoleInputA(HANDLE, const(INPUT_RECORD)*, DWORD, PDWORD); +BOOL WriteConsoleInputW(HANDLE, const(INPUT_RECORD)*, DWORD, PDWORD); +BOOL WriteConsoleOutputA(HANDLE, const(CHAR_INFO)*, COORD, COORD, PSMALL_RECT); +BOOL WriteConsoleOutputW(HANDLE, const(CHAR_INFO)*, COORD, COORD, PSMALL_RECT); +BOOL WriteConsoleOutputAttribute(HANDLE, const(WORD)*, DWORD, COORD, PDWORD); +BOOL WriteConsoleOutputCharacterA(HANDLE, LPCSTR, DWORD, COORD, PDWORD); +BOOL WriteConsoleOutputCharacterW(HANDLE, LPCWSTR, DWORD, COORD, PDWORD); + +version (Unicode) { + alias FillConsoleOutputCharacterW FillConsoleOutputCharacter; + alias GetConsoleTitleW GetConsoleTitle; + alias PeekConsoleInputW PeekConsoleInput; + alias ReadConsoleW ReadConsole; + alias ReadConsoleInputW ReadConsoleInput; + alias ReadConsoleOutputW ReadConsoleOutput; + alias ReadConsoleOutputCharacterW ReadConsoleOutputCharacter; + alias ScrollConsoleScreenBufferW ScrollConsoleScreenBuffer; + alias SetConsoleTitleW SetConsoleTitle; + alias WriteConsoleW WriteConsole; + alias WriteConsoleInputW WriteConsoleInput; + alias WriteConsoleOutputW WriteConsoleOutput; + alias WriteConsoleOutputCharacterW WriteConsoleOutputCharacter; +} else { + alias FillConsoleOutputCharacterA FillConsoleOutputCharacter; + alias GetConsoleTitleA GetConsoleTitle; + alias PeekConsoleInputA PeekConsoleInput; + alias ReadConsoleA ReadConsole; + alias ReadConsoleInputA ReadConsoleInput; + alias ReadConsoleOutputA ReadConsoleOutput; + alias ReadConsoleOutputCharacterA ReadConsoleOutputCharacter; + alias ScrollConsoleScreenBufferA ScrollConsoleScreenBuffer; + alias SetConsoleTitleA SetConsoleTitle; + alias WriteConsoleA WriteConsole; + alias WriteConsoleInputA WriteConsoleInput; + alias WriteConsoleOutputA WriteConsoleOutput; + alias WriteConsoleOutputCharacterA WriteConsoleOutputCharacter; +} diff --git a/src/urt/internal/sys/windows/wincrypt.d b/src/urt/internal/sys/windows/wincrypt.d new file mode 100644 index 0000000..38e00b5 --- /dev/null +++ b/src/urt/internal/sys/windows/wincrypt.d @@ -0,0 +1,902 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * Authors: Stewart Gordon + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_wincrypt.d) + */ +module urt.internal.sys.windows.wincrypt; +version (Windows): +pragma(lib, "advapi32"); + +version (ANSI) {} else version = Unicode; + +import urt.internal.sys.windows.w32api, urt.internal.sys.windows.winbase, urt.internal.sys.windows.windef; + +/* FIXME: + * Types of some constants + * Types of macros + * Inits of various "size" and "version" members + * Why are some #ifdefs commented out? + */ + +const TCHAR[] + MS_DEF_PROV = "Microsoft Base Cryptographic Provider v1.0", + MS_ENHANCED_PROV = "Microsoft Enhanced Cryptographic Provider v1.0", + MS_STRONG_PROV = "Microsoft Strong Cryptographic Provider", + MS_DEF_RSA_SIG_PROV = "Microsoft RSA Signature Cryptographic Provider", + MS_DEF_RSA_SCHANNEL_PROV = "Microsoft RSA SChannel Cryptographic Provider", + MS_DEF_DSS_PROV = "Microsoft Base DSS Cryptographic Provider", + MS_DEF_DSS_DH_PROV + = "Microsoft Base DSS and Diffie-Hellman Cryptographic Provider", + MS_ENH_DSS_DH_PROV + = "Microsoft Enhanced DSS and Diffie-Hellman Cryptographic Provider", + MS_DEF_DH_SCHANNEL_PROV = "Microsoft DH SChannel Cryptographic Provider", + MS_SCARD_PROV = "Microsoft Base Smart Card Crypto Provider"; + +static if (_WIN32_WINNT > 0x501) { +const TCHAR[] MS_ENH_RSA_AES_PROV + = "Microsoft Enhanced RSA and AES Cryptographic Provider"; +} else static if (_WIN32_WINNT == 0x501) { +const TCHAR[] MS_ENH_RSA_AES_PROV + = "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)"; +} + +ALG_ID GET_ALG_CLASS()(ALG_ID x) { return x & 0xE000; } +ALG_ID GET_ALG_TYPE ()(ALG_ID x) { return x & 0x1E00; } +ALG_ID GET_ALG_SID ()(ALG_ID x) { return x & 0x01FF; } + +enum : ALG_ID { + ALG_CLASS_ANY = 0, + ALG_CLASS_SIGNATURE = 0x2000, + ALG_CLASS_MSG_ENCRYPT = 0x4000, + ALG_CLASS_DATA_ENCRYPT = 0x6000, + ALG_CLASS_HASH = 0x8000, + ALG_CLASS_KEY_EXCHANGE = 0xA000, + ALG_CLASS_ALL = 0xE000 +} + +enum : ALG_ID { + ALG_TYPE_ANY = 0, + ALG_TYPE_DSS = 0x0200, + ALG_TYPE_RSA = 0x0400, + ALG_TYPE_BLOCK = 0x0600, + ALG_TYPE_STREAM = 0x0800, + ALG_TYPE_DH = 0x0A00, + ALG_TYPE_SECURECHANNEL = 0x0C00 +} + +enum : ALG_ID { + ALG_SID_ANY = 0, + ALG_SID_RSA_ANY = 0, + ALG_SID_RSA_PKCS, + ALG_SID_RSA_MSATWORK, + ALG_SID_RSA_ENTRUST, + ALG_SID_RSA_PGP, // = 4 + ALG_SID_DSS_ANY = 0, + ALG_SID_DSS_PKCS, + ALG_SID_DSS_DMS, // = 2 + ALG_SID_DES = 1, + ALG_SID_3DES = 3, + ALG_SID_DESX, + ALG_SID_IDEA, + ALG_SID_CAST, + ALG_SID_SAFERSK64, + ALG_SID_SAFERSK128, + ALG_SID_3DES_112, + ALG_SID_SKIPJACK, + ALG_SID_TEK, + ALG_SID_CYLINK_MEK, + ALG_SID_RC5, // = 13 + ALG_SID_RC2 = 2, + ALG_SID_RC4 = 1, + ALG_SID_SEAL = 2, + ALG_SID_MD2 = 1, + ALG_SID_MD4, + ALG_SID_MD5, + ALG_SID_SHA, + ALG_SID_MAC, + ALG_SID_RIPEMD, + ALG_SID_RIPEMD160, + ALG_SID_SSL3SHAMD5, + ALG_SID_HMAC, + ALG_SID_TLS1PRF, // = 10 + ALG_SID_AES_128 = 14, + ALG_SID_AES_192, + ALG_SID_AES_256, + ALG_SID_AES, // = 17 + ALG_SID_EXAMPLE = 80 +} + +enum : ALG_ID { + CALG_MD2 = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD2, + CALG_MD4 = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD4, + CALG_MD5 = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD5, + CALG_SHA = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA, + CALG_SHA1 = CALG_SHA, + CALG_MAC = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MAC, + CALG_3DES = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | 3, + CALG_CYLINK_MEK = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | 12, + CALG_SKIPJACK = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | 10, + CALG_KEA_KEYX = ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_STREAM | ALG_TYPE_DSS | 4, + CALG_RSA_SIGN = ALG_CLASS_SIGNATURE | ALG_TYPE_RSA | ALG_SID_RSA_ANY, + CALG_DSS_SIGN = ALG_CLASS_SIGNATURE | ALG_TYPE_DSS | ALG_SID_DSS_ANY, + CALG_RSA_KEYX = ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_RSA | ALG_SID_RSA_ANY, + CALG_DES = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_DES, + CALG_RC2 = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_RC2, + CALG_RC4 = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_STREAM | ALG_SID_RC4, + CALG_SEAL = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_STREAM | ALG_SID_SEAL, + CALG_DH_EPHEM = ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_STREAM | ALG_TYPE_DSS + | ALG_SID_DSS_DMS, + CALG_DESX = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_DESX, +// is undefined ALG_CLASS_DHASH in MinGW - presuming typo + CALG_TLS1PRF = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_TLS1PRF, + CALG_AES_128 = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_128, + CALG_AES_192 = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_192, + CALG_AES_256 = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_256, + CALG_AES = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES, +} + +enum { + CRYPT_VERIFYCONTEXT = 0xF0000000, +} + +enum { + CRYPT_NEWKEYSET = 8, + CRYPT_DELETEKEYSET = 16, + CRYPT_MACHINE_KEYSET = 32, + CRYPT_SILENT = 64, +} + +enum { + CRYPT_EXPORTABLE = 1, + CRYPT_USER_PROTECTED = 2, + CRYPT_CREATE_SALT = 4, + CRYPT_UPDATE_KEY = 8, +} + +enum { + SIMPLEBLOB = 1, + PUBLICKEYBLOB = 6, + PRIVATEKEYBLOB = 7, + PLAINTEXTKEYBLOB = 8, + OPAQUEKEYBLOB = 9, + PUBLICKEYBLOBEX = 10, + SYMMETRICWRAPKEYBLOB = 11, +} + +enum { + AT_KEYEXCHANGE = 1, + AT_SIGNATURE = 2, +} + +enum { + CRYPT_USERDATA = 1, +} + +enum { + PKCS5_PADDING = 1, +} + +enum { + CRYPT_MODE_CBC = 1, + CRYPT_MODE_ECB = 2, + CRYPT_MODE_OFB = 3, + CRYPT_MODE_CFB = 4, + CRYPT_MODE_CTS = 5, + CRYPT_MODE_CBCI = 6, + CRYPT_MODE_CFBP = 7, + CRYPT_MODE_OFBP = 8, + CRYPT_MODE_CBCOFM = 9, + CRYPT_MODE_CBCOFMI = 10, +} + +enum { + CRYPT_ENCRYPT = 1, + CRYPT_DECRYPT = 2, + CRYPT_EXPORT = 4, + CRYPT_READ = 8, + CRYPT_WRITE = 16, + CRYPT_MAC = 32, +} + +enum { + HP_ALGID = 1, + HP_HASHVAL = 2, + HP_HASHSIZE = 4, + HP_HMAC_INFO = 5, +} + +enum { + CRYPT_FAILED = FALSE, + CRYPT_SUCCEED = TRUE, +} + +bool RCRYPT_SUCCEEDED()(BOOL r) { return r==CRYPT_SUCCEED; } +bool RCRYPT_FAILED()(BOOL r) { return r==CRYPT_FAILED; } + +enum { + PP_ENUMALGS = 1, + PP_ENUMCONTAINERS = 2, + PP_IMPTYPE = 3, + PP_NAME = 4, + PP_VERSION = 5, + PP_CONTAINER = 6, + PP_CHANGE_PASSWORD = 7, + PP_KEYSET_SEC_DESCR = 8, + PP_CERTCHAIN = 9, + PP_KEY_TYPE_SUBTYPE = 10, + PP_PROVTYPE = 16, + PP_KEYSTORAGE = 17, + PP_APPLI_CERT = 18, + PP_SYM_KEYSIZE = 19, + PP_SESSION_KEYSIZE = 20, + PP_UI_PROMPT = 21, + PP_ENUMALGS_EX = 22, + PP_ENUMMANDROOTS = 25, + PP_ENUMELECTROOTS = 26, + PP_KEYSET_TYPE = 27, + PP_ADMIN_PIN = 31, + PP_KEYEXCHANGE_PIN = 32, + PP_SIGNATURE_PIN = 33, + PP_SIG_KEYSIZE_INC = 34, + PP_KEYX_KEYSIZE_INC = 35, + PP_UNIQUE_CONTAINER = 36, + PP_SGC_INFO = 37, + PP_USE_HARDWARE_RNG = 38, + PP_KEYSPEC = 39, + PP_ENUMEX_SIGNING_PROT = 40, +} + +enum { + CRYPT_FIRST = 1, + CRYPT_NEXT = 2, +} + +enum { + CRYPT_IMPL_HARDWARE = 1, + CRYPT_IMPL_SOFTWARE = 2, + CRYPT_IMPL_MIXED = 3, + CRYPT_IMPL_UNKNOWN = 4, +} + +enum { + PROV_RSA_FULL = 1, + PROV_RSA_SIG = 2, + PROV_DSS = 3, + PROV_FORTEZZA = 4, + PROV_MS_MAIL = 5, + PROV_SSL = 6, + PROV_STT_MER = 7, + PROV_STT_ACQ = 8, + PROV_STT_BRND = 9, + PROV_STT_ROOT = 10, + PROV_STT_ISS = 11, + PROV_RSA_SCHANNEL = 12, + PROV_DSS_DH = 13, + PROV_EC_ECDSA_SIG = 14, + PROV_EC_ECNRA_SIG = 15, + PROV_EC_ECDSA_FULL = 16, + PROV_EC_ECNRA_FULL = 17, + PROV_DH_SCHANNEL = 18, + PROV_SPYRUS_LYNKS = 20, + PROV_RNG = 21, + PROV_INTEL_SEC = 22, + PROV_RSA_AES = 24, + MAXUIDLEN = 64, +} + +enum { + CUR_BLOB_VERSION = 2, +} + +enum { + X509_ASN_ENCODING = 1, + PKCS_7_ASN_ENCODING = 65536, +} + +enum { + CERT_V1 = 0, + CERT_V2 = 1, + CERT_V3 = 2, +} + +enum { + CERT_E_CHAINING = (-2146762486), + CERT_E_CN_NO_MATCH = (-2146762481), + CERT_E_EXPIRED = (-2146762495), + CERT_E_PURPOSE = (-2146762490), + CERT_E_REVOCATION_FAILURE = (-2146762482), + CERT_E_REVOKED = (-2146762484), + CERT_E_ROLE = (-2146762493), + CERT_E_UNTRUSTEDROOT = (-2146762487), + CERT_E_UNTRUSTEDTESTROOT = (-2146762483), + CERT_E_VALIDITYPERIODNESTING = (-2146762494), + CERT_E_WRONG_USAGE = (-2146762480), + CERT_E_PATHLENCONST = (-2146762492), + CERT_E_CRITICAL = (-2146762491), + CERT_E_ISSUERCHAINING = (-2146762489), + CERT_E_MALFORMED = (-2146762488), + CRYPT_E_REVOCATION_OFFLINE = (-2146885613), + CRYPT_E_REVOKED = (-2146885616), + TRUST_E_BASIC_CONSTRAINTS = (-2146869223), + TRUST_E_CERT_SIGNATURE = (-2146869244), + TRUST_E_FAIL = (-2146762485), +} + +enum { + CERT_TRUST_NO_ERROR = 0, + CERT_TRUST_IS_NOT_TIME_VALID = 1, + CERT_TRUST_IS_NOT_TIME_NESTED = 2, + CERT_TRUST_IS_REVOKED = 4, + CERT_TRUST_IS_NOT_SIGNATURE_VALID = 8, + CERT_TRUST_IS_NOT_VALID_FOR_USAGE = 16, + CERT_TRUST_IS_UNTRUSTED_ROOT = 32, + CERT_TRUST_REVOCATION_STATUS_UNKNOWN = 64, + CERT_TRUST_IS_CYCLIC = 128, + CERT_TRUST_IS_PARTIAL_CHAIN = 65536, + CERT_TRUST_CTL_IS_NOT_TIME_VALID = 131072, + CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID = 262144, + CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE = 524288, +} + +enum { + CERT_TRUST_HAS_EXACT_MATCH_ISSUER = 1, + CERT_TRUST_HAS_KEY_MATCH_ISSUER = 2, + CERT_TRUST_HAS_NAME_MATCH_ISSUER = 4, + CERT_TRUST_IS_SELF_SIGNED = 8, + CERT_TRUST_IS_COMPLEX_CHAIN = 65536, +} + +enum { + CERT_CHAIN_POLICY_BASE = cast(LPCSTR) 1, + CERT_CHAIN_POLICY_AUTHENTICODE = cast(LPCSTR) 2, + CERT_CHAIN_POLICY_AUTHENTICODE_TS = cast(LPCSTR) 3, + CERT_CHAIN_POLICY_SSL = cast(LPCSTR) 4, + CERT_CHAIN_POLICY_BASIC_CONSTRAINTS = cast(LPCSTR) 5, + CERT_CHAIN_POLICY_NT_AUTH = cast(LPCSTR) 6, +} + +enum { + USAGE_MATCH_TYPE_AND = 0, + USAGE_MATCH_TYPE_OR = 1, +} + +enum { + CERT_SIMPLE_NAME_STR = 1, + CERT_OID_NAME_STR = 2, + CERT_X500_NAME_STR = 3, +} +enum { + CERT_NAME_STR_SEMICOLON_FLAG = 1073741824, + CERT_NAME_STR_CRLF_FLAG = 134217728, + CERT_NAME_STR_NO_PLUS_FLAG = 536870912, + CERT_NAME_STR_NO_QUOTING_FLAG = 268435456, + CERT_NAME_STR_REVERSE_FLAG = 33554432, + CERT_NAME_STR_ENABLE_T61_UNICODE_FLAG = 131072, +} + +enum { + CERT_FIND_ANY = 0, + CERT_FIND_CERT_ID = 1048576, + CERT_FIND_CTL_USAGE = 655360, + CERT_FIND_ENHKEY_USAGE = 655360, + CERT_FIND_EXISTING = 851968, + CERT_FIND_HASH = 65536, + CERT_FIND_ISSUER_ATTR = 196612, + CERT_FIND_ISSUER_NAME = 131076, + CERT_FIND_ISSUER_OF = 786432, + CERT_FIND_KEY_IDENTIFIER = 983040, + CERT_FIND_KEY_SPEC = 589824, + CERT_FIND_MD5_HASH = 262144, + CERT_FIND_PROPERTY = 327680, + CERT_FIND_PUBLIC_KEY = 393216, + CERT_FIND_SHA1_HASH = 65536, + CERT_FIND_SIGNATURE_HASH = 917504, + CERT_FIND_SUBJECT_ATTR = 196615, + CERT_FIND_SUBJECT_CERT = 720896, + CERT_FIND_SUBJECT_NAME = 131079, + CERT_FIND_SUBJECT_STR_A = 458759, + CERT_FIND_SUBJECT_STR_W = 524295, + CERT_FIND_ISSUER_STR_A = 458756, + CERT_FIND_ISSUER_STR_W = 524292, +} + +enum { + CERT_FIND_OR_ENHKEY_USAGE_FLAG = 16, + CERT_FIND_OPTIONAL_ENHKEY_USAGE_FLAG = 1, + CERT_FIND_NO_ENHKEY_USAGE_FLAG = 8, + CERT_FIND_VALID_ENHKEY_USAGE_FLAG = 32, + CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG = 2, +} + +enum { + CERT_CASE_INSENSITIVE_IS_RDN_ATTRS_FLAG = 2, + CERT_UNICODE_IS_RDN_ATTRS_FLAG = 1, + CERT_CHAIN_FIND_BY_ISSUER = 1, +} + +enum { + CERT_CHAIN_FIND_BY_ISSUER_COMPARE_KEY_FLAG = 1, + CERT_CHAIN_FIND_BY_ISSUER_COMPLEX_CHAIN_FLAG = 2, + CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_URL_FLAG = 4, + CERT_CHAIN_FIND_BY_ISSUER_LOCAL_MACHINE_FLAG = 8, + CERT_CHAIN_FIND_BY_ISSUER_NO_KEY_FLAG = 16384, + CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_FLAG = 32768, +} + +enum { + CERT_STORE_PROV_SYSTEM = 10, + CERT_SYSTEM_STORE_LOCAL_MACHINE = 131072, +} + +enum { + szOID_PKIX_KP_SERVER_AUTH = "4235600", + szOID_SERVER_GATED_CRYPTO = "4235658", + szOID_SGC_NETSCAPE = "2.16.840.1.113730.4.1", + szOID_PKIX_KP_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2", +} + +enum { + CRYPT_NOHASHOID = 0x00000001, + CRYPT_NO_SALT = 0x10, + CRYPT_PREGEN = 0x40, +} + +enum { + CRYPT_RECIPIENT = 0x10, + CRYPT_INITIATOR = 0x40, + CRYPT_ONLINE = 0x80, + CRYPT_SF = 0x100, + CRYPT_CREATE_IV = 0x200, + CRYPT_KEK = 0x400, + CRYPT_DATA_KEY = 0x800, + CRYPT_VOLATILE = 0x1000, + CRYPT_SGCKEY = 0x2000, +} + +enum { + KP_IV = 0x00000001, + KP_SALT = 0x00000002, + KP_PADDING = 0x00000003, + KP_MODE = 0x00000004, + KP_MODE_BITS = 0x00000005, + KP_PERMISSIONS = 0x00000006, + KP_ALGID = 0x00000007, + KP_BLOCKLEN = 0x00000008, + KP_KEYLEN = 0x00000009, + KP_SALT_EX = 0x0000000a, + KP_P = 0x0000000b, + KP_G = 0x0000000c, + KP_Q = 0x0000000d, + KP_X = 0x0000000e, + KP_Y = 0x0000000f, + KP_RA = 0x00000010, + KP_RB = 0x00000011, + KP_INFO = 0x00000012, + KP_EFFECTIVE_KEYLEN = 0x00000013, + KP_SCHANNEL_ALG = 0x00000014, + KP_PUB_PARAMS = 0x00000027, +} + +enum { + CRYPT_FLAG_PCT1 = 0x0001, + CRYPT_FLAG_SSL2 = 0x0002, + CRYPT_FLAG_SSL3 = 0x0004, + CRYPT_FLAG_TLS1 = 0x0008, + CRYPT_FLAG_IPSEC = 0x0010, + CRYPT_FLAG_SIGNING = 0x0020, +} + +enum { + SCHANNEL_MAC_KEY = 0x00000000, + SCHANNEL_ENC_KEY = 0x00000001, +} + +enum { + INTERNATIONAL_USAGE = 0x00000001, +} + + +alias UINT ALG_ID; +alias ULONG_PTR HCRYPTPROV, HCRYPTKEY, HCRYPTHASH; +alias PVOID HCERTSTORE, HCRYPTMSG, HCERTCHAINENGINE; + +struct VTableProvStruc { + FARPROC FuncVerifyImage; +} +alias VTableProvStruc* PVTableProvStruc; + +struct _CRYPTOAPI_BLOB { + DWORD cbData; + BYTE* pbData; +} +alias _CRYPTOAPI_BLOB CRYPT_INTEGER_BLOB, CRYPT_UINT_BLOB, + CRYPT_OBJID_BLOB, CERT_NAME_BLOB, CERT_RDN_VALUE_BLOB, CERT_BLOB, + CRL_BLOB, DATA_BLOB, CRYPT_DATA_BLOB, CRYPT_HASH_BLOB, + CRYPT_DIGEST_BLOB, CRYPT_DER_BLOB, CRYPT_ATTR_BLOB; +alias _CRYPTOAPI_BLOB* PCRYPT_INTEGER_BLOB, PCRYPT_UINT_BLOB, + PCRYPT_OBJID_BLOB, PCERT_NAME_BLOB, PCERT_RDN_VALUE_BLOB, PCERT_BLOB, + PCRL_BLOB, PDATA_BLOB, PCRYPT_DATA_BLOB, PCRYPT_HASH_BLOB, + PCRYPT_DIGEST_BLOB, PCRYPT_DER_BLOB, PCRYPT_ATTR_BLOB; + +// not described in SDK; has the same layout as HTTPSPolicyCallbackData +struct SSL_EXTRA_CERT_CHAIN_POLICY_PARA { + DWORD cbStruct; + DWORD dwAuthType; + DWORD fdwChecks; + LPWSTR pwszServerName; +} +alias SSL_EXTRA_CERT_CHAIN_POLICY_PARA HTTPSPolicyCallbackData; +alias SSL_EXTRA_CERT_CHAIN_POLICY_PARA* PSSL_EXTRA_CERT_CHAIN_POLICY_PARA, + PHTTPSPolicyCallbackData; + +/* #if (_WIN32_WINNT>=0x500) */ +struct CERT_CHAIN_POLICY_PARA { + DWORD cbSize = CERT_CHAIN_POLICY_PARA.sizeof; + DWORD dwFlags; + void* pvExtraPolicyPara; +} +alias CERT_CHAIN_POLICY_PARA* PCERT_CHAIN_POLICY_PARA; + +struct CERT_CHAIN_POLICY_STATUS { + DWORD cbSize = CERT_CHAIN_POLICY_STATUS.sizeof; + DWORD dwError; + LONG lChainIndex; + LONG lElementIndex; + void* pvExtraPolicyStatus; +} +alias CERT_CHAIN_POLICY_STATUS* PCERT_CHAIN_POLICY_STATUS; +/* #endif */ + +struct CRYPT_ALGORITHM_IDENTIFIER { + LPSTR pszObjId; + CRYPT_OBJID_BLOB Parameters; +} +alias CRYPT_ALGORITHM_IDENTIFIER* PCRYPT_ALGORITHM_IDENTIFIER; + +struct CRYPT_BIT_BLOB { + DWORD cbData; + BYTE* pbData; + DWORD cUnusedBits; +} +alias CRYPT_BIT_BLOB* PCRYPT_BIT_BLOB; + +struct CERT_PUBLIC_KEY_INFO { + CRYPT_ALGORITHM_IDENTIFIER Algorithm; + CRYPT_BIT_BLOB PublicKey; +} +alias CERT_PUBLIC_KEY_INFO* PCERT_PUBLIC_KEY_INFO; + +struct CERT_EXTENSION { + LPSTR pszObjId; + BOOL fCritical; + CRYPT_OBJID_BLOB Value; +} +alias CERT_EXTENSION* PCERT_EXTENSION; + +struct CERT_INFO { + DWORD dwVersion; + CRYPT_INTEGER_BLOB SerialNumber; + CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm; + CERT_NAME_BLOB Issuer; + FILETIME NotBefore; + FILETIME NotAfter; + CERT_NAME_BLOB Subject; + CERT_PUBLIC_KEY_INFO SubjectPublicKeyInfo; + CRYPT_BIT_BLOB IssuerUniqueId; + CRYPT_BIT_BLOB SubjectUniqueId; + DWORD cExtension; + PCERT_EXTENSION rgExtension; +} +alias CERT_INFO* PCERT_INFO; + +struct CERT_CONTEXT { + DWORD dwCertEncodingType; + BYTE* pbCertEncoded; + DWORD cbCertEncoded; + PCERT_INFO pCertInfo; + HCERTSTORE hCertStore; +} +alias CERT_CONTEXT* PCERT_CONTEXT; +alias const(CERT_CONTEXT)* PCCERT_CONTEXT; + +struct CTL_USAGE { + DWORD cUsageIdentifier; + LPSTR* rgpszUsageIdentifier; +} +alias CTL_USAGE CERT_ENHKEY_USAGE; +alias CTL_USAGE* PCTRL_USAGE, PCERT_ENHKEY_USAGE; + +struct CERT_USAGE_MATCH { + DWORD dwType; + CERT_ENHKEY_USAGE Usage; +} +alias CERT_USAGE_MATCH* PCERT_USAGE_MATCH; +/* #if (_WIN32_WINNT>=0x500) */ + +struct CERT_CHAIN_PARA { + DWORD cbSize = CERT_CHAIN_PARA.sizeof; + CERT_USAGE_MATCH RequestedUsage; +//#if CERT_CHAIN_PARA_HAS_EXTRA_FIELDS + CERT_USAGE_MATCH RequestedIssuancePolicy; + DWORD dwUrlRetrievalTimeout; + BOOL fCheckRevocationFreshnessTime; + DWORD dwRevocationFreshnessTime; +//#endif +} +alias CERT_CHAIN_PARA* PCERT_CHAIN_PARA; + +extern (Windows) alias BOOL function(PCCERT_CONTEXT, void*) + PFN_CERT_CHAIN_FIND_BY_ISSUER_CALLBACK; + +struct CERT_CHAIN_FIND_BY_ISSUER_PARA { + DWORD cbSize = CERT_CHAIN_FIND_BY_ISSUER_PARA.sizeof; + LPCSTR pszUsageIdentifier; + DWORD dwKeySpec; + DWORD dwAcquirePrivateKeyFlags; + DWORD cIssuer; + CERT_NAME_BLOB* rgIssuer; + PFN_CERT_CHAIN_FIND_BY_ISSUER_CALLBACK pfnFIndCallback; + void* pvFindArg; + DWORD* pdwIssuerChainIndex; + DWORD* pdwIssuerElementIndex; +} +alias CERT_CHAIN_FIND_BY_ISSUER_PARA* PCERT_CHAIN_FIND_BY_ISSUER_PARA; +/* #endif */ + +struct CERT_TRUST_STATUS { + DWORD dwErrorStatus; + DWORD dwInfoStatus; +} +alias CERT_TRUST_STATUS* PCERT_TRUST_STATUS; + +struct CRL_ENTRY { + CRYPT_INTEGER_BLOB SerialNumber; + FILETIME RevocationDate; + DWORD cExtension; + PCERT_EXTENSION rgExtension; +} +alias CRL_ENTRY* PCRL_ENTRY; + +struct CRL_INFO { + DWORD dwVersion; + CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm; + CERT_NAME_BLOB Issuer; + FILETIME ThisUpdate; + FILETIME NextUpdate; + DWORD cCRLEntry; + PCRL_ENTRY rgCRLEntry; + DWORD cExtension; + PCERT_EXTENSION rgExtension; +} +alias CRL_INFO* PCRL_INFO; + +struct CRL_CONTEXT { + DWORD dwCertEncodingType; + BYTE* pbCrlEncoded; + DWORD cbCrlEncoded; + PCRL_INFO pCrlInfo; + HCERTSTORE hCertStore; +} +alias CRL_CONTEXT* PCRL_CONTEXT; +alias const(CRL_CONTEXT)* PCCRL_CONTEXT; + +struct CERT_REVOCATION_CRL_INFO { + DWORD cbSize = CERT_REVOCATION_CRL_INFO.sizeof; + PCCRL_CONTEXT pBaseCRLContext; + PCCRL_CONTEXT pDeltaCRLContext; + PCRL_ENTRY pCrlEntry; + BOOL fDeltaCrlEntry; +} +alias CERT_REVOCATION_CRL_INFO* PCERT_REVOCATION_CRL_INFO; + +struct CERT_REVOCATION_INFO { + DWORD cbSize = CERT_REVOCATION_INFO.sizeof; + DWORD dwRevocationResult; + LPCSTR pszRevocationOid; + LPVOID pvOidSpecificInfo; + BOOL fHasFreshnessTime; + DWORD dwFreshnessTime; + PCERT_REVOCATION_CRL_INFO pCrlInfo; +} +alias CERT_REVOCATION_INFO* PCERT_REVOCATION_INFO; + +/* #if (_WIN32_WINNT>=0x500) */ +struct CERT_CHAIN_ELEMENT { + DWORD cbSize = CERT_CHAIN_ELEMENT.sizeof; + PCCERT_CONTEXT pCertContext; + CERT_TRUST_STATUS TrustStatus; + PCERT_REVOCATION_INFO pRevocationInfo; + PCERT_ENHKEY_USAGE pIssuanceUsage; + PCERT_ENHKEY_USAGE pApplicationUsage; +} +alias CERT_CHAIN_ELEMENT* PCERT_CHAIN_ELEMENT; +/* #endif */ + +struct CRYPT_ATTRIBUTE { + LPSTR pszObjId; + DWORD cValue; + PCRYPT_ATTR_BLOB rgValue; +} +alias CRYPT_ATTRIBUTE* PCRYPT_ATTRIBUTE; + +struct CTL_ENTRY { + CRYPT_DATA_BLOB SubjectIdentifier; + DWORD cAttribute; + PCRYPT_ATTRIBUTE rgAttribute; +} +alias CTL_ENTRY* PCTL_ENTRY; + +struct CTL_INFO { + DWORD dwVersion; + CTL_USAGE SubjectUsage; + CRYPT_DATA_BLOB ListIdentifier; + CRYPT_INTEGER_BLOB SequenceNumber; + FILETIME ThisUpdate; + FILETIME NextUpdate; + CRYPT_ALGORITHM_IDENTIFIER SubjectAlgorithm; + DWORD cCTLEntry; + PCTL_ENTRY rgCTLEntry; + DWORD cExtension; + PCERT_EXTENSION rgExtension; +} +alias CTL_INFO* PCTL_INFO; + +struct CTL_CONTEXT { + DWORD dwMsgAndCertEncodingType; + BYTE* pbCtlEncoded; + DWORD cbCtlEncoded; + PCTL_INFO pCtlInfo; + HCERTSTORE hCertStore; + HCRYPTMSG hCryptMsg; + BYTE* pbCtlContent; + DWORD cbCtlContent; +} +alias CTL_CONTEXT* PCTL_CONTEXT; +alias const(CTL_CONTEXT)* PCCTL_CONTEXT; + +struct CERT_TRUST_LIST_INFO { + DWORD cbSize = CERT_TRUST_LIST_INFO.sizeof; + PCTL_ENTRY pCtlEntry; + PCCTL_CONTEXT pCtlContext; +} +alias CERT_TRUST_LIST_INFO* PCERT_TRUST_LIST_INFO; + +struct CERT_SIMPLE_CHAIN { + DWORD cbSize = CERT_SIMPLE_CHAIN.sizeof; + CERT_TRUST_STATUS TrustStatus; + DWORD cElement; + PCERT_CHAIN_ELEMENT* rgpElement; + PCERT_TRUST_LIST_INFO pTrustListInfo; + BOOL fHasRevocationFreshnessTime; + DWORD dwRevocationFreshnessTime; +} +alias CERT_SIMPLE_CHAIN* PCERT_SIMPLE_CHAIN; + +/* #if (_WIN32_WINNT>=0x500) */ +alias const(CERT_CHAIN_CONTEXT)* PCCERT_CHAIN_CONTEXT; +struct CERT_CHAIN_CONTEXT { + DWORD cbSize = CERT_CHAIN_CONTEXT.sizeof; + CERT_TRUST_STATUS TrustStatus; + DWORD cChain; + PCERT_SIMPLE_CHAIN* rgpChain; + DWORD cLowerQualityChainContext; + PCCERT_CHAIN_CONTEXT* rgpLowerQualityChainContext; + BOOL fHasRevocationFreshnessTime; + DWORD dwRevocationFreshnessTime; +} +alias CERT_CHAIN_CONTEXT* PCERT_CHAIN_CONTEXT; +/* #endif */ + +struct PROV_ENUMALGS { + ALG_ID aiAlgid; + DWORD dwBitLen; + DWORD dwNameLen; + CHAR[20] szName = 0; +} + +struct PUBLICKEYSTRUC { + BYTE bType; + BYTE bVersion; + WORD reserved; + ALG_ID aiKeyAlg; +} +alias PUBLICKEYSTRUC BLOBHEADER; + +struct RSAPUBKEY { + DWORD magic; + DWORD bitlen; + DWORD pubexp; +} + +struct HMAC_INFO { + ALG_ID HashAlgid; + BYTE* pbInnerString; + DWORD cbInnerString; + BYTE* pbOuterString; + DWORD cbOuterString; +} +alias HMAC_INFO* PHMAC_INFO; + +extern (Windows) nothrow @nogc { + BOOL CertCloseStore(HCERTSTORE, DWORD); + BOOL CertGetCertificateChain(HCERTCHAINENGINE, PCCERT_CONTEXT, LPFILETIME, + HCERTSTORE, PCERT_CHAIN_PARA, DWORD, LPVOID, PCCERT_CHAIN_CONTEXT*); + BOOL CertVerifyCertificateChainPolicy(LPCSTR, PCCERT_CHAIN_CONTEXT, + PCERT_CHAIN_POLICY_PARA, PCERT_CHAIN_POLICY_STATUS); + void CertFreeCertificateChain(PCCERT_CHAIN_CONTEXT); + DWORD CertNameToStrA(DWORD, PCERT_NAME_BLOB, DWORD, LPSTR, DWORD); + DWORD CertNameToStrW(DWORD, PCERT_NAME_BLOB, DWORD, LPWSTR, DWORD); + HCERTSTORE CertOpenSystemStoreA(HCRYPTPROV, LPCSTR); + HCERTSTORE CertOpenSystemStoreW(HCRYPTPROV, LPCWSTR); + HCERTSTORE CertOpenStore(LPCSTR, DWORD, HCRYPTPROV, DWORD, const(void)*); + PCCERT_CONTEXT CertFindCertificateInStore(HCERTSTORE, DWORD, DWORD, DWORD, +const(void)*, PCCERT_CONTEXT); + BOOL CertFreeCertificateContext(PCCERT_CONTEXT); + PCCERT_CONTEXT CertGetIssuerCertificateFromStore(HCERTSTORE, + PCCERT_CONTEXT, PCCERT_CONTEXT, DWORD*); + PCCERT_CHAIN_CONTEXT CertFindChainInStore(HCERTSTORE, DWORD, DWORD, DWORD, +const(void)*, PCCERT_CHAIN_CONTEXT); + + BOOL CryptAcquireContextA(HCRYPTPROV*, LPCSTR, LPCSTR, DWORD, DWORD); + BOOL CryptAcquireContextW(HCRYPTPROV*, LPCWSTR, LPCWSTR, DWORD, DWORD); + BOOL CryptContextAddRef(HCRYPTPROV, DWORD*, DWORD); + BOOL CryptReleaseContext(HCRYPTPROV, ULONG_PTR); + BOOL CryptGenKey(HCRYPTPROV, ALG_ID, DWORD, HCRYPTKEY*); + BOOL CryptDeriveKey(HCRYPTPROV, ALG_ID, HCRYPTHASH, DWORD, HCRYPTKEY*); + BOOL CryptDestroyKey(HCRYPTKEY); + static if (_WIN32_WINNT >= 0x500) { + BOOL CryptDuplicateHash(HCRYPTHASH, DWORD*, DWORD, HCRYPTHASH*); + BOOL CryptDuplicateKey(HCRYPTKEY, DWORD*, DWORD, HCRYPTKEY*); + } + BOOL CryptSetKeyParam(HCRYPTKEY, DWORD, PBYTE, DWORD); + BOOL CryptGetKeyParam(HCRYPTKEY, DWORD, PBYTE, PDWORD, DWORD); + BOOL CryptSetHashParam(HCRYPTHASH, DWORD, PBYTE, DWORD); + BOOL CryptGetHashParam(HCRYPTHASH, DWORD, PBYTE, PDWORD, DWORD); + BOOL CryptSetProvParam(HCRYPTPROV, DWORD, PBYTE, DWORD); + BOOL CryptGetProvParam(HCRYPTPROV, DWORD, PBYTE, PDWORD, DWORD); + BOOL CryptGenRandom(HCRYPTPROV, DWORD, PBYTE); + BOOL CryptGetUserKey(HCRYPTPROV, DWORD, HCRYPTKEY*); + BOOL CryptExportKey(HCRYPTKEY, HCRYPTKEY, DWORD, DWORD, PBYTE, PDWORD); + BOOL CryptImportKey(HCRYPTPROV, PBYTE, DWORD, HCRYPTKEY, DWORD, + HCRYPTKEY*); + BOOL CryptEncrypt(HCRYPTKEY, HCRYPTHASH, BOOL, DWORD, PBYTE, PDWORD, + DWORD); + BOOL CryptDecrypt(HCRYPTKEY, HCRYPTHASH, BOOL, DWORD, PBYTE, PDWORD); + BOOL CryptCreateHash(HCRYPTPROV, ALG_ID, HCRYPTKEY, DWORD, HCRYPTHASH*); + BOOL CryptHashData(HCRYPTHASH, PBYTE, DWORD, DWORD); + BOOL CryptHashSessionKey(HCRYPTHASH, HCRYPTKEY, DWORD); + BOOL CryptGetHashValue(HCRYPTHASH, DWORD, PBYTE, PDWORD); + BOOL CryptDestroyHash(HCRYPTHASH); + BOOL CryptSignHashA(HCRYPTHASH, DWORD, LPCSTR, DWORD, PBYTE, PDWORD); + BOOL CryptSignHashW(HCRYPTHASH, DWORD, LPCWSTR, DWORD, PBYTE, PDWORD); + BOOL CryptVerifySignatureA(HCRYPTHASH, PBYTE, DWORD, HCRYPTKEY, LPCSTR, + DWORD); + BOOL CryptVerifySignatureW(HCRYPTHASH, PBYTE, DWORD, HCRYPTKEY, LPCWSTR, + DWORD); + BOOL CryptSetProviderA(LPCSTR, DWORD); + BOOL CryptSetProviderW(LPCWSTR, DWORD); +} + +version (Unicode) { + alias CertNameToStrW CertNameToStr; + alias CryptAcquireContextW CryptAcquireContext; + alias CryptSignHashW CryptSignHash; + alias CryptVerifySignatureW CryptVerifySignature; + alias CryptSetProviderW CryptSetProvider; + alias CertOpenSystemStoreW CertOpenSystemStore; + /+alias CERT_FIND_SUBJECT_STR_W CERT_FIND_SUBJECT_STR; + alias CERT_FIND_ISSUER_STR_W CERT_FIND_ISSUER_STR;+/ +} else { + alias CertNameToStrA CertNameToStr; + alias CryptAcquireContextA CryptAcquireContext; + alias CryptSignHashA CryptSignHash; + alias CryptVerifySignatureA CryptVerifySignature; + alias CryptSetProviderA CryptSetProvider; + alias CertOpenSystemStoreA CertOpenSystemStore; + /+alias CERT_FIND_SUBJECT_STR_A CERT_FIND_SUBJECT_STR; + alias CERT_FIND_ISSUER_STR_A CERT_FIND_ISSUER_STR;+/ +} diff --git a/src/urt/internal/sys/windows/windef.d b/src/urt/internal/sys/windows/windef.d new file mode 100644 index 0000000..20767e1 --- /dev/null +++ b/src/urt/internal/sys/windows/windef.d @@ -0,0 +1,151 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * Authors: Stewart Gordon + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_windef.d) + */ +module urt.internal.sys.windows.windef; +version (Windows): + +public import urt.internal.sys.windows.winnt; +import urt.internal.sys.windows.w32api; + +enum size_t MAX_PATH = 260; + +pure nothrow @nogc { + ushort MAKEWORD(ubyte a, ubyte b) { + return cast(ushort) ((b << 8) | a); + } + + ushort MAKEWORD(ushort a, ushort b) { + assert((a & 0xFF00) == 0); + assert((b & 0xFF00) == 0); + return MAKEWORD(cast(ubyte)a, cast(ubyte)b); + } + + uint MAKELONG(ushort a, ushort b) { + return cast(uint) ((b << 16) | a); + } + + uint MAKELONG(uint a, uint b) { + assert((a & 0xFFFF0000) == 0); + assert((b & 0xFFFF0000) == 0); + return MAKELONG(cast(ushort)a, cast(ushort)b); + } + + ushort LOWORD(ulong l) { + return cast(ushort) l; + } + + ushort HIWORD(ulong l) { + return cast(ushort) (l >>> 16); + } + + ubyte LOBYTE(ushort w) { + return cast(ubyte) w; + } + + ubyte HIBYTE(ushort w) { + return cast(ubyte) (w >>> 8); + } +} + +enum NULL = null; +static assert (is(typeof({ + void test(int* p) {} + test(NULL); +}))); + +alias ubyte BYTE; +alias ubyte* PBYTE, LPBYTE; +alias ushort USHORT, WORD, ATOM; +alias ushort* PUSHORT, PWORD, LPWORD; +alias uint ULONG, DWORD, UINT, COLORREF; +alias uint* PULONG, PDWORD, LPDWORD, PUINT, LPUINT, LPCOLORREF; +alias int WINBOOL, BOOL, INT, LONG, HFILE, HRESULT; +alias int* PWINBOOL, LPWINBOOL, PBOOL, LPBOOL, PINT, LPINT, LPLONG; +alias float FLOAT; +alias float* PFLOAT; +alias const(void)* PCVOID, LPCVOID; + +alias UINT_PTR WPARAM; +alias LONG_PTR LPARAM, LRESULT; + +alias HHOOK = HANDLE; +alias HGLOBAL = HANDLE; +alias HLOCAL = HANDLE; +alias GLOBALHANDLE = HANDLE; +alias LOCALHANDLE = HANDLE; +alias HGDIOBJ = HANDLE; +alias HACCEL = HANDLE; +alias HBITMAP = HGDIOBJ; +alias HBRUSH = HGDIOBJ; +alias HCOLORSPACE = HANDLE; +alias HDC = HANDLE; +alias HGLRC = HANDLE; +alias HDESK = HANDLE; +alias HENHMETAFILE = HANDLE; +alias HFONT = HGDIOBJ; +alias HICON = HANDLE; +alias HINSTANCE = HANDLE; +alias HKEY = HANDLE; +alias HMENU = HANDLE; +alias HMETAFILE = HANDLE; +alias HMODULE = HANDLE; +alias HMONITOR = HANDLE; +alias HPALETTE = HANDLE; +alias HPEN = HGDIOBJ; +alias HRGN = HGDIOBJ; +alias HRSRC = HANDLE; +alias HSTR = HANDLE; +alias HTASK = HANDLE; +alias HWND = HANDLE; +alias HWINSTA = HANDLE; +alias HKL = HANDLE; +alias HCURSOR = HANDLE; +alias HKEY* PHKEY; + +static if (_WIN32_WINNT >= 0x500) { + alias HTERMINAL = HANDLE; + alias HWINEVENTHOOK = HANDLE; +} + +alias extern (Windows) INT_PTR function() nothrow FARPROC, NEARPROC, PROC; + +struct RECT { + LONG left; + LONG top; + LONG right; + LONG bottom; +} +alias RECT RECTL; +alias RECT* PRECT, NPRECT, LPRECT, PRECTL, LPRECTL; +alias const(RECT)* LPCRECT, LPCRECTL; + +struct POINT { + LONG x; + LONG y; +} +alias POINT POINTL; +alias POINT* PPOINT, NPPOINT, LPPOINT, PPOINTL, LPPOINTL; + +struct SIZE { + LONG cx; + LONG cy; +} +alias SIZE SIZEL; +alias SIZE* PSIZE, LPSIZE, PSIZEL, LPSIZEL; + +struct POINTS { + SHORT x; + SHORT y; +} +alias POINTS* PPOINTS, LPPOINTS; + +enum : BOOL { + FALSE = 0, + TRUE = 1 +} diff --git a/src/urt/internal/sys/windows/winerror.d b/src/urt/internal/sys/windows/winerror.d new file mode 100644 index 0000000..6178028 --- /dev/null +++ b/src/urt/internal/sys/windows/winerror.d @@ -0,0 +1,2312 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_winerror.d) + */ +module urt.internal.sys.windows.winerror; +version (Windows): + +/* Comments from the Mingw header: + * WAIT_TIMEOUT is also defined in winbase.h + */ + +import urt.internal.sys.windows.windef; + +alias int SCODE; // was in urt.internal.sys.windows.wtypes. + +enum : uint { + ERROR_SUCCESS = 0, + NO_ERROR = 0, + ERROR_INVALID_FUNCTION, + ERROR_FILE_NOT_FOUND, + ERROR_PATH_NOT_FOUND, + ERROR_TOO_MANY_OPEN_FILES, + ERROR_ACCESS_DENIED, + ERROR_INVALID_HANDLE, + ERROR_ARENA_TRASHED, + ERROR_NOT_ENOUGH_MEMORY, + ERROR_INVALID_BLOCK, + ERROR_BAD_ENVIRONMENT, + ERROR_BAD_FORMAT, + ERROR_INVALID_ACCESS, + ERROR_INVALID_DATA, + ERROR_OUTOFMEMORY, + ERROR_INVALID_DRIVE, + ERROR_CURRENT_DIRECTORY, + ERROR_NOT_SAME_DEVICE, + ERROR_NO_MORE_FILES, + ERROR_WRITE_PROTECT, + ERROR_BAD_UNIT, + ERROR_NOT_READY, + ERROR_BAD_COMMAND, + ERROR_CRC, + ERROR_BAD_LENGTH, + ERROR_SEEK, + ERROR_NOT_DOS_DISK, + ERROR_SECTOR_NOT_FOUND, + ERROR_OUT_OF_PAPER, + ERROR_WRITE_FAULT, + ERROR_READ_FAULT, + ERROR_GEN_FAILURE, + ERROR_SHARING_VIOLATION, + ERROR_LOCK_VIOLATION, + ERROR_WRONG_DISK, // = 34 + ERROR_SHARING_BUFFER_EXCEEDED = 36, + ERROR_HANDLE_EOF = 38, + ERROR_HANDLE_DISK_FULL, // = 39 + ERROR_NOT_SUPPORTED = 50, + ERROR_REM_NOT_LIST, + ERROR_DUP_NAME, + ERROR_BAD_NETPATH, + ERROR_NETWORK_BUSY, + ERROR_DEV_NOT_EXIST, + ERROR_TOO_MANY_CMDS, + ERROR_ADAP_HDW_ERR, + ERROR_BAD_NET_RESP, + ERROR_UNEXP_NET_ERR, + ERROR_BAD_REM_ADAP, + ERROR_PRINTQ_FULL, + ERROR_NO_SPOOL_SPACE, + ERROR_PRINT_CANCELLED, + ERROR_NETNAME_DELETED, + ERROR_NETWORK_ACCESS_DENIED, + ERROR_BAD_DEV_TYPE, + ERROR_BAD_NET_NAME, + ERROR_TOO_MANY_NAMES, + ERROR_TOO_MANY_SESS, + ERROR_SHARING_PAUSED, + ERROR_REQ_NOT_ACCEP, + ERROR_REDIR_PAUSED, // = 72 + ERROR_FILE_EXISTS = 80, + ERROR_CANNOT_MAKE = 82, + ERROR_FAIL_I24, + ERROR_OUT_OF_STRUCTURES, + ERROR_ALREADY_ASSIGNED, + ERROR_INVALID_PASSWORD, + ERROR_INVALID_PARAMETER, + ERROR_NET_WRITE_FAULT, + ERROR_NO_PROC_SLOTS, // = 89 + ERROR_TOO_MANY_SEMAPHORES = 100, + ERROR_EXCL_SEM_ALREADY_OWNED, + ERROR_SEM_IS_SET, + ERROR_TOO_MANY_SEM_REQUESTS, + ERROR_INVALID_AT_INTERRUPT_TIME, + ERROR_SEM_OWNER_DIED, + ERROR_SEM_USER_LIMIT, + ERROR_DISK_CHANGE, + ERROR_DRIVE_LOCKED, + ERROR_BROKEN_PIPE, + ERROR_OPEN_FAILED, + ERROR_BUFFER_OVERFLOW, + ERROR_DISK_FULL, + ERROR_NO_MORE_SEARCH_HANDLES, + ERROR_INVALID_TARGET_HANDLE, // = 114 + ERROR_INVALID_CATEGORY = 117, + ERROR_INVALID_VERIFY_SWITCH, + ERROR_BAD_DRIVER_LEVEL, + ERROR_CALL_NOT_IMPLEMENTED, + ERROR_SEM_TIMEOUT, + ERROR_INSUFFICIENT_BUFFER, + ERROR_INVALID_NAME, + ERROR_INVALID_LEVEL, + ERROR_NO_VOLUME_LABEL, + ERROR_MOD_NOT_FOUND, + ERROR_PROC_NOT_FOUND, + ERROR_WAIT_NO_CHILDREN, + ERROR_CHILD_NOT_COMPLETE, + ERROR_DIRECT_ACCESS_HANDLE, + ERROR_NEGATIVE_SEEK, + ERROR_SEEK_ON_DEVICE, + ERROR_IS_JOIN_TARGET, + ERROR_IS_JOINED, + ERROR_IS_SUBSTED, + ERROR_NOT_JOINED, + ERROR_NOT_SUBSTED, + ERROR_JOIN_TO_JOIN, + ERROR_SUBST_TO_SUBST, + ERROR_JOIN_TO_SUBST, + ERROR_SUBST_TO_JOIN, + ERROR_BUSY_DRIVE, + ERROR_SAME_DRIVE, + ERROR_DIR_NOT_ROOT, + ERROR_DIR_NOT_EMPTY, + ERROR_IS_SUBST_PATH, + ERROR_IS_JOIN_PATH, + ERROR_PATH_BUSY, + ERROR_IS_SUBST_TARGET, + ERROR_SYSTEM_TRACE, + ERROR_INVALID_EVENT_COUNT, + ERROR_TOO_MANY_MUXWAITERS, + ERROR_INVALID_LIST_FORMAT, + ERROR_LABEL_TOO_LONG, + ERROR_TOO_MANY_TCBS, + ERROR_SIGNAL_REFUSED, + ERROR_DISCARDED, + ERROR_NOT_LOCKED, + ERROR_BAD_THREADID_ADDR, + ERROR_BAD_ARGUMENTS, + ERROR_BAD_PATHNAME, + ERROR_SIGNAL_PENDING, // = 162 + ERROR_MAX_THRDS_REACHED = 164, + ERROR_LOCK_FAILED = 167, + ERROR_BUSY = 170, + ERROR_CANCEL_VIOLATION = 173, + ERROR_ATOMIC_LOCKS_NOT_SUPPORTED, // = 174 + ERROR_INVALID_SEGMENT_NUMBER = 180, + ERROR_INVALID_ORDINAL = 182, + ERROR_ALREADY_EXISTS, // = 183 + ERROR_INVALID_FLAG_NUMBER = 186, + ERROR_SEM_NOT_FOUND, + ERROR_INVALID_STARTING_CODESEG, + ERROR_INVALID_STACKSEG, + ERROR_INVALID_MODULETYPE, + ERROR_INVALID_EXE_SIGNATURE, + ERROR_EXE_MARKED_INVALID, + ERROR_BAD_EXE_FORMAT, + ERROR_ITERATED_DATA_EXCEEDS_64k, + ERROR_INVALID_MINALLOCSIZE, + ERROR_DYNLINK_FROM_INVALID_RING, + ERROR_IOPL_NOT_ENABLED, + ERROR_INVALID_SEGDPL, + ERROR_AUTODATASEG_EXCEEDS_64k, + ERROR_RING2SEG_MUST_BE_MOVABLE, + ERROR_RELOC_CHAIN_XEEDS_SEGLIM, + ERROR_INFLOOP_IN_RELOC_CHAIN, + ERROR_ENVVAR_NOT_FOUND, // = 203 + ERROR_NO_SIGNAL_SENT = 205, + ERROR_FILENAME_EXCED_RANGE, + ERROR_RING2_STACK_IN_USE, + ERROR_META_EXPANSION_TOO_LONG, + ERROR_INVALID_SIGNAL_NUMBER, + ERROR_THREAD_1_INACTIVE, // = 210 + ERROR_LOCKED = 212, + ERROR_TOO_MANY_MODULES = 214, + ERROR_NESTING_NOT_ALLOWED, + ERROR_EXE_MACHINE_TYPE_MISMATCH, + ERROR_EXE_CANNOT_MODIFY_SIGNED_BINARY, + ERROR_EXE_CANNOT_MODIFY_STRONG_SIGNED_BINARY, // = 218 + ERROR_BAD_PIPE = 230, + ERROR_PIPE_BUSY, + ERROR_NO_DATA, + ERROR_PIPE_NOT_CONNECTED, + ERROR_MORE_DATA, // = 234 + ERROR_VC_DISCONNECTED = 240, + ERROR_INVALID_EA_NAME = 254, + ERROR_EA_LIST_INCONSISTENT, // = 255 + WAIT_TIMEOUT = 258, + ERROR_NO_MORE_ITEMS, // = 259 + ERROR_CANNOT_COPY = 266, + ERROR_DIRECTORY, // = 267 + ERROR_EAS_DIDNT_FIT = 275, + ERROR_EA_FILE_CORRUPT, + ERROR_EA_TABLE_FULL, + ERROR_INVALID_EA_HANDLE, // = 278 + ERROR_EAS_NOT_SUPPORTED = 282, + ERROR_NOT_OWNER = 288, + ERROR_TOO_MANY_POSTS = 298, + ERROR_PARTIAL_COPY, + ERROR_OPLOCK_NOT_GRANTED, + ERROR_INVALID_OPLOCK_PROTOCOL, + ERROR_DISK_TOO_FRAGMENTED, + ERROR_DELETE_PENDING, // = 303 + ERROR_MR_MID_NOT_FOUND = 317, + ERROR_SCOPE_NOT_FOUND, // = 318 + ERROR_INVALID_ADDRESS = 487, + ERROR_ARITHMETIC_OVERFLOW = 534, + ERROR_PIPE_CONNECTED, + ERROR_PIPE_LISTENING, // = 536 + ERROR_EA_ACCESS_DENIED = 994, + ERROR_OPERATION_ABORTED, + ERROR_IO_INCOMPLETE, + ERROR_IO_PENDING, + ERROR_NOACCESS, + ERROR_SWAPERROR, // = 999 + ERROR_STACK_OVERFLOW = 1001, + ERROR_INVALID_MESSAGE, + ERROR_CAN_NOT_COMPLETE, + ERROR_INVALID_FLAGS, + ERROR_UNRECOGNIZED_VOLUME, + ERROR_FILE_INVALID, + ERROR_FULLSCREEN_MODE, + ERROR_NO_TOKEN, + ERROR_BADDB, + ERROR_BADKEY, + ERROR_CANTOPEN, + ERROR_CANTREAD, + ERROR_CANTWRITE, + ERROR_REGISTRY_RECOVERED, + ERROR_REGISTRY_CORRUPT, + ERROR_REGISTRY_IO_FAILED, + ERROR_NOT_REGISTRY_FILE, + ERROR_KEY_DELETED, + ERROR_NO_LOG_SPACE, + ERROR_KEY_HAS_CHILDREN, + ERROR_CHILD_MUST_BE_VOLATILE, + ERROR_NOTIFY_ENUM_DIR, // = 1022 + ERROR_DEPENDENT_SERVICES_RUNNING = 1051, + ERROR_INVALID_SERVICE_CONTROL, + ERROR_SERVICE_REQUEST_TIMEOUT, + ERROR_SERVICE_NO_THREAD, + ERROR_SERVICE_DATABASE_LOCKED, + ERROR_SERVICE_ALREADY_RUNNING, + ERROR_INVALID_SERVICE_ACCOUNT, + ERROR_SERVICE_DISABLED, + ERROR_CIRCULAR_DEPENDENCY, + ERROR_SERVICE_DOES_NOT_EXIST, + ERROR_SERVICE_CANNOT_ACCEPT_CTRL, + ERROR_SERVICE_NOT_ACTIVE, + ERROR_FAILED_SERVICE_CONTROLLER_CONNECT, + ERROR_EXCEPTION_IN_SERVICE, + ERROR_DATABASE_DOES_NOT_EXIST, + ERROR_SERVICE_SPECIFIC_ERROR, + ERROR_PROCESS_ABORTED, + ERROR_SERVICE_DEPENDENCY_FAIL, + ERROR_SERVICE_LOGON_FAILED, + ERROR_SERVICE_START_HANG, + ERROR_INVALID_SERVICE_LOCK, + ERROR_SERVICE_MARKED_FOR_DELETE, + ERROR_SERVICE_EXISTS, + ERROR_ALREADY_RUNNING_LKG, + ERROR_SERVICE_DEPENDENCY_DELETED, + ERROR_BOOT_ALREADY_ACCEPTED, + ERROR_SERVICE_NEVER_STARTED, + ERROR_DUPLICATE_SERVICE_NAME, + ERROR_DIFFERENT_SERVICE_ACCOUNT, + ERROR_CANNOT_DETECT_DRIVER_FAILURE, + ERROR_CANNOT_DETECT_PROCESS_ABORT, + ERROR_NO_RECOVERY_PROGRAM, + ERROR_SERVICE_NOT_IN_EXE, + ERROR_NOT_SAFEBOOT_SERVICE, // = 1084 + ERROR_END_OF_MEDIA = 1100, + ERROR_FILEMARK_DETECTED, + ERROR_BEGINNING_OF_MEDIA, + ERROR_SETMARK_DETECTED, + ERROR_NO_DATA_DETECTED, + ERROR_PARTITION_FAILURE, + ERROR_INVALID_BLOCK_LENGTH, + ERROR_DEVICE_NOT_PARTITIONED, + ERROR_UNABLE_TO_LOCK_MEDIA, + ERROR_UNABLE_TO_UNLOAD_MEDIA, + ERROR_MEDIA_CHANGED, + ERROR_BUS_RESET, + ERROR_NO_MEDIA_IN_DRIVE, + ERROR_NO_UNICODE_TRANSLATION, + ERROR_DLL_INIT_FAILED, + ERROR_SHUTDOWN_IN_PROGRESS, + ERROR_NO_SHUTDOWN_IN_PROGRESS, + ERROR_IO_DEVICE, + ERROR_SERIAL_NO_DEVICE, + ERROR_IRQ_BUSY, + ERROR_MORE_WRITES, + ERROR_COUNTER_TIMEOUT, + ERROR_FLOPPY_ID_MARK_NOT_FOUND, + ERROR_FLOPPY_WRONG_CYLINDER, + ERROR_FLOPPY_UNKNOWN_ERROR, + ERROR_FLOPPY_BAD_REGISTERS, + ERROR_DISK_RECALIBRATE_FAILED, + ERROR_DISK_OPERATION_FAILED, + ERROR_DISK_RESET_FAILED, + ERROR_EOM_OVERFLOW, + ERROR_NOT_ENOUGH_SERVER_MEMORY, + ERROR_POSSIBLE_DEADLOCK, + ERROR_MAPPED_ALIGNMENT, // = 1132 + ERROR_SET_POWER_STATE_VETOED = 1140, + ERROR_SET_POWER_STATE_FAILED, + ERROR_TOO_MANY_LINKS, // = 1142 + ERROR_OLD_WIN_VERSION = 1150, + ERROR_APP_WRONG_OS, + ERROR_SINGLE_INSTANCE_APP, + ERROR_RMODE_APP, + ERROR_INVALID_DLL, + ERROR_NO_ASSOCIATION, + ERROR_DDE_FAIL, + ERROR_DLL_NOT_FOUND, + ERROR_NO_MORE_USER_HANDLES, + ERROR_MESSAGE_SYNC_ONLY, + ERROR_SOURCE_ELEMENT_EMPTY, + ERROR_DESTINATION_ELEMENT_FULL, + ERROR_ILLEGAL_ELEMENT_ADDRESS, + ERROR_MAGAZINE_NOT_PRESENT, + ERROR_DEVICE_REINITIALIZATION_NEEDED, + ERROR_DEVICE_REQUIRES_CLEANING, + ERROR_DEVICE_DOOR_OPEN, + ERROR_DEVICE_NOT_CONNECTED, + ERROR_NOT_FOUND, + ERROR_NO_MATCH, + ERROR_SET_NOT_FOUND, + ERROR_POINT_NOT_FOUND, + ERROR_NO_TRACKING_SERVICE, + ERROR_NO_VOLUME_ID, // = 1173 + ERROR_UNABLE_TO_REMOVE_REPLACED = 1175, + ERROR_UNABLE_TO_MOVE_REPLACEMENT, + ERROR_UNABLE_TO_MOVE_REPLACEMENT_2, + ERROR_JOURNAL_DELETE_IN_PROGRESS, + ERROR_JOURNAL_NOT_ACTIVE, + ERROR_POTENTIAL_FILE_FOUND, + ERROR_JOURNAL_ENTRY_DELETED, // = 1181 + ERROR_BAD_DEVICE = 1200, + ERROR_CONNECTION_UNAVAIL, + ERROR_DEVICE_ALREADY_REMEMBERED, + ERROR_NO_NET_OR_BAD_PATH, + ERROR_BAD_PROVIDER, + ERROR_CANNOT_OPEN_PROFILE, + ERROR_BAD_PROFILE, + ERROR_NOT_CONTAINER, + ERROR_EXTENDED_ERROR, + ERROR_INVALID_GROUPNAME, + ERROR_INVALID_COMPUTERNAME, + ERROR_INVALID_EVENTNAME, + ERROR_INVALID_DOMAINNAME, + ERROR_INVALID_SERVICENAME, + ERROR_INVALID_NETNAME, + ERROR_INVALID_SHARENAME, + ERROR_INVALID_PASSWORDNAME, + ERROR_INVALID_MESSAGENAME, + ERROR_INVALID_MESSAGEDEST, + ERROR_SESSION_CREDENTIAL_CONFLICT, + ERROR_REMOTE_SESSION_LIMIT_EXCEEDED, + ERROR_DUP_DOMAINNAME, + ERROR_NO_NETWORK, + ERROR_CANCELLED, + ERROR_USER_MAPPED_FILE, + ERROR_CONNECTION_REFUSED, + ERROR_GRACEFUL_DISCONNECT, + ERROR_ADDRESS_ALREADY_ASSOCIATED, + ERROR_ADDRESS_NOT_ASSOCIATED, + ERROR_CONNECTION_INVALID, + ERROR_CONNECTION_ACTIVE, + ERROR_NETWORK_UNREACHABLE, + ERROR_HOST_UNREACHABLE, + ERROR_PROTOCOL_UNREACHABLE, + ERROR_PORT_UNREACHABLE, + ERROR_REQUEST_ABORTED, + ERROR_CONNECTION_ABORTED, + ERROR_RETRY, + ERROR_CONNECTION_COUNT_LIMIT, + ERROR_LOGIN_TIME_RESTRICTION, + ERROR_LOGIN_WKSTA_RESTRICTION, + ERROR_INCORRECT_ADDRESS, + ERROR_ALREADY_REGISTERED, + ERROR_SERVICE_NOT_FOUND, + ERROR_NOT_AUTHENTICATED, + ERROR_NOT_LOGGED_ON, + ERROR_CONTINUE, + ERROR_ALREADY_INITIALIZED, + ERROR_NO_MORE_DEVICES, + ERROR_NO_SUCH_SITE, + ERROR_DOMAIN_CONTROLLER_EXISTS, + ERROR_ONLY_IF_CONNECTED, + ERROR_OVERRIDE_NOCHANGES, + ERROR_BAD_USER_PROFILE, + ERROR_NOT_SUPPORTED_ON_SBS, + ERROR_SERVER_SHUTDOWN_IN_PROGRESS, + ERROR_HOST_DOWN, + ERROR_NON_ACCOUNT_SID, + ERROR_NON_DOMAIN_SID, + ERROR_APPHELP_BLOCK, + ERROR_ACCESS_DISABLED_BY_POLICY, + ERROR_REG_NAT_CONSUMPTION, + ERROR_CSCSHARE_OFFLINE, + ERROR_PKINIT_FAILURE, + ERROR_SMARTCARD_SUBSYSTEM_FAILURE, + ERROR_DOWNGRADE_DETECTED, + SEC_E_SMARTCARD_CERT_REVOKED, + SEC_E_ISSUING_CA_UNTRUSTED, + SEC_E_REVOCATION_OFFLINE_C, + SEC_E_PKINIT_CLIENT_FAILUR, + SEC_E_SMARTCARD_CERT_EXPIRED, + ERROR_MACHINE_LOCKED, // = 1271 + ERROR_CALLBACK_SUPPLIED_INVALID_DATA = 1273, + ERROR_SYNC_FOREGROUND_REFRESH_REQUIRED, + ERROR_DRIVER_BLOCKED, + ERROR_INVALID_IMPORT_OF_NON_DLL, + ERROR_ACCESS_DISABLED_WEBBLADE, + ERROR_ACCESS_DISABLED_WEBBLADE_TAMPER, + ERROR_RECOVERY_FAILURE, + ERROR_ALREADY_FIBER, + ERROR_ALREADY_THREAD, + ERROR_STACK_BUFFER_OVERRUN, + ERROR_PARAMETER_QUOTA_EXCEEDED, + ERROR_DEBUGGER_INACTIVE, // = 1284 + ERROR_NOT_ALL_ASSIGNED = 1300, + ERROR_SOME_NOT_MAPPED, + ERROR_NO_QUOTAS_FOR_ACCOUNT, + ERROR_LOCAL_USER_SESSION_KEY, + ERROR_NULL_LM_PASSWORD, + ERROR_UNKNOWN_REVISION, + ERROR_REVISION_MISMATCH, + ERROR_INVALID_OWNER, + ERROR_INVALID_PRIMARY_GROUP, + ERROR_NO_IMPERSONATION_TOKEN, + ERROR_CANT_DISABLE_MANDATORY, + ERROR_NO_LOGON_SERVERS, + ERROR_NO_SUCH_LOGON_SESSION, + ERROR_NO_SUCH_PRIVILEGE, + ERROR_PRIVILEGE_NOT_HELD, + ERROR_INVALID_ACCOUNT_NAME, + ERROR_USER_EXISTS, + ERROR_NO_SUCH_USER, + ERROR_GROUP_EXISTS, + ERROR_NO_SUCH_GROUP, + ERROR_MEMBER_IN_GROUP, + ERROR_MEMBER_NOT_IN_GROUP, + ERROR_LAST_ADMIN, + ERROR_WRONG_PASSWORD, + ERROR_ILL_FORMED_PASSWORD, + ERROR_PASSWORD_RESTRICTION, + ERROR_LOGON_FAILURE, + ERROR_ACCOUNT_RESTRICTION, + ERROR_INVALID_LOGON_HOURS, + ERROR_INVALID_WORKSTATION, + ERROR_PASSWORD_EXPIRED, + ERROR_ACCOUNT_DISABLED, + ERROR_NONE_MAPPED, + ERROR_TOO_MANY_LUIDS_REQUESTED, + ERROR_LUIDS_EXHAUSTED, + ERROR_INVALID_SUB_AUTHORITY, + ERROR_INVALID_ACL, + ERROR_INVALID_SID, + ERROR_INVALID_SECURITY_DESCR, // = 1338 + ERROR_BAD_INHERITANCE_ACL = 1340, + ERROR_SERVER_DISABLED, + ERROR_SERVER_NOT_DISABLED, + ERROR_INVALID_ID_AUTHORITY, + ERROR_ALLOTTED_SPACE_EXCEEDED, + ERROR_INVALID_GROUP_ATTRIBUTES, + ERROR_BAD_IMPERSONATION_LEVEL, + ERROR_CANT_OPEN_ANONYMOUS, + ERROR_BAD_VALIDATION_CLASS, + ERROR_BAD_TOKEN_TYPE, + ERROR_NO_SECURITY_ON_OBJECT, + ERROR_CANT_ACCESS_DOMAIN_INFO, + ERROR_INVALID_SERVER_STATE, + ERROR_INVALID_DOMAIN_STATE, + ERROR_INVALID_DOMAIN_ROLE, + ERROR_NO_SUCH_DOMAIN, + ERROR_DOMAIN_EXISTS, + ERROR_DOMAIN_LIMIT_EXCEEDED, + ERROR_INTERNAL_DB_CORRUPTION, + ERROR_INTERNAL_ERROR, + ERROR_GENERIC_NOT_MAPPED, + ERROR_BAD_DESCRIPTOR_FORMAT, + ERROR_NOT_LOGON_PROCESS, + ERROR_LOGON_SESSION_EXISTS, + ERROR_NO_SUCH_PACKAGE, + ERROR_BAD_LOGON_SESSION_STATE, + ERROR_LOGON_SESSION_COLLISION, + ERROR_INVALID_LOGON_TYPE, + ERROR_CANNOT_IMPERSONATE, + ERROR_RXACT_INVALID_STATE, + ERROR_RXACT_COMMIT_FAILURE, + ERROR_SPECIAL_ACCOUNT, + ERROR_SPECIAL_GROUP, + ERROR_SPECIAL_USER, + ERROR_MEMBERS_PRIMARY_GROUP, + ERROR_TOKEN_ALREADY_IN_USE, + ERROR_NO_SUCH_ALIAS, + ERROR_MEMBER_NOT_IN_ALIAS, + ERROR_MEMBER_IN_ALIAS, + ERROR_ALIAS_EXISTS, + ERROR_LOGON_NOT_GRANTED, + ERROR_TOO_MANY_SECRETS, + ERROR_SECRET_TOO_LONG, + ERROR_INTERNAL_DB_ERROR, + ERROR_TOO_MANY_CONTEXT_IDS, + ERROR_LOGON_TYPE_NOT_GRANTED, + ERROR_NT_CROSS_ENCRYPTION_REQUIRED, + ERROR_NO_SUCH_MEMBER, + ERROR_INVALID_MEMBER, + ERROR_TOO_MANY_SIDS, + ERROR_LM_CROSS_ENCRYPTION_REQUIRED, + ERROR_NO_INHERITANCE, + ERROR_FILE_CORRUPT, + ERROR_DISK_CORRUPT, + ERROR_NO_USER_SESSION_KEY, + ERROR_LICENSE_QUOTA_EXCEEDED, + ERROR_WRONG_TARGET_NAME, + ERROR_MUTUAL_AUTH_FAILED, + ERROR_TIME_SKEW, + ERROR_CURRENT_DOMAIN_NOT_ALLOWED, + ERROR_INVALID_WINDOW_HANDLE, + ERROR_INVALID_MENU_HANDLE, + ERROR_INVALID_CURSOR_HANDLE, + ERROR_INVALID_ACCEL_HANDLE, + ERROR_INVALID_HOOK_HANDLE, + ERROR_INVALID_DWP_HANDLE, + ERROR_TLW_WITH_WSCHILD, + ERROR_CANNOT_FIND_WND_CLASS, + ERROR_WINDOW_OF_OTHER_THREAD, + ERROR_HOTKEY_ALREADY_REGISTERED, + ERROR_CLASS_ALREADY_EXISTS, + ERROR_CLASS_DOES_NOT_EXIST, + ERROR_CLASS_HAS_WINDOWS, + ERROR_INVALID_INDEX, + ERROR_INVALID_ICON_HANDLE, + ERROR_PRIVATE_DIALOG_INDEX, + ERROR_LISTBOX_ID_NOT_FOUND, + ERROR_NO_WILDCARD_CHARACTERS, + ERROR_CLIPBOARD_NOT_OPEN, + ERROR_HOTKEY_NOT_REGISTERED, + ERROR_WINDOW_NOT_DIALOG, + ERROR_CONTROL_ID_NOT_FOUND, + ERROR_INVALID_COMBOBOX_MESSAGE, + ERROR_WINDOW_NOT_COMBOBOX, + ERROR_INVALID_EDIT_HEIGHT, + ERROR_DC_NOT_FOUND, + ERROR_INVALID_HOOK_FILTER, + ERROR_INVALID_FILTER_PROC, + ERROR_HOOK_NEEDS_HMOD, + ERROR_GLOBAL_ONLY_HOOK, + ERROR_JOURNAL_HOOK_SET, + ERROR_HOOK_NOT_INSTALLED, + ERROR_INVALID_LB_MESSAGE, + ERROR_SETCOUNT_ON_BAD_LB, + ERROR_LB_WITHOUT_TABSTOPS, + ERROR_DESTROY_OBJECT_OF_OTHER_THREAD, + ERROR_CHILD_WINDOW_MENU, + ERROR_NO_SYSTEM_MENU, + ERROR_INVALID_MSGBOX_STYLE, + ERROR_INVALID_SPI_VALUE, + ERROR_SCREEN_ALREADY_LOCKED, + ERROR_HWNDS_HAVE_DIFF_PARENT, + ERROR_NOT_CHILD_WINDOW, + ERROR_INVALID_GW_COMMAND, + ERROR_INVALID_THREAD_ID, + ERROR_NON_MDICHILD_WINDOW, + ERROR_POPUP_ALREADY_ACTIVE, + ERROR_NO_SCROLLBARS, + ERROR_INVALID_SCROLLBAR_RANGE, + ERROR_INVALID_SHOWWIN_COMMAND, + ERROR_NO_SYSTEM_RESOURCES, + ERROR_NONPAGED_SYSTEM_RESOURCES, + ERROR_PAGED_SYSTEM_RESOURCES, + ERROR_WORKING_SET_QUOTA, + ERROR_PAGEFILE_QUOTA, + ERROR_COMMITMENT_LIMIT, + ERROR_MENU_ITEM_NOT_FOUND, + ERROR_INVALID_KEYBOARD_HANDLE, + ERROR_HOOK_TYPE_NOT_ALLOWED, + ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION, + ERROR_TIMEOUT, + ERROR_INVALID_MONITOR_HANDLE, // = 1461 + ERROR_EVENTLOG_FILE_CORRUPT = 1500, + ERROR_EVENTLOG_CANT_START, + ERROR_LOG_FILE_FULL, + ERROR_EVENTLOG_FILE_CHANGED, // = 1503 + ERROR_INSTALL_SERVICE_FAILURE = 1601, + ERROR_INSTALL_USEREXIT, + ERROR_INSTALL_FAILURE, + ERROR_INSTALL_SUSPEND, + ERROR_UNKNOWN_PRODUCT, + ERROR_UNKNOWN_FEATURE, + ERROR_UNKNOWN_COMPONENT, + ERROR_UNKNOWN_PROPERTY, + ERROR_INVALID_HANDLE_STATE, + ERROR_BAD_CONFIGURATION, + ERROR_INDEX_ABSENT, + ERROR_INSTALL_SOURCE_ABSENT, + ERROR_INSTALL_PACKAGE_VERSION, + ERROR_PRODUCT_UNINSTALLED, + ERROR_BAD_QUERY_SYNTAX, + ERROR_INVALID_FIELD, + ERROR_DEVICE_REMOVED, + ERROR_INSTALL_ALREADY_RUNNING, + ERROR_INSTALL_PACKAGE_OPEN_FAILED, + ERROR_INSTALL_PACKAGE_INVALID, + ERROR_INSTALL_UI_FAILURE, + ERROR_INSTALL_LOG_FAILURE, + ERROR_INSTALL_LANGUAGE_UNSUPPORTED, + ERROR_INSTALL_TRANSFORM_FAILURE, + ERROR_INSTALL_PACKAGE_REJECTED, + ERROR_FUNCTION_NOT_CALLED, + ERROR_FUNCTION_FAILED, + ERROR_INVALID_TABLE, + ERROR_DATATYPE_MISMATCH, + ERROR_UNSUPPORTED_TYPE, + ERROR_CREATE_FAILED, + ERROR_INSTALL_TEMP_UNWRITABLE, + ERROR_INSTALL_PLATFORM_UNSUPPORTED, + ERROR_INSTALL_NOTUSED, + ERROR_PATCH_PACKAGE_OPEN_FAILED, + ERROR_PATCH_PACKAGE_INVALID, + ERROR_PATCH_PACKAGE_UNSUPPORTED, + ERROR_PRODUCT_VERSION, + ERROR_INVALID_COMMAND_LINE, + ERROR_INSTALL_REMOTE_DISALLOWED, + ERROR_SUCCESS_REBOOT_INITIATED, + ERROR_PATCH_TARGET_NOT_FOUND, + ERROR_PATCH_PACKAGE_REJECTED, + ERROR_INSTALL_TRANSFORM_REJECTED, + ERROR_INSTALL_REMOTE_PROHIBITED, // = 1645 + RPC_S_INVALID_STRING_BINDING = 1700, + RPC_S_WRONG_KIND_OF_BINDING, + RPC_S_INVALID_BINDING, + RPC_S_PROTSEQ_NOT_SUPPORTED, + RPC_S_INVALID_RPC_PROTSEQ, + RPC_S_INVALID_STRING_UUID, + RPC_S_INVALID_ENDPOINT_FORMAT, + RPC_S_INVALID_NET_ADDR, + RPC_S_NO_ENDPOINT_FOUND, + RPC_S_INVALID_TIMEOUT, + RPC_S_OBJECT_NOT_FOUND, + RPC_S_ALREADY_REGISTERED, + RPC_S_TYPE_ALREADY_REGISTERED, + RPC_S_ALREADY_LISTENING, + RPC_S_NO_PROTSEQS_REGISTERED, + RPC_S_NOT_LISTENING, + RPC_S_UNKNOWN_MGR_TYPE, + RPC_S_UNKNOWN_IF, + RPC_S_NO_BINDINGS, + RPC_S_NO_PROTSEQS, + RPC_S_CANT_CREATE_ENDPOINT, + RPC_S_OUT_OF_RESOURCES, + RPC_S_SERVER_UNAVAILABLE, + RPC_S_SERVER_TOO_BUSY, + RPC_S_INVALID_NETWORK_OPTIONS, + RPC_S_NO_CALL_ACTIVE, + RPC_S_CALL_FAILED, + RPC_S_CALL_FAILED_DNE, + RPC_S_PROTOCOL_ERROR, // = 1728 + RPC_S_UNSUPPORTED_TRANS_SYN = 1730, + RPC_S_UNSUPPORTED_TYPE = 1732, + RPC_S_INVALID_TAG, + RPC_S_INVALID_BOUND, + RPC_S_NO_ENTRY_NAME, + RPC_S_INVALID_NAME_SYNTAX, + RPC_S_UNSUPPORTED_NAME_SYNTAX, // = 1737 + RPC_S_UUID_NO_ADDRESS = 1739, + RPC_S_DUPLICATE_ENDPOINT, + RPC_S_UNKNOWN_AUTHN_TYPE, + RPC_S_MAX_CALLS_TOO_SMALL, + RPC_S_STRING_TOO_LONG, + RPC_S_PROTSEQ_NOT_FOUND, + RPC_S_PROCNUM_OUT_OF_RANGE, + RPC_S_BINDING_HAS_NO_AUTH, + RPC_S_UNKNOWN_AUTHN_SERVICE, + RPC_S_UNKNOWN_AUTHN_LEVEL, + RPC_S_INVALID_AUTH_IDENTITY, + RPC_S_UNKNOWN_AUTHZ_SERVICE, + EPT_S_INVALID_ENTRY, + EPT_S_CANT_PERFORM_OP, + EPT_S_NOT_REGISTERED, + RPC_S_NOTHING_TO_EXPORT, + RPC_S_INCOMPLETE_NAME, + RPC_S_INVALID_VERS_OPTION, + RPC_S_NO_MORE_MEMBERS, + RPC_S_NOT_ALL_OBJS_UNEXPORTED, + RPC_S_INTERFACE_NOT_FOUND, + RPC_S_ENTRY_ALREADY_EXISTS, + RPC_S_ENTRY_NOT_FOUND, + RPC_S_NAME_SERVICE_UNAVAILABLE, + RPC_S_INVALID_NAF_ID, + RPC_S_CANNOT_SUPPORT, + RPC_S_NO_CONTEXT_AVAILABLE, + RPC_S_INTERNAL_ERROR, + RPC_S_ZERO_DIVIDE, + RPC_S_ADDRESS_ERROR, + RPC_S_FP_DIV_ZERO, + RPC_S_FP_UNDERFLOW, + RPC_S_FP_OVERFLOW, + RPC_X_NO_MORE_ENTRIES, + RPC_X_SS_CHAR_TRANS_OPEN_FAIL, + RPC_X_SS_CHAR_TRANS_SHORT_FILE, + RPC_X_SS_IN_NULL_CONTEXT, // = 1775 + RPC_X_SS_CONTEXT_DAMAGED = 1777, + RPC_X_SS_HANDLES_MISMATCH, + RPC_X_SS_CANNOT_GET_CALL_HANDLE, + RPC_X_NULL_REF_POINTER, + RPC_X_ENUM_VALUE_OUT_OF_RANGE, + RPC_X_BYTE_COUNT_TOO_SMALL, + RPC_X_BAD_STUB_DATA, + ERROR_INVALID_USER_BUFFER, + ERROR_UNRECOGNIZED_MEDIA, + ERROR_NO_TRUST_LSA_SECRET, + ERROR_NO_TRUST_SAM_ACCOUNT, + ERROR_TRUSTED_DOMAIN_FAILURE, + ERROR_TRUSTED_RELATIONSHIP_FAILURE, + ERROR_TRUST_FAILURE, + RPC_S_CALL_IN_PROGRESS, + ERROR_NETLOGON_NOT_STARTED, + ERROR_ACCOUNT_EXPIRED, + ERROR_REDIRECTOR_HAS_OPEN_HANDLES, + ERROR_PRINTER_DRIVER_ALREADY_INSTALLED, + ERROR_UNKNOWN_PORT, + ERROR_UNKNOWN_PRINTER_DRIVER, + ERROR_UNKNOWN_PRINTPROCESSOR, + ERROR_INVALID_SEPARATOR_FILE, + ERROR_INVALID_PRIORITY, + ERROR_INVALID_PRINTER_NAME, + ERROR_PRINTER_ALREADY_EXISTS, + ERROR_INVALID_PRINTER_COMMAND, + ERROR_INVALID_DATATYPE, + ERROR_INVALID_ENVIRONMENT, + RPC_S_NO_MORE_BINDINGS, + ERROR_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT, + ERROR_NOLOGON_WORKSTATION_TRUST_ACCOUNT, + ERROR_NOLOGON_SERVER_TRUST_ACCOUNT, + ERROR_DOMAIN_TRUST_INCONSISTENT, + ERROR_SERVER_HAS_OPEN_HANDLES, + ERROR_RESOURCE_DATA_NOT_FOUND, + ERROR_RESOURCE_TYPE_NOT_FOUND, + ERROR_RESOURCE_NAME_NOT_FOUND, + ERROR_RESOURCE_LANG_NOT_FOUND, + ERROR_NOT_ENOUGH_QUOTA, + RPC_S_NO_INTERFACES, + RPC_S_CALL_CANCELLED, + RPC_S_BINDING_INCOMPLETE, + RPC_S_COMM_FAILURE, + RPC_S_UNSUPPORTED_AUTHN_LEVEL, + RPC_S_NO_PRINC_NAME, + RPC_S_NOT_RPC_ERROR, + RPC_S_UUID_LOCAL_ONLY, + RPC_S_SEC_PKG_ERROR, + RPC_S_NOT_CANCELLED, + RPC_X_INVALID_ES_ACTION, + RPC_X_WRONG_ES_VERSION, + RPC_X_WRONG_STUB_VERSION, + RPC_X_INVALID_PIPE_OBJECT, + RPC_X_WRONG_PIPE_ORDER, + RPC_X_WRONG_PIPE_VERSION, // = 1832 + RPC_S_GROUP_MEMBER_NOT_FOUND = 1898, + EPT_S_CANT_CREATE, + RPC_S_INVALID_OBJECT, + ERROR_INVALID_TIME, + ERROR_INVALID_FORM_NAME, + ERROR_INVALID_FORM_SIZE, + ERROR_ALREADY_WAITING, + ERROR_PRINTER_DELETED, + ERROR_INVALID_PRINTER_STATE, + ERROR_PASSWORD_MUST_CHANGE, + ERROR_DOMAIN_CONTROLLER_NOT_FOUND, + ERROR_ACCOUNT_LOCKED_OUT, + OR_INVALID_OXID, + OR_INVALID_OID, + OR_INVALID_SET, + RPC_S_SEND_INCOMPLETE, + RPC_S_INVALID_ASYNC_HANDLE, + RPC_S_INVALID_ASYNC_CALL, + RPC_X_PIPE_CLOSED, + RPC_X_PIPE_DISCIPLINE_ERROR, + RPC_X_PIPE_EMPTY, + ERROR_NO_SITENAME, + ERROR_CANT_ACCESS_FILE, + ERROR_CANT_RESOLVE_FILENAME, + RPC_S_ENTRY_TYPE_MISMATCH, + RPC_S_NOT_ALL_OBJS_EXPORTED, + RPC_S_INTERFACE_NOT_EXPORTED, + RPC_S_PROFILE_NOT_ADDED, + RPC_S_PRF_ELT_NOT_ADDED, + RPC_S_PRF_ELT_NOT_REMOVED, + RPC_S_GRP_ELT_NOT_ADDED, + RPC_S_GRP_ELT_NOT_REMOVED, + ERROR_KM_DRIVER_BLOCKED, + ERROR_CONTEXT_EXPIRED, + ERROR_PER_USER_TRUST_QUOTA_EXCEEDED, + ERROR_ALL_USER_TRUST_QUOTA_EXCEEDED, + ERROR_USER_DELETE_TRUST_QUOTA_EXCEEDED, // = 1934 + ERROR_INVALID_PIXEL_FORMAT = 2000, + ERROR_BAD_DRIVER, + ERROR_INVALID_WINDOW_STYLE, + ERROR_METAFILE_NOT_SUPPORTED, + ERROR_TRANSFORM_NOT_SUPPORTED, + ERROR_CLIPPING_NOT_SUPPORTED, // = 2005 + ERROR_INVALID_CMM = 2010, + ERROR_INVALID_PROFILE, + ERROR_TAG_NOT_FOUND, + ERROR_TAG_NOT_PRESENT, + ERROR_DUPLICATE_TAG, + ERROR_PROFILE_NOT_ASSOCIATED_WITH_DEVICE, + ERROR_PROFILE_NOT_FOUND, + ERROR_INVALID_COLORSPACE, + ERROR_ICM_NOT_ENABLED, + ERROR_DELETING_ICM_XFORM, + ERROR_INVALID_TRANSFORM, + ERROR_COLORSPACE_MISMATCH, + ERROR_INVALID_COLORINDEX, // = 2022 + ERROR_CONNECTED_OTHER_PASSWORD = 2108, + ERROR_CONNECTED_OTHER_PASSWORD_DEFAULT, // = 2109 + ERROR_BAD_USERNAME = 2202, + ERROR_NOT_CONNECTED = 2250, + ERROR_OPEN_FILES = 2401, + ERROR_ACTIVE_CONNECTIONS, // = 2402 + ERROR_DEVICE_IN_USE = 2404, + ERROR_UNKNOWN_PRINT_MONITOR = 3000, + ERROR_PRINTER_DRIVER_IN_USE, + ERROR_SPOOL_FILE_NOT_FOUND, + ERROR_SPL_NO_STARTDOC, + ERROR_SPL_NO_ADDJOB, + ERROR_PRINT_PROCESSOR_ALREADY_INSTALLED, + ERROR_PRINT_MONITOR_ALREADY_INSTALLED, + ERROR_INVALID_PRINT_MONITOR, + ERROR_PRINT_MONITOR_IN_USE, + ERROR_PRINTER_HAS_JOBS_QUEUED, + ERROR_SUCCESS_REBOOT_REQUIRED, + ERROR_SUCCESS_RESTART_REQUIRED, + ERROR_PRINTER_NOT_FOUND, + ERROR_PRINTER_DRIVER_WARNED, + ERROR_PRINTER_DRIVER_BLOCKED, // = 3014 + ERROR_WINS_INTERNAL = 4000, + ERROR_CAN_NOT_DEL_LOCAL_WINS, + ERROR_STATIC_INIT, + ERROR_INC_BACKUP, + ERROR_FULL_BACKUP, + ERROR_REC_NON_EXISTENT, + ERROR_RPL_NOT_ALLOWED, // = 4006 + ERROR_DHCP_ADDRESS_CONFLICT = 4100, + ERROR_WMI_GUID_NOT_FOUND = 4200, + ERROR_WMI_INSTANCE_NOT_FOUND, + ERROR_WMI_ITEMID_NOT_FOUND, + ERROR_WMI_TRY_AGAIN, + ERROR_WMI_DP_NOT_FOUND, + ERROR_WMI_UNRESOLVED_INSTANCE_REF, + ERROR_WMI_ALREADY_ENABLED, + ERROR_WMI_GUID_DISCONNECTED, + ERROR_WMI_SERVER_UNAVAILABLE, + ERROR_WMI_DP_FAILED, + ERROR_WMI_INVALID_MOF, + ERROR_WMI_INVALID_REGINFO, + ERROR_WMI_ALREADY_DISABLED, + ERROR_WMI_READ_ONLY, + ERROR_WMI_SET_FAILURE, // = 4214 + ERROR_INVALID_MEDIA = 4300, + ERROR_INVALID_LIBRARY, + ERROR_INVALID_MEDIA_POOL, + ERROR_DRIVE_MEDIA_MISMATCH, + ERROR_MEDIA_OFFLINE, + ERROR_LIBRARY_OFFLINE, + ERROR_EMPTY, + ERROR_NOT_EMPTY, + ERROR_MEDIA_UNAVAILABLE, + ERROR_RESOURCE_DISABLED, + ERROR_INVALID_CLEANER, + ERROR_UNABLE_TO_CLEAN, + ERROR_OBJECT_NOT_FOUND, + ERROR_DATABASE_FAILURE, + ERROR_DATABASE_FULL, + ERROR_MEDIA_INCOMPATIBLE, + ERROR_RESOURCE_NOT_PRESENT, + ERROR_INVALID_OPERATION, + ERROR_MEDIA_NOT_AVAILABLE, + ERROR_DEVICE_NOT_AVAILABLE, + ERROR_REQUEST_REFUSED, + ERROR_INVALID_DRIVE_OBJECT, + ERROR_LIBRARY_FULL, + ERROR_MEDIUM_NOT_ACCESSIBLE, + ERROR_UNABLE_TO_LOAD_MEDIUM, + ERROR_UNABLE_TO_INVENTORY_DRIVE, + ERROR_UNABLE_TO_INVENTORY_SLOT, + ERROR_UNABLE_TO_INVENTORY_TRANSPORT, + ERROR_TRANSPORT_FULL, + ERROR_CONTROLLING_IEPORT, + ERROR_UNABLE_TO_EJECT_MOUNTED_MEDIA, + ERROR_CLEANER_SLOT_SET, + ERROR_CLEANER_SLOT_NOT_SET, + ERROR_CLEANER_CARTRIDGE_SPENT, + ERROR_UNEXPECTED_OMID, + ERROR_CANT_DELETE_LAST_ITEM, + ERROR_MESSAGE_EXCEEDS_MAX_SIZE, + ERROR_VOLUME_CONTAINS_SYS_FILES, + ERROR_INDIGENOUS_TYPE, + ERROR_NO_SUPPORTING_DRIVES, + ERROR_CLEANER_CARTRIDGE_INSTALLED, // = 4340 + ERROR_FILE_OFFLINE = 4350, + ERROR_REMOTE_STORAGE_NOT_ACTIVE, + ERROR_REMOTE_STORAGE_MEDIA_ERROR, // = 4352 + ERROR_NOT_A_REPARSE_POINT = 4390, + ERROR_REPARSE_ATTRIBUTE_CONFLICT, + ERROR_INVALID_REPARSE_DATA, + ERROR_REPARSE_TAG_INVALID, + ERROR_REPARSE_TAG_MISMATCH, // = 4394 + ERROR_VOLUME_NOT_SIS_ENABLED = 4500, + ERROR_DEPENDENT_RESOURCE_EXISTS = 5001, + ERROR_DEPENDENCY_NOT_FOUND, + ERROR_DEPENDENCY_ALREADY_EXISTS, + ERROR_RESOURCE_NOT_ONLINE, + ERROR_HOST_NODE_NOT_AVAILABLE, + ERROR_RESOURCE_NOT_AVAILABLE, + ERROR_RESOURCE_NOT_FOUND, + ERROR_SHUTDOWN_CLUSTER, + ERROR_CANT_EVICT_ACTIVE_NODE, + ERROR_OBJECT_ALREADY_EXISTS, + ERROR_OBJECT_IN_LIST, + ERROR_GROUP_NOT_AVAILABLE, + ERROR_GROUP_NOT_FOUND, + ERROR_GROUP_NOT_ONLINE, + ERROR_HOST_NODE_NOT_RESOURCE_OWNER, + ERROR_HOST_NODE_NOT_GROUP_OWNER, + ERROR_RESMON_CREATE_FAILED, + ERROR_RESMON_ONLINE_FAILED, + ERROR_RESOURCE_ONLINE, + ERROR_QUORUM_RESOURCE, + ERROR_NOT_QUORUM_CAPABLE, + ERROR_CLUSTER_SHUTTING_DOWN, + ERROR_INVALID_STATE, + ERROR_RESOURCE_PROPERTIES_STORED, + ERROR_NOT_QUORUM_CLASS, + ERROR_CORE_RESOURCE, + ERROR_QUORUM_RESOURCE_ONLINE_FAILED, + ERROR_QUORUMLOG_OPEN_FAILED, + ERROR_CLUSTERLOG_CORRUPT, + ERROR_CLUSTERLOG_RECORD_EXCEEDS_MAXSIZE, + ERROR_CLUSTERLOG_EXCEEDS_MAXSIZE, + ERROR_CLUSTERLOG_CHKPOINT_NOT_FOUND, + ERROR_CLUSTERLOG_NOT_ENOUGH_SPACE, + ERROR_QUORUM_OWNER_ALIVE, + ERROR_NETWORK_NOT_AVAILABLE, + ERROR_NODE_NOT_AVAILABLE, + ERROR_ALL_NODES_NOT_AVAILABLE, + ERROR_RESOURCE_FAILED, + ERROR_CLUSTER_INVALID_NODE, + ERROR_CLUSTER_NODE_EXISTS, + ERROR_CLUSTER_JOIN_IN_PROGRESS, + ERROR_CLUSTER_NODE_NOT_FOUND, + ERROR_CLUSTER_LOCAL_NODE_NOT_FOUND, + ERROR_CLUSTER_NETWORK_EXISTS, + ERROR_CLUSTER_NETWORK_NOT_FOUND, + ERROR_CLUSTER_NETINTERFACE_EXISTS, + ERROR_CLUSTER_NETINTERFACE_NOT_FOUND, + ERROR_CLUSTER_INVALID_REQUEST, + ERROR_CLUSTER_INVALID_NETWORK_PROVIDER, + ERROR_CLUSTER_NODE_DOWN, + ERROR_CLUSTER_NODE_UNREACHABLE, + ERROR_CLUSTER_NODE_NOT_MEMBER, + ERROR_CLUSTER_JOIN_NOT_IN_PROGRESS, + ERROR_CLUSTER_INVALID_NETWORK, // = 5054 + ERROR_CLUSTER_NODE_UP = 5056, + ERROR_CLUSTER_IPADDR_IN_USE, + ERROR_CLUSTER_NODE_NOT_PAUSED, + ERROR_CLUSTER_NO_SECURITY_CONTEXT, + ERROR_CLUSTER_NETWORK_NOT_INTERNAL, + ERROR_CLUSTER_NODE_ALREADY_UP, + ERROR_CLUSTER_NODE_ALREADY_DOWN, + ERROR_CLUSTER_NETWORK_ALREADY_ONLINE, + ERROR_CLUSTER_NETWORK_ALREADY_OFFLINE, + ERROR_CLUSTER_NODE_ALREADY_MEMBER, + ERROR_CLUSTER_LAST_INTERNAL_NETWORK, + ERROR_CLUSTER_NETWORK_HAS_DEPENDENTS, + ERROR_INVALID_OPERATION_ON_QUORUM, + ERROR_DEPENDENCY_NOT_ALLOWED, + ERROR_CLUSTER_NODE_PAUSED, + ERROR_NODE_CANT_HOST_RESOURCE, + ERROR_CLUSTER_NODE_NOT_READY, + ERROR_CLUSTER_NODE_SHUTTING_DOWN, + ERROR_CLUSTER_JOIN_ABORTED, + ERROR_CLUSTER_INCOMPATIBLE_VERSIONS, + ERROR_CLUSTER_MAXNUM_OF_RESOURCES_EXCEEDED, + ERROR_CLUSTER_SYSTEM_CONFIG_CHANGED, + ERROR_CLUSTER_RESOURCE_TYPE_NOT_FOUND, + ERROR_CLUSTER_RESTYPE_NOT_SUPPORTED, + ERROR_CLUSTER_RESNAME_NOT_FOUND, + ERROR_CLUSTER_NO_RPC_PACKAGES_REGISTERED, + ERROR_CLUSTER_OWNER_NOT_IN_PREFLIST, + ERROR_CLUSTER_DATABASE_SEQMISMATCH, + ERROR_RESMON_INVALID_STATE, + ERROR_CLUSTER_GUM_NOT_LOCKER, + ERROR_QUORUM_DISK_NOT_FOUND, + ERROR_DATABASE_BACKUP_CORRUPT, + ERROR_CLUSTER_NODE_ALREADY_HAS_DFS_ROOT, + ERROR_RESOURCE_PROPERTY_UNCHANGEABLE, // = 5089 + ERROR_CLUSTER_MEMBERSHIP_INVALID_STATE = 5890, + ERROR_CLUSTER_QUORUMLOG_NOT_FOUND, + ERROR_CLUSTER_MEMBERSHIP_HALT, + ERROR_CLUSTER_INSTANCE_ID_MISMATCH, + ERROR_CLUSTER_NETWORK_NOT_FOUND_FOR_IP, + ERROR_CLUSTER_PROPERTY_DATA_TYPE_MISMATCH, + ERROR_CLUSTER_EVICT_WITHOUT_CLEANUP, + ERROR_CLUSTER_PARAMETER_MISMATCH, + ERROR_NODE_CANNOT_BE_CLUSTERED, + ERROR_CLUSTER_WRONG_OS_VERSION, + ERROR_CLUSTER_CANT_CREATE_DUP_CLUSTER_NAME, + ERROR_CLUSCFG_ALREADY_COMMITTED, + ERROR_CLUSCFG_ROLLBACK_FAILED, + ERROR_CLUSCFG_SYSTEM_DISK_DRIVE_LETTER_CONFLICT, + ERROR_CLUSTER_OLD_VERSION, + ERROR_CLUSTER_MISMATCHED_COMPUTER_ACCT_NAME, // = 5905 + ERROR_ENCRYPTION_FAILED = 6000, + ERROR_DECRYPTION_FAILED, + ERROR_FILE_ENCRYPTED, + ERROR_NO_RECOVERY_POLICY, + ERROR_NO_EFS, + ERROR_WRONG_EFS, + ERROR_NO_USER_KEYS, + ERROR_FILE_NOT_ENCRYPTED, + ERROR_NOT_EXPORT_FORMAT, + ERROR_FILE_READ_ONLY, + ERROR_DIR_EFS_DISALLOWED, + ERROR_EFS_SERVER_NOT_TRUSTED, + ERROR_BAD_RECOVERY_POLICY, + ERROR_EFS_ALG_BLOB_TOO_BIG, + ERROR_VOLUME_NOT_SUPPORT_EFS, + ERROR_EFS_DISABLED, + ERROR_EFS_VERSION_NOT_SUPPORT, // = 6016 + ERROR_NO_BROWSER_SERVERS_FOUND = 6118, + SCHED_E_SERVICE_NOT_LOCALSYSTEM = 6200, + + ERROR_CTX_WINSTATION_NAME_INVALID = 7001, + ERROR_CTX_INVALID_PD, + ERROR_CTX_PD_NOT_FOUND, + ERROR_CTX_WD_NOT_FOUND, + ERROR_CTX_CANNOT_MAKE_EVENTLOG_ENTRY, + ERROR_CTX_SERVICE_NAME_COLLISION, + ERROR_CTX_CLOSE_PENDING, + ERROR_CTX_NO_OUTBUF, + ERROR_CTX_MODEM_INF_NOT_FOUND, + ERROR_CTX_INVALID_MODEMNAME, + ERROR_CTX_MODEM_RESPONSE_ERROR, + ERROR_CTX_MODEM_RESPONSE_TIMEOUT, + ERROR_CTX_MODEM_RESPONSE_NO_CARRIER, + ERROR_CTX_MODEM_RESPONSE_NO_DIALTONE, + ERROR_CTX_MODEM_RESPONSE_BUSY, + ERROR_CTX_MODEM_RESPONSE_VOICE, + ERROR_CTX_TD_ERROR, // = 7017 + ERROR_CTX_WINSTATION_NOT_FOUND = 7022, + ERROR_CTX_WINSTATION_ALREADY_EXISTS, + ERROR_CTX_WINSTATION_BUSY, + ERROR_CTX_BAD_VIDEO_MODE, // = 7025 + ERROR_CTX_GRAPHICS_INVALID = 7035, + ERROR_CTX_LOGON_DISABLED = 7037, + ERROR_CTX_NOT_CONSOLE, // = 7038 + ERROR_CTX_CLIENT_QUERY_TIMEOUT = 7040, + ERROR_CTX_CONSOLE_DISCONNECT, + ERROR_CTX_CONSOLE_CONNECT, // = 7042 + ERROR_CTX_SHADOW_DENIED = 7044, + ERROR_CTX_WINSTATION_ACCESS_DENIED, // = 7045 + ERROR_CTX_INVALID_WD = 7049, + ERROR_CTX_SHADOW_INVALID, + ERROR_CTX_SHADOW_DISABLED, + ERROR_CTX_CLIENT_LICENSE_IN_USE, + ERROR_CTX_CLIENT_LICENSE_NOT_SET, + ERROR_CTX_LICENSE_NOT_AVAILABLE, + ERROR_CTX_LICENSE_CLIENT_INVALID, + ERROR_CTX_LICENSE_EXPIRED, + ERROR_CTX_SHADOW_NOT_RUNNING, + ERROR_CTX_SHADOW_ENDED_BY_MODE_CHANGE, + ERROR_ACTIVATION_COUNT_EXCEEDED, // = 7059 + + FRS_ERR_INVALID_API_SEQUENCE = 8001, + FRS_ERR_STARTING_SERVICE, + FRS_ERR_STOPPING_SERVICE, + FRS_ERR_INTERNAL_API, + FRS_ERR_INTERNAL, + FRS_ERR_SERVICE_COMM, + FRS_ERR_INSUFFICIENT_PRIV, + FRS_ERR_AUTHENTICATION, + FRS_ERR_PARENT_INSUFFICIENT_PRIV, + FRS_ERR_PARENT_AUTHENTICATION, + FRS_ERR_CHILD_TO_PARENT_COMM, + FRS_ERR_PARENT_TO_CHILD_COMM, + FRS_ERR_SYSVOL_POPULATE, + FRS_ERR_SYSVOL_POPULATE_TIMEOUT, + FRS_ERR_SYSVOL_IS_BUSY, + FRS_ERR_SYSVOL_DEMOTE, + FRS_ERR_INVALID_SERVICE_PARAMETER, // = 8017 + ERROR_DS_NOT_INSTALLED = 8200, + ERROR_DS_MEMBERSHIP_EVALUATED_LOCALLY, + ERROR_DS_NO_ATTRIBUTE_OR_VALUE, + ERROR_DS_INVALID_ATTRIBUTE_SYNTAX, + ERROR_DS_ATTRIBUTE_TYPE_UNDEFINED, + ERROR_DS_ATTRIBUTE_OR_VALUE_EXISTS, + ERROR_DS_BUSY, + ERROR_DS_UNAVAILABLE, + ERROR_DS_NO_RIDS_ALLOCATED, + ERROR_DS_NO_MORE_RIDS, + ERROR_DS_INCORRECT_ROLE_OWNER, + ERROR_DS_RIDMGR_INIT_ERROR, + ERROR_DS_OBJ_CLASS_VIOLATION, + ERROR_DS_CANT_ON_NON_LEAF, + ERROR_DS_CANT_ON_RDN, + ERROR_DS_CANT_MOD_OBJ_CLASS, + ERROR_DS_CROSS_DOM_MOVE_ERROR, + ERROR_DS_GC_NOT_AVAILABLE, + ERROR_SHARED_POLICY, + ERROR_POLICY_OBJECT_NOT_FOUND, + ERROR_POLICY_ONLY_IN_DS, + ERROR_PROMOTION_ACTIVE, + ERROR_NO_PROMOTION_ACTIVE, // = 8222 + ERROR_DS_OPERATIONS_ERROR = 8224, + ERROR_DS_PROTOCOL_ERROR, + ERROR_DS_TIMELIMIT_EXCEEDED, + ERROR_DS_SIZELIMIT_EXCEEDED, + ERROR_DS_ADMIN_LIMIT_EXCEEDED, + ERROR_DS_COMPARE_FALSE, + ERROR_DS_COMPARE_TRUE, + ERROR_DS_AUTH_METHOD_NOT_SUPPORTED, + ERROR_DS_STRONG_AUTH_REQUIRED, + ERROR_DS_INAPPROPRIATE_AUTH, + ERROR_DS_AUTH_UNKNOWN, + ERROR_DS_REFERRAL, + ERROR_DS_UNAVAILABLE_CRIT_EXTENSION, + ERROR_DS_CONFIDENTIALITY_REQUIRED, + ERROR_DS_INAPPROPRIATE_MATCHING, + ERROR_DS_CONSTRAINT_VIOLATION, + ERROR_DS_NO_SUCH_OBJECT, + ERROR_DS_ALIAS_PROBLEM, + ERROR_DS_INVALID_DN_SYNTAX, + ERROR_DS_IS_LEAF, + ERROR_DS_ALIAS_DEREF_PROBLEM, + ERROR_DS_UNWILLING_TO_PERFORM, + ERROR_DS_LOOP_DETECT, + ERROR_DS_NAMING_VIOLATION, + ERROR_DS_OBJECT_RESULTS_TOO_LARGE, + ERROR_DS_AFFECTS_MULTIPLE_DSAS, + ERROR_DS_SERVER_DOWN, + ERROR_DS_LOCAL_ERROR, + ERROR_DS_ENCODING_ERROR, + ERROR_DS_DECODING_ERROR, + ERROR_DS_FILTER_UNKNOWN, + ERROR_DS_PARAM_ERROR, + ERROR_DS_NOT_SUPPORTED, + ERROR_DS_NO_RESULTS_RETURNED, + ERROR_DS_CONTROL_NOT_FOUND, + ERROR_DS_CLIENT_LOOP, + ERROR_DS_REFERRAL_LIMIT_EXCEEDED, + ERROR_DS_SORT_CONTROL_MISSING, + ERROR_DS_OFFSET_RANGE_ERROR, // = 8262 + ERROR_DS_ROOT_MUST_BE_NC = 8301, + ERROR_DS_ADD_REPLICA_INHIBITED, + ERROR_DS_ATT_NOT_DEF_IN_SCHEMA, + ERROR_DS_MAX_OBJ_SIZE_EXCEEDED, + ERROR_DS_OBJ_STRING_NAME_EXISTS, + ERROR_DS_NO_RDN_DEFINED_IN_SCHEMA, + ERROR_DS_RDN_DOESNT_MATCH_SCHEMA, + ERROR_DS_NO_REQUESTED_ATTS_FOUND, + ERROR_DS_USER_BUFFER_TO_SMALL, + ERROR_DS_ATT_IS_NOT_ON_OBJ, + ERROR_DS_ILLEGAL_MOD_OPERATION, + ERROR_DS_OBJ_TOO_LARGE, + ERROR_DS_BAD_INSTANCE_TYPE, + ERROR_DS_MASTERDSA_REQUIRED, + ERROR_DS_OBJECT_CLASS_REQUIRED, + ERROR_DS_MISSING_REQUIRED_ATT, + ERROR_DS_ATT_NOT_DEF_FOR_CLASS, + ERROR_DS_ATT_ALREADY_EXISTS, // = 8318 + ERROR_DS_CANT_ADD_ATT_VALUES = 8320, + ERROR_DS_SINGLE_VALUE_CONSTRAINT, + ERROR_DS_RANGE_CONSTRAINT, + ERROR_DS_ATT_VAL_ALREADY_EXISTS, + ERROR_DS_CANT_REM_MISSING_ATT, + ERROR_DS_CANT_REM_MISSING_ATT_VAL, + ERROR_DS_ROOT_CANT_BE_SUBREF, + ERROR_DS_NO_CHAINING, + ERROR_DS_NO_CHAINED_EVAL, + ERROR_DS_NO_PARENT_OBJECT, + ERROR_DS_PARENT_IS_AN_ALIAS, + ERROR_DS_CANT_MIX_MASTER_AND_REPS, + ERROR_DS_CHILDREN_EXIST, + ERROR_DS_OBJ_NOT_FOUND, + ERROR_DS_ALIASED_OBJ_MISSING, + ERROR_DS_BAD_NAME_SYNTAX, + ERROR_DS_ALIAS_POINTS_TO_ALIAS, + ERROR_DS_CANT_DEREF_ALIAS, + ERROR_DS_OUT_OF_SCOPE, + ERROR_DS_OBJECT_BEING_REMOVED, + ERROR_DS_CANT_DELETE_DSA_OBJ, + ERROR_DS_GENERIC_ERROR, + ERROR_DS_DSA_MUST_BE_INT_MASTER, + ERROR_DS_CLASS_NOT_DSA, + ERROR_DS_INSUFF_ACCESS_RIGHTS, + ERROR_DS_ILLEGAL_SUPERIOR, + ERROR_DS_ATTRIBUTE_OWNED_BY_SAM, + ERROR_DS_NAME_TOO_MANY_PARTS, + ERROR_DS_NAME_TOO_LONG, + ERROR_DS_NAME_VALUE_TOO_LONG, + ERROR_DS_NAME_UNPARSEABLE, + ERROR_DS_NAME_TYPE_UNKNOWN, + ERROR_DS_NOT_AN_OBJECT, + ERROR_DS_SEC_DESC_TOO_SHORT, + ERROR_DS_SEC_DESC_INVALID, + ERROR_DS_NO_DELETED_NAME, + ERROR_DS_SUBREF_MUST_HAVE_PARENT, + ERROR_DS_NCNAME_MUST_BE_NC, + ERROR_DS_CANT_ADD_SYSTEM_ONLY, + ERROR_DS_CLASS_MUST_BE_CONCRETE, + ERROR_DS_INVALID_DMD, + ERROR_DS_OBJ_GUID_EXISTS, + ERROR_DS_NOT_ON_BACKLINK, + ERROR_DS_NO_CROSSREF_FOR_NC, + ERROR_DS_SHUTTING_DOWN, + ERROR_DS_UNKNOWN_OPERATION, + ERROR_DS_INVALID_ROLE_OWNER, + ERROR_DS_COULDNT_CONTACT_FSMO, + ERROR_DS_CROSS_NC_DN_RENAME, + ERROR_DS_CANT_MOD_SYSTEM_ONLY, + ERROR_DS_REPLICATOR_ONLY, + ERROR_DS_OBJ_CLASS_NOT_DEFINED, + ERROR_DS_OBJ_CLASS_NOT_SUBCLASS, + ERROR_DS_NAME_REFERENCE_INVALID, + ERROR_DS_CROSS_REF_EXISTS, + ERROR_DS_CANT_DEL_MASTER_CROSSREF, + ERROR_DS_SUBTREE_NOTIFY_NOT_NC_HEAD, + ERROR_DS_NOTIFY_FILTER_TOO_COMPLEX, + ERROR_DS_DUP_RDN, + ERROR_DS_DUP_OID, + ERROR_DS_DUP_MAPI_ID, + ERROR_DS_DUP_SCHEMA_ID_GUID, + ERROR_DS_DUP_LDAP_DISPLAY_NAME, + ERROR_DS_SEMANTIC_ATT_TEST, + ERROR_DS_SYNTAX_MISMATCH, + ERROR_DS_EXISTS_IN_MUST_HAVE, + ERROR_DS_EXISTS_IN_MAY_HAVE, + ERROR_DS_NONEXISTENT_MAY_HAVE, + ERROR_DS_NONEXISTENT_MUST_HAVE, + ERROR_DS_AUX_CLS_TEST_FAIL, + ERROR_DS_NONEXISTENT_POSS_SUP, + ERROR_DS_SUB_CLS_TEST_FAIL, + ERROR_DS_BAD_RDN_ATT_ID_SYNTAX, + ERROR_DS_EXISTS_IN_AUX_CLS, + ERROR_DS_EXISTS_IN_SUB_CLS, + ERROR_DS_EXISTS_IN_POSS_SUP, + ERROR_DS_RECALCSCHEMA_FAILED, + ERROR_DS_TREE_DELETE_NOT_FINISHED, + ERROR_DS_CANT_DELETE, + ERROR_DS_ATT_SCHEMA_REQ_ID, + ERROR_DS_BAD_ATT_SCHEMA_SYNTAX, + ERROR_DS_CANT_CACHE_ATT, + ERROR_DS_CANT_CACHE_CLASS, + ERROR_DS_CANT_REMOVE_ATT_CACHE, + ERROR_DS_CANT_REMOVE_CLASS_CACHE, + ERROR_DS_CANT_RETRIEVE_DN, + ERROR_DS_MISSING_SUPREF, + ERROR_DS_CANT_RETRIEVE_INSTANCE, + ERROR_DS_CODE_INCONSISTENCY, + ERROR_DS_DATABASE_ERROR, + ERROR_DS_GOVERNSID_MISSING, + ERROR_DS_MISSING_EXPECTED_ATT, + ERROR_DS_NCNAME_MISSING_CR_REF, + ERROR_DS_SECURITY_CHECKING_ERROR, + ERROR_DS_SCHEMA_NOT_LOADED, + ERROR_DS_SCHEMA_ALLOC_FAILED, + ERROR_DS_ATT_SCHEMA_REQ_SYNTAX, + ERROR_DS_GCVERIFY_ERROR, + ERROR_DS_DRA_SCHEMA_MISMATCH, + ERROR_DS_CANT_FIND_DSA_OBJ, + ERROR_DS_CANT_FIND_EXPECTED_NC, + ERROR_DS_CANT_FIND_NC_IN_CACHE, + ERROR_DS_CANT_RETRIEVE_CHILD, + ERROR_DS_SECURITY_ILLEGAL_MODIFY, + ERROR_DS_CANT_REPLACE_HIDDEN_REC, + ERROR_DS_BAD_HIERARCHY_FILE, + ERROR_DS_BUILD_HIERARCHY_TABLE_FAILED, + ERROR_DS_CONFIG_PARAM_MISSING, + ERROR_DS_COUNTING_AB_INDICES_FAILED, + ERROR_DS_HIERARCHY_TABLE_MALLOC_FAILED, + ERROR_DS_INTERNAL_FAILURE, + ERROR_DS_UNKNOWN_ERROR, + ERROR_DS_ROOT_REQUIRES_CLASS_TOP, + ERROR_DS_REFUSING_FSMO_ROLES, + ERROR_DS_MISSING_FSMO_SETTINGS, + ERROR_DS_UNABLE_TO_SURRENDER_ROLES, + ERROR_DS_DRA_GENERIC, + ERROR_DS_DRA_INVALID_PARAMETER, + ERROR_DS_DRA_BUSY, + ERROR_DS_DRA_BAD_DN, + ERROR_DS_DRA_BAD_NC, + ERROR_DS_DRA_DN_EXISTS, + ERROR_DS_DRA_INTERNAL_ERROR, + ERROR_DS_DRA_INCONSISTENT_DIT, + ERROR_DS_DRA_CONNECTION_FAILED, + ERROR_DS_DRA_BAD_INSTANCE_TYPE, + ERROR_DS_DRA_OUT_OF_MEM, + ERROR_DS_DRA_MAIL_PROBLEM, + ERROR_DS_DRA_REF_ALREADY_EXISTS, + ERROR_DS_DRA_REF_NOT_FOUND, + ERROR_DS_DRA_OBJ_IS_REP_SOURCE, + ERROR_DS_DRA_DB_ERROR, + ERROR_DS_DRA_NO_REPLICA, + ERROR_DS_DRA_ACCESS_DENIED, + ERROR_DS_DRA_NOT_SUPPORTED, + ERROR_DS_DRA_RPC_CANCELLED, + ERROR_DS_DRA_SOURCE_DISABLED, + ERROR_DS_DRA_SINK_DISABLED, + ERROR_DS_DRA_NAME_COLLISION, + ERROR_DS_DRA_SOURCE_REINSTALLED, + ERROR_DS_DRA_MISSING_PARENT, + ERROR_DS_DRA_PREEMPTED, + ERROR_DS_DRA_ABANDON_SYNC, + ERROR_DS_DRA_SHUTDOWN, + ERROR_DS_DRA_INCOMPATIBLE_PARTIAL_SET, + ERROR_DS_DRA_SOURCE_IS_PARTIAL_REPLICA, + ERROR_DS_DRA_EXTN_CONNECTION_FAILED, + ERROR_DS_INSTALL_SCHEMA_MISMATCH, + ERROR_DS_DUP_LINK_ID, + ERROR_DS_NAME_ERROR_RESOLVING, + ERROR_DS_NAME_ERROR_NOT_FOUND, + ERROR_DS_NAME_ERROR_NOT_UNIQUE, + ERROR_DS_NAME_ERROR_NO_MAPPING, + ERROR_DS_NAME_ERROR_DOMAIN_ONLY, + ERROR_DS_NAME_ERROR_NO_SYNTACTICAL_MAPPING, + ERROR_DS_CONSTRUCTED_ATT_MOD, + ERROR_DS_WRONG_OM_OBJ_CLASS, + ERROR_DS_DRA_REPL_PENDING, + ERROR_DS_DS_REQUIRED, + ERROR_DS_INVALID_LDAP_DISPLAY_NAME, + ERROR_DS_NON_BASE_SEARCH, + ERROR_DS_CANT_RETRIEVE_ATTS, + ERROR_DS_BACKLINK_WITHOUT_LINK, + ERROR_DS_EPOCH_MISMATCH, + ERROR_DS_SRC_NAME_MISMATCH, + ERROR_DS_SRC_AND_DST_NC_IDENTICAL, + ERROR_DS_DST_NC_MISMATCH, + ERROR_DS_NOT_AUTHORITIVE_FOR_DST_NC, + ERROR_DS_SRC_GUID_MISMATCH, + ERROR_DS_CANT_MOVE_DELETED_OBJECT, + ERROR_DS_PDC_OPERATION_IN_PROGRESS, + ERROR_DS_CROSS_DOMAIN_CLEANUP_REQD, + ERROR_DS_ILLEGAL_XDOM_MOVE_OPERATION, + ERROR_DS_CANT_WITH_ACCT_GROUP_MEMBERSHPS, + ERROR_DS_NC_MUST_HAVE_NC_PARENT, + ERROR_DS_CR_IMPOSSIBLE_TO_VALIDATE, + ERROR_DS_DST_DOMAIN_NOT_NATIVE, + ERROR_DS_MISSING_INFRASTRUCTURE_CONTAINER, + ERROR_DS_CANT_MOVE_ACCOUNT_GROUP, + ERROR_DS_CANT_MOVE_RESOURCE_GROUP, + ERROR_DS_INVALID_SEARCH_FLAG, + ERROR_DS_NO_TREE_DELETE_ABOVE_NC, + ERROR_DS_COULDNT_LOCK_TREE_FOR_DELETE, + ERROR_DS_COULDNT_IDENTIFY_OBJECTS_FOR_TREE_DELETE, + ERROR_DS_SAM_INIT_FAILURE, + ERROR_DS_SENSITIVE_GROUP_VIOLATION, + ERROR_DS_CANT_MOD_PRIMARYGROUPID, + ERROR_DS_ILLEGAL_BASE_SCHEMA_MOD, + ERROR_DS_NONSAFE_SCHEMA_CHANGE, + ERROR_DS_SCHEMA_UPDATE_DISALLOWED, + ERROR_DS_CANT_CREATE_UNDER_SCHEMA, + ERROR_DS_INSTALL_NO_SRC_SCH_VERSION, + ERROR_DS_INSTALL_NO_SCH_VERSION_IN_INIFILE, + ERROR_DS_INVALID_GROUP_TYPE, + ERROR_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN, + ERROR_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN, + ERROR_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER, + ERROR_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER, + ERROR_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER, + ERROR_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER, + ERROR_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER, + ERROR_DS_HAVE_PRIMARY_MEMBERS, + ERROR_DS_STRING_SD_CONVERSION_FAILED, + ERROR_DS_NAMING_MASTER_GC, + ERROR_DS_LOOKUP_FAILURE, + ERROR_DS_COULDNT_UPDATE_SPNS, + ERROR_DS_CANT_RETRIEVE_SD, + ERROR_DS_KEY_NOT_UNIQUE, + ERROR_DS_WRONG_LINKED_ATT_SYNTAX, + ERROR_DS_SAM_NEED_BOOTKEY_PASSWORD, + ERROR_DS_SAM_NEED_BOOTKEY_FLOPPY, + ERROR_DS_CANT_START, + ERROR_DS_INIT_FAILURE, + ERROR_DS_NO_PKT_PRIVACY_ON_CONNECTION, + ERROR_DS_SOURCE_DOMAIN_IN_FOREST, + ERROR_DS_DESTINATION_DOMAIN_NOT_IN_FOREST, + ERROR_DS_DESTINATION_AUDITING_NOT_ENABLED, + ERROR_DS_CANT_FIND_DC_FOR_SRC_DOMAIN, + ERROR_DS_SRC_OBJ_NOT_GROUP_OR_USER, + ERROR_DS_SRC_SID_EXISTS_IN_FOREST, + ERROR_DS_SRC_AND_DST_OBJECT_CLASS_MISMATCH, + ERROR_SAM_INIT_FAILURE, + ERROR_DS_DRA_SCHEMA_INFO_SHIP, + ERROR_DS_DRA_SCHEMA_CONFLICT, + ERROR_DS_DRA_EARLIER_SCHEMA_CONLICT, + ERROR_DS_DRA_OBJ_NC_MISMATCH, + ERROR_DS_NC_STILL_HAS_DSAS, + ERROR_DS_GC_REQUIRED, + ERROR_DS_LOCAL_MEMBER_OF_LOCAL_ONLY, + ERROR_DS_NO_FPO_IN_UNIVERSAL_GROUPS, + ERROR_DS_CANT_ADD_TO_GC, + ERROR_DS_NO_CHECKPOINT_WITH_PDC, + ERROR_DS_SOURCE_AUDITING_NOT_ENABLED, + ERROR_DS_CANT_CREATE_IN_NONDOMAIN_NC, + ERROR_DS_INVALID_NAME_FOR_SPN, + ERROR_DS_FILTER_USES_CONTRUCTED_ATTRS, + ERROR_DS_UNICODEPWD_NOT_IN_QUOTES, + ERROR_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED, + ERROR_DS_MUST_BE_RUN_ON_DST_DC, + ERROR_DS_SRC_DC_MUST_BE_SP4_OR_GREATER, + ERROR_DS_CANT_TREE_DELETE_CRITICAL_OBJ, + ERROR_DS_INIT_FAILURE_CONSOLE, + ERROR_DS_SAM_INIT_FAILURE_CONSOLE, + ERROR_DS_FOREST_VERSION_TOO_HIGH, + ERROR_DS_DOMAIN_VERSION_TOO_HIGH, + ERROR_DS_FOREST_VERSION_TOO_LOW, + ERROR_DS_DOMAIN_VERSION_TOO_LOW, + ERROR_DS_INCOMPATIBLE_VERSION, + ERROR_DS_LOW_DSA_VERSION, + ERROR_DS_NO_BEHAVIOR_VERSION_IN_MIXEDDOMAIN, + ERROR_DS_NOT_SUPPORTED_SORT_ORDER, + ERROR_DS_NAME_NOT_UNIQUE, + ERROR_DS_MACHINE_ACCOUNT_CREATED_PRENT4, + ERROR_DS_OUT_OF_VERSION_STORE, + ERROR_DS_INCOMPATIBLE_CONTROLS_USED, + ERROR_DS_NO_REF_DOMAIN, + ERROR_DS_RESERVED_LINK_ID, + ERROR_DS_LINK_ID_NOT_AVAILABLE, + ERROR_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER, + ERROR_DS_MODIFYDN_DISALLOWED_BY_INSTANCE_TYPE, + ERROR_DS_NO_OBJECT_MOVE_IN_SCHEMA_NC, + ERROR_DS_MODIFYDN_DISALLOWED_BY_FLAG, + ERROR_DS_MODIFYDN_WRONG_GRANDPARENT, + ERROR_DS_NAME_ERROR_TRUST_REFERRAL, + ERROR_NOT_SUPPORTED_ON_STANDARD_SERVER, + ERROR_DS_CANT_ACCESS_REMOTE_PART_OF_AD, + ERROR_DS_CR_IMPOSSIBLE_TO_VALIDATE_V2, + ERROR_DS_THREAD_LIMIT_EXCEEDED, + ERROR_DS_NOT_CLOSEST, + ERROR_DS_CANT_DERIVE_SPN_WITHOUT_SERVER_REF, + ERROR_DS_SINGLE_USER_MODE_FAILED, + ERROR_DS_NTDSCRIPT_SYNTAX_ERROR, + ERROR_DS_NTDSCRIPT_PROCESS_ERROR, + ERROR_DS_DIFFERENT_REPL_EPOCHS, + ERROR_DS_DRS_EXTENSIONS_CHANGED, + ERROR_DS_REPLICA_SET_CHANGE_NOT_ALLOWED_ON_DISABLED_CR, + ERROR_DS_NO_MSDS_INTID, + ERROR_DS_DUP_MSDS_INTID, + ERROR_DS_EXISTS_IN_RDNATTID, + ERROR_DS_AUTHORIZATION_FAILED, + ERROR_DS_INVALID_SCRIPT, + ERROR_DS_REMOTE_CROSSREF_OP_FAILED, + ERROR_DS_CROSS_REF_BUSY, + ERROR_DS_CANT_DERIVE_SPN_FOR_DELETED_DOMAIN, + ERROR_DS_CANT_DEMOTE_WITH_WRITEABLE_NC, + ERROR_DS_DUPLICATE_ID_FOUND, + ERROR_DS_INSUFFICIENT_ATTR_TO_CREATE_OBJECT, + ERROR_DS_GROUP_CONVERSION_ERROR, + ERROR_DS_CANT_MOVE_APP_BASIC_GROUP, + ERROR_DS_CANT_MOVE_APP_QUERY_GROUP, + ERROR_DS_ROLE_NOT_VERIFIED, + ERROR_DS_WKO_CONTAINER_CANNOT_BE_SPECIAL, + ERROR_DS_DOMAIN_RENAME_IN_PROGRESS, + ERROR_DS_EXISTING_AD_CHILD_NC, // = 8613 + DNS_ERROR_RCODE_FORMAT_ERROR = 9001, + DNS_ERROR_RCODE_SERVER_FAILURE, + DNS_ERROR_RCODE_NAME_ERROR, + DNS_ERROR_RCODE_NOT_IMPLEMENTED, + DNS_ERROR_RCODE_REFUSED, + DNS_ERROR_RCODE_YXDOMAIN, + DNS_ERROR_RCODE_YXRRSET, + DNS_ERROR_RCODE_NXRRSET, + DNS_ERROR_RCODE_NOTAUTH, + DNS_ERROR_RCODE_NOTZONE, // = 9010 + DNS_ERROR_RCODE_BADSIG = 9016, + DNS_ERROR_RCODE_BADKEY, + DNS_ERROR_RCODE_BADTIME, // = 9018 + DNS_INFO_NO_RECORDS = 9501, + DNS_ERROR_BAD_PACKET, + DNS_ERROR_NO_PACKET, + DNS_ERROR_RCODE, + DNS_ERROR_UNSECURE_PACKET, // = 9505 + DNS_ERROR_INVALID_TYPE = 9551, + DNS_ERROR_INVALID_IP_ADDRESS, + DNS_ERROR_INVALID_PROPERTY, + DNS_ERROR_TRY_AGAIN_LATER, + DNS_ERROR_NOT_UNIQUE, + DNS_ERROR_NON_RFC_NAME, + DNS_STATUS_FQDN, + DNS_STATUS_DOTTED_NAME, + DNS_STATUS_SINGLE_PART_NAME, + DNS_ERROR_INVALID_NAME_CHAR, + DNS_ERROR_NUMERIC_NAME, + DNS_ERROR_NOT_ALLOWED_ON_ROOT_SERVER, + DNS_ERROR_NOT_ALLOWED_UNDER_DELEGATION, + DNS_ERROR_CANNOT_FIND_ROOT_HINTS, + DNS_ERROR_INCONSISTENT_ROOT_HINTS, // = 9565 + DNS_ERROR_ZONE_DOES_NOT_EXIST = 9601, + DNS_ERROR_NO_ZONE_INFO, + DNS_ERROR_INVALID_ZONE_OPERATION, + DNS_ERROR_ZONE_CONFIGURATION_ERROR, + DNS_ERROR_ZONE_HAS_NO_SOA_RECORD, + DNS_ERROR_ZONE_HAS_NO_NS_RECORDS, + DNS_ERROR_ZONE_LOCKED, + DNS_ERROR_ZONE_CREATION_FAILED, + DNS_ERROR_ZONE_ALREADY_EXISTS, + DNS_ERROR_AUTOZONE_ALREADY_EXISTS, + DNS_ERROR_INVALID_ZONE_TYPE, + DNS_ERROR_SECONDARY_REQUIRES_MASTER_IP, + DNS_ERROR_ZONE_NOT_SECONDARY, + DNS_ERROR_NEED_SECONDARY_ADDRESSES, + DNS_ERROR_WINS_INIT_FAILED, + DNS_ERROR_NEED_WINS_SERVERS, + DNS_ERROR_NBSTAT_INIT_FAILED, + DNS_ERROR_SOA_DELETE_INVALID, + DNS_ERROR_FORWARDER_ALREADY_EXISTS, + DNS_ERROR_ZONE_REQUIRES_MASTER_IP, + DNS_ERROR_ZONE_IS_SHUTDOWN, // = 9621 + DNS_ERROR_PRIMARY_REQUIRES_DATAFILE = 9651, + DNS_ERROR_INVALID_DATAFILE_NAME, + DNS_ERROR_DATAFILE_OPEN_FAILURE, + DNS_ERROR_FILE_WRITEBACK_FAILED, + DNS_ERROR_DATAFILE_PARSING, // = 9655 + DNS_ERROR_RECORD_DOES_NOT_EXIST = 9701, + DNS_ERROR_RECORD_FORMAT, + DNS_ERROR_NODE_CREATION_FAILED, + DNS_ERROR_UNKNOWN_RECORD_TYPE, + DNS_ERROR_RECORD_TIMED_OUT, + DNS_ERROR_NAME_NOT_IN_ZONE, + DNS_ERROR_CNAME_LOOP, + DNS_ERROR_NODE_IS_CNAME, + DNS_ERROR_CNAME_COLLISION, + DNS_ERROR_RECORD_ONLY_AT_ZONE_ROOT, + DNS_ERROR_RECORD_ALREADY_EXISTS, + DNS_ERROR_SECONDARY_DATA, + DNS_ERROR_NO_CREATE_CACHE_DATA, + DNS_ERROR_NAME_DOES_NOT_EXIST, + DNS_WARNING_PTR_CREATE_FAILED, + DNS_WARNING_DOMAIN_UNDELETED, + DNS_ERROR_DS_UNAVAILABLE, + DNS_ERROR_DS_ZONE_ALREADY_EXISTS, + DNS_ERROR_NO_BOOTFILE_IF_DS_ZONE, // = 9719 + DNS_INFO_AXFR_COMPLETE = 9751, + DNS_ERROR_AXFR, + DNS_INFO_ADDED_LOCAL_WINS, // = 9753 + DNS_STATUS_CONTINUE_NEEDED = 9801, + DNS_ERROR_NO_TCPIP = 9851, + DNS_ERROR_NO_DNS_SERVERS, // = 9852 + DNS_ERROR_DP_DOES_NOT_EXIST = 9901, + DNS_ERROR_DP_ALREADY_EXISTS, + DNS_ERROR_DP_NOT_ENLISTED, + DNS_ERROR_DP_ALREADY_ENLISTED, + DNS_ERROR_DP_NOT_AVAILABLE, // = 9905 + +/+ already in winsock2.d defined! + + WSABASEERR = 10000, + WSAEINTR = 10004, + WSAEBADF = 10009, + WSAEACCES = 10013, + WSAEFAULT, // = 10014 + WSAEINVAL = 10022, + WSAEMFILE = 10024, + WSAEWOULDBLOCK = 10035, + WSAEINPROGRESS, + WSAEALREADY, + WSAENOTSOCK, + WSAEDESTADDRREQ, + WSAEMSGSIZE, + WSAEPROTOTYPE, + WSAENOPROTOOPT, + WSAEPROTONOSUPPORT, + WSAESOCKTNOSUPPORT, + WSAEOPNOTSUPP, + WSAEPFNOSUPPORT, + WSAEAFNOSUPPORT, + WSAEADDRINUSE, + WSAEADDRNOTAVAIL, + WSAENETDOWN, + WSAENETUNREACH, + WSAENETRESET, + WSAECONNABORTED, + WSAECONNRESET, + WSAENOBUFS, + WSAEISCONN, + WSAENOTCONN, + WSAESHUTDOWN, + WSAETOOMANYREFS, + WSAETIMEDOUT, + WSAECONNREFUSED, + WSAELOOP, + WSAENAMETOOLONG, + WSAEHOSTDOWN, + WSAEHOSTUNREACH, + WSAENOTEMPTY, + WSAEPROCLIM, + WSAEUSERS, + WSAEDQUOT, + WSAESTALE, + WSAEREMOTE, // = 10071 + WSASYSNOTREADY = 10091, + WSAVERNOTSUPPORTED, + WSANOTINITIALISED, // = 10093 + WSAEDISCON = 10101, + WSAENOMORE, + WSAECANCELLED, + WSAEINVALIDPROCTABLE, + WSAEINVALIDPROVIDER, + WSAEPROVIDERFAILEDINIT, + WSASYSCALLFAILURE, + WSASERVICE_NOT_FOUND, + WSATYPE_NOT_FOUND, + WSA_E_NO_MORE, + WSA_E_CANCELLED, + WSAEREFUSED, // = 10112 + WSAHOST_NOT_FOUND = 11001, + WSATRY_AGAIN, + WSANO_RECOVERY, + WSANO_DATA, + WSA_QOS_RECEIVERS, + WSA_QOS_SENDERS, + WSA_QOS_NO_SENDERS, + WSA_QOS_NO_RECEIVERS, + WSA_QOS_REQUEST_CONFIRMED, + WSA_QOS_ADMISSION_FAILURE, + WSA_QOS_POLICY_FAILURE, + WSA_QOS_BAD_STYLE, + WSA_QOS_BAD_OBJECT, + WSA_QOS_TRAFFIC_CTRL_ERROR, + WSA_QOS_GENERIC_ERROR, + WSA_QOS_ESERVICETYPE, + WSA_QOS_EFLOWSPEC, + WSA_QOS_EPROVSPECBUF, + WSA_QOS_EFILTERSTYLE, + WSA_QOS_EFILTERTYPE, + WSA_QOS_EFILTERCOUNT, + WSA_QOS_EOBJLENGTH, + WSA_QOS_EFLOWCOUNT, + WSA_QOS_EUNKNOWNPSOBJ, + WSA_QOS_EPOLICYOBJ, + WSA_QOS_EFLOWDESC, + WSA_QOS_EPSFLOWSPEC, + WSA_QOS_EPSFILTERSPEC, + WSA_QOS_ESDMODEOBJ, + WSA_QOS_ESHAPERATEOBJ, + WSA_QOS_RESERVED_PETYPE, // = 11031 + ++/ + + ERROR_IPSEC_QM_POLICY_EXISTS = 13000, + ERROR_IPSEC_QM_POLICY_NOT_FOUND, + ERROR_IPSEC_QM_POLICY_IN_USE, + ERROR_IPSEC_MM_POLICY_EXISTS, + ERROR_IPSEC_MM_POLICY_NOT_FOUND, + ERROR_IPSEC_MM_POLICY_IN_USE, + ERROR_IPSEC_MM_FILTER_EXISTS, + ERROR_IPSEC_MM_FILTER_NOT_FOUND, + ERROR_IPSEC_TRANSPORT_FILTER_EXISTS, + ERROR_IPSEC_TRANSPORT_FILTER_NOT_FOUND, + ERROR_IPSEC_MM_AUTH_EXISTS, + ERROR_IPSEC_MM_AUTH_NOT_FOUND, + ERROR_IPSEC_MM_AUTH_IN_USE, + ERROR_IPSEC_DEFAULT_MM_POLICY_NOT_FOUND, + ERROR_IPSEC_DEFAULT_MM_AUTH_NOT_FOUND, + ERROR_IPSEC_DEFAULT_QM_POLICY_NOT_FOUND, + ERROR_IPSEC_TUNNEL_FILTER_EXISTS, + ERROR_IPSEC_TUNNEL_FILTER_NOT_FOUND, + ERROR_IPSEC_MM_FILTER_PENDING_DELETION, + ERROR_IPSEC_TRANSPORT_FILTER_PENDING_DELETION, + ERROR_IPSEC_TUNNEL_FILTER_PENDING_DELETION, + ERROR_IPSEC_MM_POLICY_PENDING_DELETION, + ERROR_IPSEC_MM_AUTH_PENDING_DELETION, + ERROR_IPSEC_QM_POLICY_PENDING_DELETION, + WARNING_IPSEC_MM_POLICY_PRUNED, + WARNING_IPSEC_QM_POLICY_PRUNED, // = 13025 + ERROR_IPSEC_IKE_AUTH_FAIL = 13801, + ERROR_IPSEC_IKE_ATTRIB_FAIL, + ERROR_IPSEC_IKE_NEGOTIATION_PENDING, + ERROR_IPSEC_IKE_GENERAL_PROCESSING_ERROR, + ERROR_IPSEC_IKE_TIMED_OUT, + ERROR_IPSEC_IKE_NO_CERT, + ERROR_IPSEC_IKE_SA_DELETED, + ERROR_IPSEC_IKE_SA_REAPED, + ERROR_IPSEC_IKE_MM_ACQUIRE_DROP, + ERROR_IPSEC_IKE_QM_ACQUIRE_DROP, + ERROR_IPSEC_IKE_QUEUE_DROP_MM, + ERROR_IPSEC_IKE_QUEUE_DROP_NO_MM, + ERROR_IPSEC_IKE_DROP_NO_RESPONSE, + ERROR_IPSEC_IKE_MM_DELAY_DROP, + ERROR_IPSEC_IKE_QM_DELAY_DROP, + ERROR_IPSEC_IKE_ERROR, + ERROR_IPSEC_IKE_CRL_FAILED, + ERROR_IPSEC_IKE_INVALID_KEY_USAGE, + ERROR_IPSEC_IKE_INVALID_CERT_TYPE, + ERROR_IPSEC_IKE_NO_PRIVATE_KEY, // = 13820 + ERROR_IPSEC_IKE_DH_FAIL = 13822, + ERROR_IPSEC_IKE_INVALID_HEADER = 13824, + ERROR_IPSEC_IKE_NO_POLICY, + ERROR_IPSEC_IKE_INVALID_SIGNATURE, + ERROR_IPSEC_IKE_KERBEROS_ERROR, + ERROR_IPSEC_IKE_NO_PUBLIC_KEY, + ERROR_IPSEC_IKE_PROCESS_ERR, + ERROR_IPSEC_IKE_PROCESS_ERR_SA, + ERROR_IPSEC_IKE_PROCESS_ERR_PROP, + ERROR_IPSEC_IKE_PROCESS_ERR_TRANS, + ERROR_IPSEC_IKE_PROCESS_ERR_KE, + ERROR_IPSEC_IKE_PROCESS_ERR_ID, + ERROR_IPSEC_IKE_PROCESS_ERR_CERT, + ERROR_IPSEC_IKE_PROCESS_ERR_CERT_REQ, + ERROR_IPSEC_IKE_PROCESS_ERR_HASH, + ERROR_IPSEC_IKE_PROCESS_ERR_SIG, + ERROR_IPSEC_IKE_PROCESS_ERR_NONCE, + ERROR_IPSEC_IKE_PROCESS_ERR_NOTIFY, + ERROR_IPSEC_IKE_PROCESS_ERR_DELETE, + ERROR_IPSEC_IKE_PROCESS_ERR_VENDOR, + ERROR_IPSEC_IKE_INVALID_PAYLOAD, + ERROR_IPSEC_IKE_LOAD_SOFT_SA, + ERROR_IPSEC_IKE_SOFT_SA_TORN_DOWN, + ERROR_IPSEC_IKE_INVALID_COOKIE, + ERROR_IPSEC_IKE_NO_PEER_CERT, + ERROR_IPSEC_IKE_PEER_CRL_FAILED, + ERROR_IPSEC_IKE_POLICY_CHANGE, + ERROR_IPSEC_IKE_NO_MM_POLICY, + ERROR_IPSEC_IKE_NOTCBPRIV, + ERROR_IPSEC_IKE_SECLOADFAIL, + ERROR_IPSEC_IKE_FAILSSPINIT, + ERROR_IPSEC_IKE_FAILQUERYSSP, + ERROR_IPSEC_IKE_SRVACQFAIL, + ERROR_IPSEC_IKE_SRVQUERYCRED, + ERROR_IPSEC_IKE_GETSPIFAIL, + ERROR_IPSEC_IKE_INVALID_FILTER, + ERROR_IPSEC_IKE_OUT_OF_MEMORY, + ERROR_IPSEC_IKE_ADD_UPDATE_KEY_FAILED, + ERROR_IPSEC_IKE_INVALID_POLICY, + ERROR_IPSEC_IKE_UNKNOWN_DOI, + ERROR_IPSEC_IKE_INVALID_SITUATION, + ERROR_IPSEC_IKE_DH_FAILURE, + ERROR_IPSEC_IKE_INVALID_GROUP, + ERROR_IPSEC_IKE_ENCRYPT, + ERROR_IPSEC_IKE_DECRYPT, + ERROR_IPSEC_IKE_POLICY_MATCH, + ERROR_IPSEC_IKE_UNSUPPORTED_ID, + ERROR_IPSEC_IKE_INVALID_HASH, + ERROR_IPSEC_IKE_INVALID_HASH_ALG, + ERROR_IPSEC_IKE_INVALID_HASH_SIZE, + ERROR_IPSEC_IKE_INVALID_ENCRYPT_ALG, + ERROR_IPSEC_IKE_INVALID_AUTH_ALG, + ERROR_IPSEC_IKE_INVALID_SIG, + ERROR_IPSEC_IKE_LOAD_FAILED, + ERROR_IPSEC_IKE_RPC_DELETE, + ERROR_IPSEC_IKE_BENIGN_REINIT, + ERROR_IPSEC_IKE_INVALID_RESPONDER_LIFETIME_NOTIFY, // = 13879 + ERROR_IPSEC_IKE_INVALID_CERT_KEYLEN = 13881, + ERROR_IPSEC_IKE_MM_LIMIT, + ERROR_IPSEC_IKE_NEGOTIATION_DISABLED, + ERROR_IPSEC_IKE_NEG_STATUS_END, + ERROR_SXS_SECTION_NOT_FOUND, + ERROR_SXS_CANT_GEN_ACTCTX, + ERROR_SXS_INVALID_ACTCTXDATA_FORMAT, + ERROR_SXS_ASSEMBLY_NOT_FOUND, + ERROR_SXS_MANIFEST_FORMAT_ERROR, + ERROR_SXS_MANIFEST_PARSE_ERROR, + ERROR_SXS_ACTIVATION_CONTEXT_DISABLED, + ERROR_SXS_KEY_NOT_FOUND, + ERROR_SXS_VERSION_CONFLICT, + ERROR_SXS_WRONG_SECTION_TYPE, + ERROR_SXS_THREAD_QUERIES_DISABLED, + ERROR_SXS_PROCESS_DEFAULT_ALREADY_SET, + ERROR_SXS_UNKNOWN_ENCODING_GROUP, + ERROR_SXS_UNKNOWN_ENCODING, + ERROR_SXS_INVALID_XML_NAMESPACE_URI, + ERROR_SXS_ROOT_MANIFEST_DEPENDENCY_NOT_INSTALLED, + ERROR_SXS_LEAF_MANIFEST_DEPENDENCY_NOT_INSTALLED, + ERROR_SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE, + ERROR_SXS_MANIFEST_MISSING_REQUIRED_DEFAULT_NAMESPACE, + ERROR_SXS_MANIFEST_INVALID_REQUIRED_DEFAULT_NAMESPACE, + ERROR_SXS_PRIVATE_MANIFEST_CROSS_PATH_WITH_REPARSE_POINT, + ERROR_SXS_DUPLICATE_DLL_NAME, + ERROR_SXS_DUPLICATE_WINDOWCLASS_NAME, + ERROR_SXS_DUPLICATE_CLSID, + ERROR_SXS_DUPLICATE_IID, + ERROR_SXS_DUPLICATE_TLBID, + ERROR_SXS_DUPLICATE_PROGID, + ERROR_SXS_DUPLICATE_ASSEMBLY_NAME, + ERROR_SXS_FILE_HASH_MISMATCH, + ERROR_SXS_POLICY_PARSE_ERROR, + ERROR_SXS_XML_E_MISSINGQUOTE, + ERROR_SXS_XML_E_COMMENTSYNTAX, + ERROR_SXS_XML_E_BADSTARTNAMECHAR, + ERROR_SXS_XML_E_BADNAMECHAR, + ERROR_SXS_XML_E_BADCHARINSTRING, + ERROR_SXS_XML_E_XMLDECLSYNTAX, + ERROR_SXS_XML_E_BADCHARDATA, + ERROR_SXS_XML_E_MISSINGWHITESPACE, + ERROR_SXS_XML_E_EXPECTINGTAGEND, + ERROR_SXS_XML_E_MISSINGSEMICOLON, + ERROR_SXS_XML_E_UNBALANCEDPAREN, + ERROR_SXS_XML_E_INTERNALERROR, + ERROR_SXS_XML_E_UNEXPECTED_WHITESPACE, + ERROR_SXS_XML_E_INCOMPLETE_ENCODING, + ERROR_SXS_XML_E_MISSING_PAREN, + ERROR_SXS_XML_E_EXPECTINGCLOSEQUOTE, + ERROR_SXS_XML_E_MULTIPLE_COLONS, + ERROR_SXS_XML_E_INVALID_DECIMAL, + ERROR_SXS_XML_E_INVALID_HEXIDECIMAL, + ERROR_SXS_XML_E_INVALID_UNICODE, + ERROR_SXS_XML_E_WHITESPACEORQUESTIONMARK, + ERROR_SXS_XML_E_UNEXPECTEDENDTAG, + ERROR_SXS_XML_E_UNCLOSEDTAG, + ERROR_SXS_XML_E_DUPLICATEATTRIBUTE, + ERROR_SXS_XML_E_MULTIPLEROOTS, + ERROR_SXS_XML_E_INVALIDATROOTLEVEL, + ERROR_SXS_XML_E_BADXMLDECL, + ERROR_SXS_XML_E_MISSINGROOT, + ERROR_SXS_XML_E_UNEXPECTEDEOF, + ERROR_SXS_XML_E_BADPEREFINSUBSET, + ERROR_SXS_XML_E_UNCLOSEDSTARTTAG, + ERROR_SXS_XML_E_UNCLOSEDENDTAG, + ERROR_SXS_XML_E_UNCLOSEDSTRING, + ERROR_SXS_XML_E_UNCLOSEDCOMMENT, + ERROR_SXS_XML_E_UNCLOSEDDECL, + ERROR_SXS_XML_E_UNCLOSEDCDATA, + ERROR_SXS_XML_E_RESERVEDNAMESPACE, + ERROR_SXS_XML_E_INVALIDENCODING, + ERROR_SXS_XML_E_INVALIDSWITCH, + ERROR_SXS_XML_E_BADXMLCASE, + ERROR_SXS_XML_E_INVALID_STANDALONE, + ERROR_SXS_XML_E_UNEXPECTED_STANDALONE, + ERROR_SXS_XML_E_INVALID_VERSION, + ERROR_SXS_XML_E_MISSINGEQUALS, + ERROR_SXS_PROTECTION_RECOVERY_FAILED, + ERROR_SXS_PROTECTION_PUBLIC_KEY_TOO_SHORT, + ERROR_SXS_PROTECTION_CATALOG_NOT_VALID, + ERROR_SXS_UNTRANSLATABLE_HRESULT, + ERROR_SXS_PROTECTION_CATALOG_FILE_MISSING, + ERROR_SXS_MISSING_ASSEMBLY_IDENTITY_ATTRIBUTE, + ERROR_SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE_NAME // = 14080 +} + +enum : HRESULT { + S_OK = 0x00000000, + S_FALSE = 0x00000001, + + NOERROR = 0x00000000, + + E_PENDING = 0x8000000A, + E_NOTIMPL = 0x80004001, + E_NOINTERFACE = 0x80004002, + E_POINTER = 0x80004003, + E_ABORT = 0x80004004, + E_FAIL = 0x80004005, + E_ACCESSDENIED = 0x80070005, + E_HANDLE = 0x80070006, + E_OUTOFMEMORY = 0x8007000E, + E_INVALIDARG = 0x80070057, + E_UNEXPECTED = 0x8000FFFF, + + CO_E_INIT_TLS = 0x80004006, + CO_E_INIT_SHARED_ALLOCATOR = 0x80004007, + CO_E_INIT_MEMORY_ALLOCATOR = 0x80004008, + CO_E_INIT_CLASS_CACHE = 0x80004009, + CO_E_INIT_RPC_CHANNEL = 0x8000400A, + CO_E_INIT_TLS_SET_CHANNEL_CONTROL = 0x8000400B, + CO_E_INIT_TLS_CHANNEL_CONTROL = 0x8000400C, + CO_E_INIT_UNACCEPTED_USER_ALLOCATOR = 0x8000400D, + CO_E_INIT_SCM_MUTEX_EXISTS = 0x8000400E, + CO_E_INIT_SCM_FILE_MAPPING_EXISTS = 0x8000400F, + CO_E_INIT_SCM_MAP_VIEW_OF_FILE = 0x80004010, + CO_E_INIT_SCM_EXEC_FAILURE = 0x80004011, + CO_E_INIT_ONLY_SINGLE_THREADED = 0x80004012, + + RPC_E_CALL_REJECTED = 0x80010001, + RPC_E_CALL_CANCELED = 0x80010002, + RPC_E_CANTPOST_INSENDCALL = 0x80010003, + RPC_E_CANTCALLOUT_INASYNCCALL = 0x80010004, + RPC_E_CANTCALLOUT_INEXTERNALCALL = 0x80010005, + RPC_E_CONNECTION_TERMINATED = 0x80010006, + RPC_E_SERVER_DIED = 0x80010007, + RPC_E_CLIENT_DIED = 0x80010008, + RPC_E_INVALID_DATAPACKET = 0x80010009, + RPC_E_CANTTRANSMIT_CALL = 0x8001000A, + RPC_E_CLIENT_CANTMARSHAL_DATA = 0x8001000B, + RPC_E_CLIENT_CANTUNMARSHAL_DATA = 0x8001000C, + RPC_E_SERVER_CANTMARSHAL_DATA = 0x8001000D, + RPC_E_SERVER_CANTUNMARSHAL_DATA = 0x8001000E, + RPC_E_INVALID_DATA = 0x8001000F, + RPC_E_INVALID_PARAMETER = 0x80010010, + RPC_E_CANTCALLOUT_AGAIN = 0x80010011, + RPC_E_SERVER_DIED_DNE = 0x80010012, + RPC_E_SYS_CALL_FAILED = 0x80010100, + RPC_E_OUT_OF_RESOURCES = 0x80010101, + RPC_E_ATTEMPTED_MULTITHREAD = 0x80010102, + RPC_E_NOT_REGISTERED = 0x80010103, + RPC_E_FAULT = 0x80010104, + RPC_E_SERVERFAULT = 0x80010105, + RPC_E_CHANGED_MODE = 0x80010106, + RPC_E_INVALIDMETHOD = 0x80010107, + RPC_E_DISCONNECTED = 0x80010108, + RPC_E_RETRY = 0x80010109, + RPC_E_SERVERCALL_RETRYLATER = 0x8001010A, + RPC_E_SERVERCALL_REJECTED = 0x8001010B, + RPC_E_INVALID_CALLDATA = 0x8001010C, + RPC_E_CANTCALLOUT_ININPUTSYNCCALL = 0x8001010D, + RPC_E_WRONG_THREAD = 0x8001010E, + RPC_E_THREAD_NOT_INIT = 0x8001010F, + RPC_E_UNEXPECTED = 0x8001FFFF, + + DISP_E_UNKNOWNINTERFACE = 0x80020001, + DISP_E_MEMBERNOTFOUND = 0x80020003, + DISP_E_PARAMNOTFOUND = 0x80020004, + DISP_E_TYPEMISMATCH = 0x80020005, + DISP_E_UNKNOWNNAME = 0x80020006, + DISP_E_NONAMEDARGS = 0x80020007, + DISP_E_BADVARTYPE = 0x80020008, + DISP_E_EXCEPTION = 0x80020009, + DISP_E_OVERFLOW = 0x8002000A, + DISP_E_BADINDEX = 0x8002000B, + DISP_E_UNKNOWNLCID = 0x8002000C, + DISP_E_ARRAYISLOCKED = 0x8002000D, + DISP_E_BADPARAMCOUNT = 0x8002000E, + DISP_E_PARAMNOTOPTIONAL = 0x8002000F, + DISP_E_BADCALLEE = 0x80020010, + DISP_E_NOTACOLLECTION = 0x80020011, + DISP_E_DIVBYZERO = 0x80020012, + + TYPE_E_BUFFERTOOSMALL = 0x80028016, + TYPE_E_INVDATAREAD = 0x80028018, + TYPE_E_UNSUPFORMAT = 0x80028019, + TYPE_E_REGISTRYACCESS = 0x8002801C, + TYPE_E_LIBNOTREGISTERED = 0x8002801D, + TYPE_E_UNDEFINEDTYPE = 0x80028027, + TYPE_E_QUALIFIEDNAMEDISALLOWED = 0x80028028, + TYPE_E_INVALIDSTATE = 0x80028029, + TYPE_E_WRONGTYPEKIND = 0x8002802A, + TYPE_E_ELEMENTNOTFOUND = 0x8002802B, + TYPE_E_AMBIGUOUSNAME = 0x8002802C, + TYPE_E_NAMECONFLICT = 0x8002802D, + TYPE_E_UNKNOWNLCID = 0x8002802E, + TYPE_E_DLLFUNCTIONNOTFOUND = 0x8002802F, + TYPE_E_BADMODULEKIND = 0x800288BD, + TYPE_E_SIZETOOBIG = 0x800288C5, + TYPE_E_DUPLICATEID = 0x800288C6, + TYPE_E_INVALIDID = 0x800288CF, + TYPE_E_TYPEMISMATCH = 0x80028CA0, + TYPE_E_OUTOFBOUNDS = 0x80028CA1, + TYPE_E_IOERROR = 0x80028CA2, + TYPE_E_CANTCREATETMPFILE = 0x80028CA3, + TYPE_E_CANTLOADLIBRARY = 0x80029C4A, + TYPE_E_INCONSISTENTPROPFUNCS = 0x80029C83, + TYPE_E_CIRCULARTYPE = 0x80029C84, + + STG_E_INVALIDFUNCTION = 0x80030001, + STG_E_FILENOTFOUND = 0x80030002, + STG_E_PATHNOTFOUND = 0x80030003, + STG_E_TOOMANYOPENFILES = 0x80030004, + STG_E_ACCESSDENIED = 0x80030005, + STG_E_INVALIDHANDLE = 0x80030006, + STG_E_INSUFFICIENTMEMORY = 0x80030008, + STG_E_INVALIDPOINTER = 0x80030009, + STG_E_NOMOREFILES = 0x80030012, + STG_E_DISKISWRITEPROTECTED = 0x80030013, + STG_E_SEEKERROR = 0x80030019, + STG_E_WRITEFAULT = 0x8003001D, + STG_E_READFAULT = 0x8003001E, + STG_E_SHAREVIOLATION = 0x80030020, + STG_E_LOCKVIOLATION = 0x80030021, + STG_E_FILEALREADYEXISTS = 0x80030050, + STG_E_INVALIDPARAMETER = 0x80030057, + STG_E_MEDIUMFULL = 0x80030070, + STG_E_ABNORMALAPIEXIT = 0x800300FA, + STG_E_INVALIDHEADER = 0x800300FB, + STG_E_INVALIDNAME = 0x800300FC, + STG_E_UNKNOWN = 0x800300FD, + STG_E_UNIMPLEMENTEDFUNCTION = 0x800300FE, + STG_E_INVALIDFLAG = 0x800300FF, + STG_E_INUSE = 0x80030100, + STG_E_NOTCURRENT = 0x80030101, + STG_E_REVERTED = 0x80030102, + STG_E_CANTSAVE = 0x80030103, + STG_E_OLDFORMAT = 0x80030104, + STG_E_OLDDLL = 0x80030105, + STG_E_SHAREREQUIRED = 0x80030106, + STG_E_NOTFILEBASEDSTORAGE = 0x80030107, + STG_E_EXTANTMARSHALLINGS = 0x80030108, + STG_S_CONVERTED = 0x00030200, + + OLE_E_FIRST = 0x80040000, + OLE_S_FIRST = 0x00040000, + OLE_E_OLEVERB = 0x80040000, + OLE_S_USEREG = 0x00040000, + OLE_E_ADVF = 0x80040001, + OLE_S_STATIC = 0x00040001, + OLE_E_ENUM_NOMORE = 0x80040002, + OLE_S_MAC_CLIPFORMAT = 0x00040002, + OLE_E_ADVISENOTSUPPORTED = 0x80040003, + OLE_E_NOCONNECTION = 0x80040004, + OLE_E_NOTRUNNING = 0x80040005, + OLE_E_NOCACHE = 0x80040006, + OLE_E_BLANK = 0x80040007, + OLE_E_CLASSDIFF = 0x80040008, + OLE_E_CANT_GETMONIKER = 0x80040009, + OLE_E_CANT_BINDTOSOURCE = 0x8004000A, + OLE_E_STATIC = 0x8004000B, + OLE_E_PROMPTSAVECANCELLED = 0x8004000C, + OLE_E_INVALIDRECT = 0x8004000D, + OLE_E_WRONGCOMPOBJ = 0x8004000E, + OLE_E_INVALIDHWND = 0x8004000F, + OLE_E_NOT_INPLACEACTIVE = 0x80040010, + OLE_E_CANTCONVERT = 0x80040011, + OLE_E_NOSTORAGE = 0x80040012, + + DV_E_FORMATETC = 0x80040064, + DV_E_DVTARGETDEVICE = 0x80040065, + DV_E_STGMEDIUM = 0x80040066, + DV_E_STATDATA = 0x80040067, + DV_E_LINDEX = 0x80040068, + DV_E_TYMED = 0x80040069, + DV_E_CLIPFORMAT = 0x8004006A, + DV_E_DVASPECT = 0x8004006B, + DV_E_DVTARGETDEVICE_SIZE = 0x8004006C, + DV_E_NOIVIEWOBJECT = 0x8004006D, + + OLE_E_LAST = 0x800400FF, + OLE_S_LAST = 0x000400FF, + DRAGDROP_E_FIRST = 0x80040100, + DRAGDROP_S_FIRST = 0x00040100, + DRAGDROP_E_NOTREGISTERED = 0x80040100, + DRAGDROP_S_DROP = 0x00040100, + DRAGDROP_E_ALREADYREGISTERED = 0x80040101, + DRAGDROP_S_CANCEL = 0x00040101, + DRAGDROP_E_INVALIDHWND = 0x80040102, + DRAGDROP_S_USEDEFAULTCURSORS = 0x00040102, + DRAGDROP_E_LAST = 0x8004010F, + DRAGDROP_S_LAST = 0x0004010F, + CLASSFACTORY_E_FIRST = 0x80040110, + CLASSFACTORY_S_FIRST = 0x00040110, + CLASS_E_NOAGGREGATION = 0x80040110, + CLASS_E_CLASSNOTAVAILABLE = 0x80040111, + CLASSFACTORY_E_LAST = 0x8004011F, + CLASSFACTORY_S_LAST = 0x0004011F, + MARSHAL_E_FIRST = 0x80040120, + MARSHAL_S_FIRST = 0x00040120, + MARSHAL_E_LAST = 0x8004012F, + MARSHAL_S_LAST = 0x0004012F, + DATA_E_FIRST = 0x80040130, + DATA_S_FIRST = 0x00040130, + DATA_S_SAMEFORMATETC = 0x00040130, + DATA_E_LAST = 0x8004013F, + DATA_S_LAST = 0x0004013F, + VIEW_E_FIRST = 0x80040140, + VIEW_S_FIRST = 0x00040140, + VIEW_E_DRAW = 0x80040140, + VIEW_S_ALREADY_FROZEN = 0x00040140, + VIEW_E_LAST = 0x8004014F, + VIEW_S_LAST = 0x0004014F, + REGDB_E_FIRST = 0x80040150, + REGDB_S_FIRST = 0x00040150, + REGDB_E_READREGDB = 0x80040150, + REGDB_E_WRITEREGDB = 0x80040151, + REGDB_E_KEYMISSING = 0x80040152, + REGDB_E_INVALIDVALUE = 0x80040153, + REGDB_E_CLASSNOTREG = 0x80040154, + REGDB_E_IIDNOTREG = 0x80040155, + REGDB_E_LAST = 0x8004015F, + REGDB_S_LAST = 0x0004015F, + CACHE_E_FIRST = 0x80040170, + CACHE_S_FIRST = 0x00040170, + CACHE_E_NOCACHE_UPDATED = 0x80040170, + CACHE_S_FORMATETC_NOTSUPPORTED = 0x00040170, + CACHE_S_SAMECACHE = 0x00040171, + CACHE_S_SOMECACHES_NOTUPDATED = 0x00040172, + CACHE_E_LAST = 0x8004017F, + CACHE_S_LAST = 0x0004017F, + OLEOBJ_E_FIRST = 0x80040180, + OLEOBJ_S_FIRST = 0x00040180, + OLEOBJ_E_NOVERBS = 0x80040180, + OLEOBJ_S_INVALIDVERB = 0x00040180, + OLEOBJ_E_INVALIDVERB = 0x80040181, + OLEOBJ_S_CANNOT_DOVERB_NOW = 0x00040181, + OLEOBJ_S_INVALIDHWND = 0x00040182, + OLEOBJ_E_LAST = 0x8004018F, + OLEOBJ_S_LAST = 0x0004018F, + CLIENTSITE_E_FIRST = 0x80040190, + CLIENTSITE_S_FIRST = 0x00040190, + CLIENTSITE_E_LAST = 0x8004019F, + CLIENTSITE_S_LAST = 0x0004019F, + INPLACE_E_NOTUNDOABLE = 0x800401A0, + INPLACE_E_FIRST = 0x800401A0, + INPLACE_S_FIRST = 0x000401A0, + INPLACE_S_TRUNCATED = 0x000401A0, + INPLACE_E_NOTOOLSPACE = 0x800401A1, + INPLACE_E_LAST = 0x800401AF, + INPLACE_S_LAST = 0x000401AF, + ENUM_E_FIRST = 0x800401B0, + ENUM_S_FIRST = 0x000401B0, + ENUM_E_LAST = 0x800401BF, + ENUM_S_LAST = 0x000401BF, + CONVERT10_E_FIRST = 0x800401C0, + CONVERT10_S_FIRST = 0x000401C0, + CONVERT10_E_OLESTREAM_GET = 0x800401C0, + CONVERT10_S_NO_PRESENTATION = 0x000401C0, + CONVERT10_E_OLESTREAM_PUT = 0x800401C1, + CONVERT10_E_OLESTREAM_FMT = 0x800401C2, + CONVERT10_E_OLESTREAM_BITMAP_TO_DIB = 0x800401C3, + CONVERT10_E_STG_FMT = 0x800401C4, + CONVERT10_E_STG_NO_STD_STREAM = 0x800401C5, + CONVERT10_E_STG_DIB_TO_BITMAP = 0x800401C6, + CONVERT10_E_LAST = 0x800401CF, + CONVERT10_S_LAST = 0x000401CF, + CLIPBRD_E_FIRST = 0x800401D0, + CLIPBRD_S_FIRST = 0x000401D0, + CLIPBRD_E_CANT_OPEN = 0x800401D0, + CLIPBRD_E_CANT_EMPTY = 0x800401D1, + CLIPBRD_E_CANT_SET = 0x800401D2, + CLIPBRD_E_BAD_DATA = 0x800401D3, + CLIPBRD_E_CANT_CLOSE = 0x800401D4, + CLIPBRD_E_LAST = 0x800401DF, + CLIPBRD_S_LAST = 0x000401DF, + MK_E_FIRST = 0x800401E0, + MK_S_FIRST = 0x000401E0, + MK_E_CONNECTMANUALLY = 0x800401E0, + MK_E_EXCEEDEDDEADLINE = 0x800401E1, + MK_E_NEEDGENERIC = 0x800401E2, + MK_S_REDUCED_TO_SELF = 0x000401E2, + MK_E_UNAVAILABLE = 0x800401E3, + MK_E_SYNTAX = 0x800401E4, + MK_S_ME = 0x000401E4, + MK_E_NOOBJECT = 0x800401E5, + MK_S_HIM = 0x000401E5, + MK_E_INVALIDEXTENSION = 0x800401E6, + MK_S_US = 0x000401E6, + MK_E_INTERMEDIATEINTERFACENOTSUPPORTED = 0x800401E7, + MK_S_MONIKERALREADYREGISTERED = 0x000401E7, + MK_E_NOTBINDABLE = 0x800401E8, + MK_E_NOTBOUND = 0x800401E9, + MK_E_CANTOPENFILE = 0x800401EA, + MK_E_MUSTBOTHERUSER = 0x800401EB, + MK_E_NOINVERSE = 0x800401EC, + MK_E_NOSTORAGE = 0x800401ED, + MK_E_NOPREFIX = 0x800401EE, + MK_E_LAST = 0x800401EF, + MK_S_LAST = 0x000401EF, + MK_E_ENUMERATION_FAILED = 0x800401EF, + CO_E_FIRST = 0x800401F0, + CO_S_FIRST = 0x000401F0, + CO_E_NOTINITIALIZED = 0x800401F0, + CO_E_ALREADYINITIALIZED = 0x800401F1, + CO_E_CANTDETERMINECLASS = 0x800401F2, + CO_E_CLASSSTRING = 0x800401F3, + CO_E_IIDSTRING = 0x800401F4, + CO_E_APPNOTFOUND = 0x800401F5, + CO_E_APPSINGLEUSE = 0x800401F6, + CO_E_ERRORINAPP = 0x800401F7, + CO_E_DLLNOTFOUND = 0x800401F8, + CO_E_ERRORINDLL = 0x800401F9, + CO_E_WRONGOSFORAPP = 0x800401FA, + CO_E_OBJNOTREG = 0x800401FB, + CO_E_OBJISREG = 0x800401FC, + CO_E_OBJNOTCONNECTED = 0x800401FD, + CO_E_APPDIDNTREG = 0x800401FE, + CO_E_LAST = 0x800401FF, + CO_S_LAST = 0x000401FF, + CO_E_RELEASED = 0x800401FF, + + CO_E_CLASS_CREATE_FAILED = 0x80080001, + CO_E_SCM_ERROR = 0x80080002, + CO_E_SCM_RPC_FAILURE = 0x80080003, + CO_E_BAD_PATH = 0x80080004, + CO_E_SERVER_EXEC_FAILURE = 0x80080005, + CO_E_OBJSRV_RPC_FAILURE = 0x80080006, + MK_E_NO_NORMALIZED = 0x80080007, + CO_E_SERVER_STOPPING = 0x80080008, + MEM_E_INVALID_ROOT = 0x80080009, + MEM_E_INVALID_LINK = 0x80080010, + MEM_E_INVALID_SIZE = 0x80080011, + CO_S_NOTALLINTERFACES = 0x00080012, + + NTE_BAD_UID = 0x80090001, + NTE_BAD_HASH = 0x80090002, + NTE_BAD_KEY = 0x80090003, + NTE_BAD_LEN = 0x80090004, + NTE_BAD_DATA = 0x80090005, + NTE_BAD_SIGNATURE = 0x80090006, + NTE_BAD_VER = 0x80090007, + NTE_BAD_ALGID = 0x80090008, + NTE_BAD_FLAGS = 0x80090009, + NTE_BAD_TYPE = 0x8009000A, + NTE_BAD_KEY_STATE = 0x8009000B, + NTE_BAD_HASH_STATE = 0x8009000C, + NTE_NO_KEY = 0x8009000D, + NTE_NO_MEMORY = 0x8009000E, + NTE_EXISTS = 0x8009000F, + NTE_PERM = 0x80090010, + NTE_NOT_FOUND = 0x80090011, + NTE_DOUBLE_ENCRYPT = 0x80090012, + NTE_BAD_PROVIDER = 0x80090013, + NTE_BAD_PROV_TYPE = 0x80090014, + NTE_BAD_PUBLIC_KEY = 0x80090015, + NTE_BAD_KEYSET = 0x80090016, + NTE_PROV_TYPE_NOT_DEF = 0x80090017, + NTE_PROV_TYPE_ENTRY_BAD = 0x80090018, + NTE_KEYSET_NOT_DEF = 0x80090019, + NTE_KEYSET_ENTRY_BAD = 0x8009001A, + NTE_PROV_TYPE_NO_MATCH = 0x8009001B, + NTE_SIGNATURE_FILE_BAD = 0x8009001C, + NTE_PROVIDER_DLL_FAIL = 0x8009001D, + NTE_PROV_DLL_NOT_FOUND = 0x8009001E, + NTE_BAD_KEYSET_PARAM = 0x8009001F, + NTE_FAIL = 0x80090020, + NTE_SYS_ERR = 0x80090021 +} + + +enum : uint { + SEVERITY_SUCCESS = 0, + SEVERITY_ERROR = 1 +} + +enum : uint { + FACILITY_NULL = 0, + FACILITY_RPC, + FACILITY_DISPATCH, + FACILITY_STORAGE, + FACILITY_ITF, // = 4 + FACILITY_WIN32 = 7, + FACILITY_WINDOWS = 8, + FACILITY_CONTROL = 10, + FACILITY_NT_BIT = 0x10000000 +} + +// C Macros + +pure nothrow @nogc { + bool SUCCEEDED(HRESULT Status) { + return Status >= 0; + } + + bool FAILED(HRESULT Status) { + return Status < 0; + } + + bool IS_ERROR(HRESULT Status) { + return (Status >>> 31) == SEVERITY_ERROR; + } + + ushort HRESULT_CODE(HRESULT r) { + return cast(ushort) (r & 0xFFFF); + } + + ushort SCODE_CODE(SCODE r) { + return cast(ushort) (r & 0xFFFF); + } + + ushort HRESULT_FACILITY(HRESULT r) { + return cast(ushort) ((r>>16) & 0x1fff); + } + + ushort SCODE_FACILITY(SCODE r) { + return cast(ushort) ((r>>16) & 0x1fff); + } + + ushort HRESULT_SEVERITY(HRESULT r) { + return cast(ushort) ((r>>31) & 0x1); + } + + ushort SCODE_SEVERITY(SCODE r) { + return cast(ushort) ((r>>31) & 0x1); + } + + HRESULT MAKE_HRESULT(bool s, uint f, uint c) { + return (s << 31) | (f << 16) | c; + } + + SCODE MAKE_SCODE(bool s, uint f, uint c) { + return (s << 31) | (f << 16) | c; + } + + SCODE GetScode(HRESULT hr) { + return hr; + } + + HRESULT ResultFromScode(SCODE c) { + return c; + } + + HRESULT HRESULT_FROM_NT(HRESULT x) { + return x | FACILITY_NT_BIT; + } + + HRESULT HRESULT_FROM_WIN32(HRESULT x) { + return x ? (x & 0x0000FFFF) | (FACILITY_WIN32 << 16) | 0x80000000 : 0; + } + + HRESULT PropagateResult(HRESULT hrPrevious, SCODE scBase) { + return scBase; + } +} diff --git a/src/urt/internal/sys/windows/winnt.d b/src/urt/internal/sys/windows/winnt.d new file mode 100644 index 0000000..882b21d --- /dev/null +++ b/src/urt/internal/sys/windows/winnt.d @@ -0,0 +1,4321 @@ +/** + * Windows API header module + * + * Translated from MinGW API for MS-Windows 3.12 + * + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_winnt.d) + */ +module urt.internal.sys.windows.winnt; +version (Windows): + +version (ANSI) {} else version = Unicode; + +public import urt.internal.sys.windows.basetsd, urt.internal.sys.windows.windef, urt.internal.sys.windows.winerror; +import urt.internal.sys.windows.w32api; + +/* Translation Notes: +The following macros are unneeded for D: +FIELD_OFFSET(t,f), CONTAINING_RECORD(address, type, field) +*/ + +alias void VOID; +alias char CHAR, CCHAR; +alias wchar WCHAR; +alias bool BOOLEAN; +alias byte FCHAR; +alias ubyte UCHAR; +alias short SHORT; +alias ushort LANGID, FSHORT; +alias uint LCID, FLONG, ACCESS_MASK; +alias long LONGLONG, USN; +alias ulong DWORDLONG, ULONGLONG; + +alias void* PVOID, LPVOID; +alias char* PSZ, PCHAR, PCCHAR, LPCH, PCH, LPSTR, PSTR; +alias wchar* PWCHAR, LPWCH, PWCH, LPWSTR, PWSTR; +alias bool* PBOOLEAN; +alias ubyte* PUCHAR; +alias short* PSHORT; +alias int* PLONG; +alias uint* PLCID, PACCESS_MASK; +alias long* PLONGLONG; +alias ulong* PDWORDLONG, PULONGLONG; + +// FIXME(MinGW) for __WIN64 +alias void* PVOID64; + +// const versions +alias const(char)* PCCH, LPCCH, PCSTR, LPCSTR; +alias const(wchar)* LPCWCH, PCWCH, LPCWSTR, PCWSTR; + +alias PSTR* PZPSTR; +alias PWSTR* PZPWSTR; + +version (Unicode) { + alias WCHAR TCHAR, _TCHAR; +} else { + alias CHAR TCHAR, _TCHAR; +} + +alias TCHAR TBYTE; +alias TCHAR* PTCH , PTBYTE, LPTCH , PTSTR , LPTSTR , LP, PTCHAR; +alias const(TCHAR)* PCTCH, LPCTCH, PCTSTR, LPCTSTR ; + +enum char ANSI_NULL = '\0'; +enum wchar UNICODE_NULL = '\0'; + +enum APPLICATION_ERROR_MASK = 0x20000000; +enum ERROR_SEVERITY_SUCCESS = 0x00000000; +enum ERROR_SEVERITY_INFORMATIONAL = 0x40000000; +enum ERROR_SEVERITY_WARNING = 0x80000000; +enum ERROR_SEVERITY_ERROR = 0xC0000000; + +// MinGW: also in ddk/ntifs.h +enum : USHORT { + COMPRESSION_FORMAT_NONE = 0x0000, + COMPRESSION_FORMAT_DEFAULT = 0x0001, + COMPRESSION_FORMAT_LZNT1 = 0x0002, + COMPRESSION_ENGINE_STANDARD = 0x0000, + COMPRESSION_ENGINE_MAXIMUM = 0x0100, + COMPRESSION_ENGINE_HIBER = 0x0200 +} + +// ACCESS_DENIED_OBJECT_ACE, etc +enum DWORD + ACE_OBJECT_TYPE_PRESENT = 0x00000001, + ACE_INHERITED_OBJECT_TYPE_PRESENT = 0x00000002; + +// ACE_HEADER.AceType +// also in ddk/ntifs.h +enum : BYTE { + ACCESS_ALLOWED_ACE_TYPE, + ACCESS_DENIED_ACE_TYPE, + SYSTEM_AUDIT_ACE_TYPE, + SYSTEM_ALARM_ACE_TYPE +} + +// ACE_HEADER.AceFlags +enum BYTE + OBJECT_INHERIT_ACE = 0x01, + CONTAINER_INHERIT_ACE = 0x02, + NO_PROPAGATE_INHERIT_ACE = 0x04, + INHERIT_ONLY_ACE = 0x08, + INHERITED_ACE = 0x10, + VALID_INHERIT_FLAGS = 0x1F, + SUCCESSFUL_ACCESS_ACE_FLAG = 0x40, + FAILED_ACCESS_ACE_FLAG = 0x80; + +// Access Mask Format +enum ACCESS_MASK + DELETE = 0x00010000, + READ_CONTROL = 0x00020000, + WRITE_DAC = 0x00040000, + WRITE_OWNER = 0x00080000, + SYNCHRONIZE = 0x00100000, + ACCESS_SYSTEM_SECURITY = 0x01000000, + MAXIMUM_ALLOWED = 0x02000000, + GENERIC_READ = 0x80000000, + GENERIC_WRITE = 0x40000000, + GENERIC_EXECUTE = 0x20000000, + GENERIC_ALL = 0x10000000, + STANDARD_RIGHTS_REQUIRED = 0x000F0000, + STANDARD_RIGHTS_READ = 0x00020000, + STANDARD_RIGHTS_WRITE = 0x00020000, + STANDARD_RIGHTS_EXECUTE = 0x00020000, + STANDARD_RIGHTS_ALL = 0x001F0000, + SPECIFIC_RIGHTS_ALL = 0x0000FFFF; + + +enum DWORD INVALID_FILE_ATTRIBUTES = -1; + +// MinGW: Also in ddk/winddk.h +enum DWORD + FILE_LIST_DIRECTORY = 0x00000001, + FILE_READ_DATA = 0x00000001, + FILE_ADD_FILE = 0x00000002, + FILE_WRITE_DATA = 0x00000002, + FILE_ADD_SUBDIRECTORY = 0x00000004, + FILE_APPEND_DATA = 0x00000004, + FILE_CREATE_PIPE_INSTANCE = 0x00000004, + FILE_READ_EA = 0x00000008, + FILE_READ_PROPERTIES = 0x00000008, + FILE_WRITE_EA = 0x00000010, + FILE_WRITE_PROPERTIES = 0x00000010, + FILE_EXECUTE = 0x00000020, + FILE_TRAVERSE = 0x00000020, + FILE_DELETE_CHILD = 0x00000040, + FILE_READ_ATTRIBUTES = 0x00000080, + FILE_WRITE_ATTRIBUTES = 0x00000100; + +enum DWORD + FILE_SHARE_READ = 0x00000001, + FILE_SHARE_WRITE = 0x00000002, + FILE_SHARE_DELETE = 0x00000004, + FILE_SHARE_VALID_FLAGS = 0x00000007; + +enum DWORD + FILE_ATTRIBUTE_READONLY = 0x00000001, + FILE_ATTRIBUTE_HIDDEN = 0x00000002, + FILE_ATTRIBUTE_SYSTEM = 0x00000004, + FILE_ATTRIBUTE_DIRECTORY = 0x00000010, + FILE_ATTRIBUTE_ARCHIVE = 0x00000020, + FILE_ATTRIBUTE_DEVICE = 0x00000040, + FILE_ATTRIBUTE_NORMAL = 0x00000080, + FILE_ATTRIBUTE_TEMPORARY = 0x00000100, + FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200, + FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400, + FILE_ATTRIBUTE_COMPRESSED = 0x00000800, + FILE_ATTRIBUTE_OFFLINE = 0x00001000, + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000, + FILE_ATTRIBUTE_ENCRYPTED = 0x00004000, + FILE_ATTRIBUTE_VALID_FLAGS = 0x00007fb7, + FILE_ATTRIBUTE_VALID_SET_FLAGS = 0x000031a7; + +// These are not documented on MSDN +enum FILE_COPY_STRUCTURED_STORAGE = 0x00000041; +enum FILE_STRUCTURED_STORAGE = 0x00000441; + +// Nor are these +enum FILE_VALID_OPTION_FLAGS = 0x00ffffff; +enum FILE_VALID_PIPE_OPTION_FLAGS = 0x00000032; +enum FILE_VALID_MAILSLOT_OPTION_FLAGS = 0x00000032; +enum FILE_VALID_SET_FLAGS = 0x00000036; + +enum ULONG + FILE_SUPERSEDE = 0x00000000, + FILE_OPEN = 0x00000001, + FILE_CREATE = 0x00000002, + FILE_OPEN_IF = 0x00000003, + FILE_OVERWRITE = 0x00000004, + FILE_OVERWRITE_IF = 0x00000005, + FILE_MAXIMUM_DISPOSITION = 0x00000005; + +enum ULONG + FILE_DIRECTORY_FILE = 0x00000001, + FILE_WRITE_THROUGH = 0x00000002, + FILE_SEQUENTIAL_ONLY = 0x00000004, + FILE_NO_INTERMEDIATE_BUFFERING = 0x00000008, + FILE_SYNCHRONOUS_IO_ALERT = 0x00000010, + FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020, + FILE_NON_DIRECTORY_FILE = 0x00000040, + FILE_CREATE_TREE_CONNECTION = 0x00000080, + FILE_COMPLETE_IF_OPLOCKED = 0x00000100, + FILE_NO_EA_KNOWLEDGE = 0x00000200, + FILE_OPEN_FOR_RECOVERY = 0x00000400, + FILE_RANDOM_ACCESS = 0x00000800, + FILE_DELETE_ON_CLOSE = 0x00001000, + FILE_OPEN_BY_FILE_ID = 0x00002000, + FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000, + FILE_NO_COMPRESSION = 0x00008000, + FILE_RESERVE_OPFILTER = 0x00100000, + FILE_OPEN_REPARSE_POINT = 0x00200000, + FILE_OPEN_NO_RECALL = 0x00400000, + FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000; + + +enum ACCESS_MASK + FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x01FF, + FILE_GENERIC_EXECUTE = STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES + | FILE_EXECUTE | SYNCHRONIZE, + FILE_GENERIC_READ = STANDARD_RIGHTS_READ | FILE_READ_DATA + | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE, + FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA + | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA + | SYNCHRONIZE; + +// MinGW: end winddk.h +// MinGW: also in ddk/ntifs.h +enum DWORD + FILE_NOTIFY_CHANGE_FILE_NAME = 0x00000001, + FILE_NOTIFY_CHANGE_DIR_NAME = 0x00000002, + FILE_NOTIFY_CHANGE_NAME = 0x00000003, + FILE_NOTIFY_CHANGE_ATTRIBUTES = 0x00000004, + FILE_NOTIFY_CHANGE_SIZE = 0x00000008, + FILE_NOTIFY_CHANGE_LAST_WRITE = 0x00000010, + FILE_NOTIFY_CHANGE_LAST_ACCESS = 0x00000020, + FILE_NOTIFY_CHANGE_CREATION = 0x00000040, + FILE_NOTIFY_CHANGE_EA = 0x00000080, + FILE_NOTIFY_CHANGE_SECURITY = 0x00000100, + FILE_NOTIFY_CHANGE_STREAM_NAME = 0x00000200, + FILE_NOTIFY_CHANGE_STREAM_SIZE = 0x00000400, + FILE_NOTIFY_CHANGE_STREAM_WRITE = 0x00000800, + FILE_NOTIFY_VALID_MASK = 0x00000fff; + +enum DWORD + FILE_CASE_SENSITIVE_SEARCH = 0x00000001, + FILE_CASE_PRESERVED_NAMES = 0x00000002, + FILE_UNICODE_ON_DISK = 0x00000004, + FILE_PERSISTENT_ACLS = 0x00000008, + FILE_FILE_COMPRESSION = 0x00000010, + FILE_VOLUME_QUOTAS = 0x00000020, + FILE_SUPPORTS_SPARSE_FILES = 0x00000040, + FILE_SUPPORTS_REPARSE_POINTS = 0x00000080, + FILE_SUPPORTS_REMOTE_STORAGE = 0x00000100, + FS_LFN_APIS = 0x00004000, + FILE_VOLUME_IS_COMPRESSED = 0x00008000, + FILE_SUPPORTS_OBJECT_IDS = 0x00010000, + FILE_SUPPORTS_ENCRYPTION = 0x00020000, + FILE_NAMED_STREAMS = 0x00040000, + FILE_READ_ONLY_VOLUME = 0x00080000, + FILE_SEQUENTIAL_WRITE_ONCE = 0x00100000, + FILE_SUPPORTS_TRANSACTIONS = 0x00200000; + +// These are not documented on MSDN +enum ACCESS_MASK + IO_COMPLETION_QUERY_STATE = 1, + IO_COMPLETION_MODIFY_STATE = 2, + IO_COMPLETION_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 3; +// MinGW: end ntifs.h + +// MinGW: also in ddk/winddk.h +enum DWORD + DUPLICATE_CLOSE_SOURCE = 1, + DUPLICATE_SAME_ACCESS = 2, + DUPLICATE_SAME_ATTRIBUTES = 4; +// MinGW: end winddk.k + +enum DWORD + MAILSLOT_NO_MESSAGE = -1, + MAILSLOT_WAIT_FOREVER = -1; + +enum ACCESS_MASK + PROCESS_TERMINATE = 0x0001, + PROCESS_CREATE_THREAD = 0x0002, + PROCESS_SET_SESSIONID = 0x0004, + PROCESS_VM_OPERATION = 0x0008, + PROCESS_VM_READ = 0x0010, + PROCESS_VM_WRITE = 0x0020, + PROCESS_DUP_HANDLE = 0x0040, + PROCESS_CREATE_PROCESS = 0x0080, + PROCESS_SET_QUOTA = 0x0100, + PROCESS_SET_INFORMATION = 0x0200, + PROCESS_QUERY_INFORMATION = 0x0400, + PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x0FFF; + +enum ACCESS_MASK + THREAD_TERMINATE = 0x0001, + THREAD_SUSPEND_RESUME = 0x0002, + THREAD_GET_CONTEXT = 0x0008, + THREAD_SET_CONTEXT = 0x0010, + THREAD_SET_INFORMATION = 0x0020, + THREAD_QUERY_INFORMATION = 0x0040, + THREAD_SET_THREAD_TOKEN = 0x0080, + THREAD_IMPERSONATE = 0x0100, + THREAD_DIRECT_IMPERSONATION = 0x0200, + THREAD_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED|SYNCHRONIZE|0x3FF; + +// These are not documented on MSDN +enum THREAD_BASE_PRIORITY_LOWRT = 15; +enum THREAD_BASE_PRIORITY_MAX = 2; +enum THREAD_BASE_PRIORITY_MIN = -2; +enum THREAD_BASE_PRIORITY_IDLE = -15; + +enum DWORD EXCEPTION_NONCONTINUABLE = 1; +enum size_t EXCEPTION_MAXIMUM_PARAMETERS = 15; + +// These are not documented on MSDN +enum ACCESS_MASK + MUTANT_QUERY_STATE = 1, + MUTANT_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | MUTANT_QUERY_STATE; + +enum ACCESS_MASK + TIMER_QUERY_STATE = 1, + TIMER_MODIFY_STATE = 2, + TIMER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | TIMER_QUERY_STATE + | TIMER_MODIFY_STATE; + +enum SID_IDENTIFIER_AUTHORITY + SECURITY_NULL_SID_AUTHORITY = {[5: 0]}, + SECURITY_WORLD_SID_AUTHORITY = {[5: 1]}, + SECURITY_LOCAL_SID_AUTHORITY = {[5: 2]}, + SECURITY_CREATOR_SID_AUTHORITY = {[5: 3]}, + SECURITY_NON_UNIQUE_AUTHORITY = {[5: 4]}, + SECURITY_NT_AUTHORITY = {[5: 5]}, + SECURITY_MANDATORY_LABEL_AUTHORITY = {[5: 6]}; + +enum DWORD + SECURITY_NULL_RID = 0, + SECURITY_WORLD_RID = 0, + SECURITY_LOCAL_RID = 0, + SECURITY_CREATOR_OWNER_RID = 0, + SECURITY_CREATOR_GROUP_RID = 1, + SECURITY_DIALUP_RID = 1, + SECURITY_NETWORK_RID = 2, + SECURITY_BATCH_RID = 3, + SECURITY_INTERACTIVE_RID = 4, + SECURITY_LOGON_IDS_RID = 5, + SECURITY_SERVICE_RID = 6, + SECURITY_LOCAL_SYSTEM_RID = 18, + SECURITY_BUILTIN_DOMAIN_RID = 32, + SECURITY_PRINCIPAL_SELF_RID = 10, + SECURITY_CREATOR_OWNER_SERVER_RID = 2, + SECURITY_CREATOR_GROUP_SERVER_RID = 3, + SECURITY_LOGON_IDS_RID_COUNT = 3, + SECURITY_ANONYMOUS_LOGON_RID = 7, + SECURITY_PROXY_RID = 8, + SECURITY_ENTERPRISE_CONTROLLERS_RID = 9, + SECURITY_SERVER_LOGON_RID = SECURITY_ENTERPRISE_CONTROLLERS_RID, + SECURITY_AUTHENTICATED_USER_RID = 11, + SECURITY_RESTRICTED_CODE_RID = 12, + SECURITY_NT_NON_UNIQUE_RID = 21, + SID_REVISION = 1; + +enum : DWORD { + DOMAIN_USER_RID_ADMIN = 0x01F4, + DOMAIN_USER_RID_GUEST = 0x01F5, + DOMAIN_GROUP_RID_ADMINS = 0x0200, + DOMAIN_GROUP_RID_USERS = 0x0201, + DOMAIN_ALIAS_RID_ADMINS = 0x0220, + DOMAIN_ALIAS_RID_USERS = 0x0221, + DOMAIN_ALIAS_RID_GUESTS = 0x0222, + DOMAIN_ALIAS_RID_POWER_USERS = 0x0223, + DOMAIN_ALIAS_RID_ACCOUNT_OPS = 0x0224, + DOMAIN_ALIAS_RID_SYSTEM_OPS = 0x0225, + DOMAIN_ALIAS_RID_PRINT_OPS = 0x0226, + DOMAIN_ALIAS_RID_BACKUP_OPS = 0x0227, + DOMAIN_ALIAS_RID_REPLICATOR = 0x0228 +} + +enum : WORD { + SECURITY_MANDATORY_UNTRUSTED_RID = 0, + SECURITY_MANDATORY_LOW_RID = 0x1000, + SECURITY_MANDATORY_MEDIUM_RID = 0x2000, + SECURITY_MANDATORY_HIGH_RID = 0x3000, + SECURITY_MANDATORY_SYSTEM_RID = 0x4000, + SECURITY_MANDATORY_PROTECTED_PROCESS_RID = 0x5000, + SECURITY_MANDATORY_MAXIMUM_USER_RID = SECURITY_MANDATORY_SYSTEM_RID +} + +const TCHAR[] + SE_CREATE_TOKEN_NAME = "SeCreateTokenPrivilege", + SE_ASSIGNPRIMARYTOKEN_NAME = "SeAssignPrimaryTokenPrivilege", + SE_LOCK_MEMORY_NAME = "SeLockMemoryPrivilege", + SE_INCREASE_QUOTA_NAME = "SeIncreaseQuotaPrivilege", + SE_UNSOLICITED_INPUT_NAME = "SeUnsolicitedInputPrivilege", + SE_MACHINE_ACCOUNT_NAME = "SeMachineAccountPrivilege", + SE_TCB_NAME = "SeTcbPrivilege", + SE_SECURITY_NAME = "SeSecurityPrivilege", + SE_TAKE_OWNERSHIP_NAME = "SeTakeOwnershipPrivilege", + SE_LOAD_DRIVER_NAME = "SeLoadDriverPrivilege", + SE_SYSTEM_PROFILE_NAME = "SeSystemProfilePrivilege", + SE_SYSTEMTIME_NAME = "SeSystemtimePrivilege", + SE_PROF_SINGLE_PROCESS_NAME = "SeProfileSingleProcessPrivilege", + SE_INC_BASE_PRIORITY_NAME = "SeIncreaseBasePriorityPrivilege", + SE_CREATE_PAGEFILE_NAME = "SeCreatePagefilePrivilege", + SE_CREATE_PERMANENT_NAME = "SeCreatePermanentPrivilege", + SE_BACKUP_NAME = "SeBackupPrivilege", + SE_RESTORE_NAME = "SeRestorePrivilege", + SE_SHUTDOWN_NAME = "SeShutdownPrivilege", + SE_DEBUG_NAME = "SeDebugPrivilege", + SE_AUDIT_NAME = "SeAuditPrivilege", + SE_SYSTEM_ENVIRONMENT_NAME = "SeSystemEnvironmentPrivilege", + SE_CHANGE_NOTIFY_NAME = "SeChangeNotifyPrivilege", + SE_REMOTE_SHUTDOWN_NAME = "SeRemoteShutdownPrivilege", + SE_CREATE_GLOBAL_NAME = "SeCreateGlobalPrivilege", + SE_UNDOCK_NAME = "SeUndockPrivilege", + SE_MANAGE_VOLUME_NAME = "SeManageVolumePrivilege", + SE_IMPERSONATE_NAME = "SeImpersonatePrivilege", + SE_ENABLE_DELEGATION_NAME = "SeEnableDelegationPrivilege", + SE_SYNC_AGENT_NAME = "SeSyncAgentPrivilege", + SE_TRUSTED_CREDMAN_ACCESS_NAME = "SeTrustedCredManAccessPrivilege", + SE_RELABEL_NAME = "SeRelabelPrivilege", + SE_INCREASE_WORKING_SET_NAME = "SeIncreaseWorkingSetPrivilege", + SE_TIME_ZONE_NAME = "SeTimeZonePrivilege", + SE_CREATE_SYMBOLIC_LINK_NAME = "SeCreateSymbolicLinkPrivilege"; + +enum DWORD + SE_GROUP_MANDATORY = 0x00000001, + SE_GROUP_ENABLED_BY_DEFAULT = 0x00000002, + SE_GROUP_ENABLED = 0x00000004, + SE_GROUP_OWNER = 0x00000008, + SE_GROUP_USE_FOR_DENY_ONLY = 0x00000010, + SE_GROUP_INTEGRITY = 0x00000020, + SE_GROUP_INTEGRITY_ENABLED = 0x00000040, + SE_GROUP_RESOURCE = 0x20000000, + SE_GROUP_LOGON_ID = 0xC0000000; + +// Primary language identifiers +enum : USHORT { + LANG_NEUTRAL, + LANG_ARABIC, + LANG_BULGARIAN, + LANG_CATALAN, + LANG_CHINESE, + LANG_CZECH, + LANG_DANISH, + LANG_GERMAN, + LANG_GREEK, + LANG_ENGLISH, + LANG_SPANISH, + LANG_FINNISH, + LANG_FRENCH, + LANG_HEBREW, + LANG_HUNGARIAN, + LANG_ICELANDIC, + LANG_ITALIAN, + LANG_JAPANESE, + LANG_KOREAN, + LANG_DUTCH, + LANG_NORWEGIAN, + LANG_POLISH, + LANG_PORTUGUESE, // = 0x16 + LANG_ROMANIAN = 0x18, + LANG_RUSSIAN, + LANG_CROATIAN, // = 0x1A + LANG_SERBIAN = 0x1A, + LANG_BOSNIAN = 0x1A, + LANG_SLOVAK, + LANG_ALBANIAN, + LANG_SWEDISH, + LANG_THAI, + LANG_TURKISH, + LANG_URDU, + LANG_INDONESIAN, + LANG_UKRAINIAN, + LANG_BELARUSIAN, + LANG_SLOVENIAN, + LANG_ESTONIAN, + LANG_LATVIAN, + LANG_LITHUANIAN, // = 0x27 + LANG_FARSI = 0x29, + LANG_PERSIAN = 0x29, + LANG_VIETNAMESE, + LANG_ARMENIAN, + LANG_AZERI, + LANG_BASQUE, + LANG_LOWER_SORBIAN, // = 0x2E + LANG_UPPER_SORBIAN = 0x2E, + LANG_MACEDONIAN, // = 0x2F + LANG_TSWANA = 0x32, + LANG_XHOSA = 0x34, + LANG_ZULU, + LANG_AFRIKAANS, + LANG_GEORGIAN, + LANG_FAEROESE, + LANG_HINDI, + LANG_MALTESE, + LANG_SAMI, + LANG_IRISH, // = 0x3C + LANG_MALAY = 0x3E, + LANG_KAZAK, + LANG_KYRGYZ, + LANG_SWAHILI, // = 0x41 + LANG_UZBEK = 0x43, + LANG_TATAR, + LANG_BENGALI, + LANG_PUNJABI, + LANG_GUJARATI, + LANG_ORIYA, + LANG_TAMIL, + LANG_TELUGU, + LANG_KANNADA, + LANG_MALAYALAM, + LANG_ASSAMESE, + LANG_MARATHI, + LANG_SANSKRIT, + LANG_MONGOLIAN, + LANG_TIBETAN, + LANG_WELSH, + LANG_KHMER, + LANG_LAO, // = 0x54 + LANG_GALICIAN = 0x56, + LANG_KONKANI, + LANG_MANIPURI, + LANG_SINDHI, + LANG_SYRIAC, + LANG_SINHALESE, // = 0x5B + LANG_INUKTITUT = 0x5D, + LANG_AMHARIC, + LANG_TAMAZIGHT, + LANG_KASHMIRI, + LANG_NEPALI, + LANG_FRISIAN, + LANG_PASHTO, + LANG_FILIPINO, + LANG_DIVEHI, // = 0x65 + LANG_HAUSA = 0x68, + LANG_YORUBA = 0x6A, + LANG_QUECHUA, + LANG_SOTHO, + LANG_BASHKIR, + LANG_LUXEMBOURGISH, + LANG_GREENLANDIC, + LANG_IGBO, // = 0x70 + LANG_TIGRIGNA = 0x73, + LANG_YI = 0x78, + LANG_MAPUDUNGUN = 0x7A, + LANG_MOHAWK = 0x7C, + LANG_BRETON = 0x7E, + LANG_UIGHUR = 0x80, + LANG_MAORI, + LANG_OCCITAN, + LANG_CORSICAN, + LANG_ALSATIAN, + LANG_YAKUT, + LANG_KICHE, + LANG_KINYARWANDA, + LANG_WOLOF, // = 0x88 + LANG_DARI = 0x8C, + LANG_MALAGASY, // = 0x8D + + LANG_SERBIAN_NEUTRAL = 0x7C1A, + LANG_BOSNIAN_NEUTRAL = 0x781A, + + LANG_INVARIANT = 0x7F +} + + +// Sublanguage identifiers +enum : USHORT { + SUBLANG_NEUTRAL, + SUBLANG_DEFAULT, + SUBLANG_SYS_DEFAULT, + SUBLANG_CUSTOM_DEFAULT, // = 3 + SUBLANG_UI_CUSTOM_DEFAULT = 3, + SUBLANG_CUSTOM_UNSPECIFIED, // = 4 + + SUBLANG_AFRIKAANS_SOUTH_AFRICA = 1, + SUBLANG_ALBANIAN_ALBANIA = 1, + SUBLANG_ALSATIAN_FRANCE = 1, + SUBLANG_AMHARIC_ETHIOPIA = 1, + + SUBLANG_ARABIC_SAUDI_ARABIA = 1, + SUBLANG_ARABIC_IRAQ, + SUBLANG_ARABIC_EGYPT, + SUBLANG_ARABIC_LIBYA, + SUBLANG_ARABIC_ALGERIA, + SUBLANG_ARABIC_MOROCCO, + SUBLANG_ARABIC_TUNISIA, + SUBLANG_ARABIC_OMAN, + SUBLANG_ARABIC_YEMEN, + SUBLANG_ARABIC_SYRIA, + SUBLANG_ARABIC_JORDAN, + SUBLANG_ARABIC_LEBANON, + SUBLANG_ARABIC_KUWAIT, + SUBLANG_ARABIC_UAE, + SUBLANG_ARABIC_BAHRAIN, + SUBLANG_ARABIC_QATAR, // = 16 + + SUBLANG_ARMENIAN_ARMENIA = 1, + SUBLANG_ASSAMESE_INDIA = 1, + + SUBLANG_AZERI_LATIN = 1, + SUBLANG_AZERI_CYRILLIC, // = 2 + + SUBLANG_BASHKIR_RUSSIA = 1, + SUBLANG_BASQUE_BASQUE = 1, + SUBLANG_BELARUSIAN_BELARUS = 1, + SUBLANG_BENGALI_INDIA = 1, + + SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_LATIN = 5, + SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_CYRILLIC = 8, + + SUBLANG_BRETON_FRANCE = 1, + SUBLANG_BULGARIAN_BULGARIA = 1, + SUBLANG_CATALAN_CATALAN = 1, + + SUBLANG_CHINESE_TRADITIONAL = 1, + SUBLANG_CHINESE_SIMPLIFIED, + SUBLANG_CHINESE_HONGKONG, + SUBLANG_CHINESE_SINGAPORE, + SUBLANG_CHINESE_MACAU, // = 5 + + SUBLANG_CORSICAN_FRANCE = 1, + + SUBLANG_CROATIAN_CROATIA = 1, + SUBLANG_CROATIAN_BOSNIA_HERZEGOVINA_LATIN = 4, + + SUBLANG_CZECH_CZECH_REPUBLIC = 1, + SUBLANG_DANISH_DENMARK = 1, + SUBLANG_DIVEHI_MALDIVES = 1, + + SUBLANG_DUTCH = 1, + SUBLANG_DUTCH_BELGIAN, // = 2 + + SUBLANG_ENGLISH_US = 1, + SUBLANG_ENGLISH_UK, + SUBLANG_ENGLISH_AUS, + SUBLANG_ENGLISH_CAN, + SUBLANG_ENGLISH_NZ, + SUBLANG_ENGLISH_EIRE, // = 6 + SUBLANG_ENGLISH_IRELAND = 6, + SUBLANG_ENGLISH_SOUTH_AFRICA, + SUBLANG_ENGLISH_JAMAICA, + SUBLANG_ENGLISH_CARIBBEAN, + SUBLANG_ENGLISH_BELIZE, + SUBLANG_ENGLISH_TRINIDAD, + SUBLANG_ENGLISH_ZIMBABWE, + SUBLANG_ENGLISH_PHILIPPINES, // = 13 + SUBLANG_ENGLISH_INDIA = 16, + SUBLANG_ENGLISH_MALAYSIA, + SUBLANG_ENGLISH_SINGAPORE, // = 18 + + SUBLANG_ESTONIAN_ESTONIA = 1, + SUBLANG_FAEROESE_FAROE_ISLANDS = 1, + SUBLANG_FILIPINO_PHILIPPINES = 1, + SUBLANG_FINNISH_FINLAND = 1, + + SUBLANG_FRENCH = 1, + SUBLANG_FRENCH_BELGIAN, + SUBLANG_FRENCH_CANADIAN, + SUBLANG_FRENCH_SWISS, + SUBLANG_FRENCH_LUXEMBOURG, + SUBLANG_FRENCH_MONACO, // = 6 + + SUBLANG_FRISIAN_NETHERLANDS = 1, + SUBLANG_GALICIAN_GALICIAN = 1, + SUBLANG_GEORGIAN_GEORGIA = 1, + + SUBLANG_GERMAN = 1, + SUBLANG_GERMAN_SWISS, + SUBLANG_GERMAN_AUSTRIAN, + SUBLANG_GERMAN_LUXEMBOURG, + SUBLANG_GERMAN_LIECHTENSTEIN, // = 5 + + SUBLANG_GREEK_GREECE = 1, + SUBLANG_GREENLANDIC_GREENLAND = 1, + SUBLANG_GUJARATI_INDIA = 1, + SUBLANG_HAUSA_NIGERIA = 1, + SUBLANG_HEBREW_ISRAEL = 1, + SUBLANG_HINDI_INDIA = 1, + SUBLANG_HUNGARIAN_HUNGARY = 1, + SUBLANG_ICELANDIC_ICELAND = 1, + SUBLANG_IGBO_NIGERIA = 1, + SUBLANG_INDONESIAN_INDONESIA = 1, + + SUBLANG_INUKTITUT_CANADA = 1, + SUBLANG_INUKTITUT_CANADA_LATIN = 1, + + SUBLANG_IRISH_IRELAND = 1, + + SUBLANG_ITALIAN = 1, + SUBLANG_ITALIAN_SWISS, // = 2 + + SUBLANG_JAPANESE_JAPAN = 1, + + SUBLANG_KASHMIRI_INDIA = 2, + SUBLANG_KASHMIRI_SASIA = 2, + + SUBLANG_KAZAK_KAZAKHSTAN = 1, + SUBLANG_KHMER_CAMBODIA = 1, + SUBLANG_KICHE_GUATEMALA = 1, + SUBLANG_KINYARWANDA_RWANDA = 1, + SUBLANG_KONKANI_INDIA = 1, + SUBLANG_KOREAN = 1, + SUBLANG_KOREAN_JOHAB = 2, + SUBLANG_KYRGYZ_KYRGYZSTAN = 1, + SUBLANG_LAO_LAO_PDR = 1, + SUBLANG_LATVIAN_LATVIA = 1, + + SUBLANG_LITHUANIAN = 1, + SUBLANG_LITHUANIAN_LITHUANIA = 1, + + SUBLANG_LOWER_SORBIAN_GERMANY = 1, + SUBLANG_LUXEMBOURGISH_LUXEMBOURG = 1, + SUBLANG_MACEDONIAN_MACEDONIA = 1, + SUBLANG_MALAYALAM_INDIA = 1, + SUBLANG_MALTESE_MALTA = 1, + SUBLANG_MAORI_NEW_ZEALAND = 1, + SUBLANG_MAPUDUNGUN_CHILE = 1, + SUBLANG_MARATHI_INDIA = 1, + SUBLANG_MOHAWK_MOHAWK = 1, + + SUBLANG_MONGOLIAN_CYRILLIC_MONGOLIA = 1, + SUBLANG_MONGOLIAN_PRC, // = 2 + + SUBLANG_MALAY_MALAYSIA = 1, + SUBLANG_MALAY_BRUNEI_DARUSSALAM, // = 2 + + SUBLANG_NEPALI_NEPAL = 1, + SUBLANG_NEPALI_INDIA, // = 2 + + SUBLANG_NORWEGIAN_BOKMAL = 1, + SUBLANG_NORWEGIAN_NYNORSK, // = 2 + + SUBLANG_OCCITAN_FRANCE = 1, + SUBLANG_ORIYA_INDIA = 1, + SUBLANG_PASHTO_AFGHANISTAN = 1, + SUBLANG_PERSIAN_IRAN = 1, + SUBLANG_POLISH_POLAND = 1, + + SUBLANG_PORTUGUESE_BRAZILIAN = 1, + SUBLANG_PORTUGUESE = 2, + SUBLANG_PORTUGUESE_PORTUGAL, // = 2 + + SUBLANG_PUNJABI_INDIA = 1, + + SUBLANG_QUECHUA_BOLIVIA = 1, + SUBLANG_QUECHUA_ECUADOR, + SUBLANG_QUECHUA_PERU, // = 3 + + SUBLANG_ROMANIAN_ROMANIA = 1, + SUBLANG_ROMANSH_SWITZERLAND = 1, + SUBLANG_RUSSIAN_RUSSIA = 1, + + SUBLANG_SAMI_NORTHERN_NORWAY = 1, + SUBLANG_SAMI_NORTHERN_SWEDEN, + SUBLANG_SAMI_NORTHERN_FINLAND, // = 3 + SUBLANG_SAMI_SKOLT_FINLAND = 3, + SUBLANG_SAMI_INARI_FINLAND = 3, + SUBLANG_SAMI_LULE_NORWAY, + SUBLANG_SAMI_LULE_SWEDEN, + SUBLANG_SAMI_SOUTHERN_NORWAY, + SUBLANG_SAMI_SOUTHERN_SWEDEN, // = 7 + + SUBLANG_SANSKRIT_INDIA = 1, + + SUBLANG_SERBIAN_LATIN = 2, + SUBLANG_SERBIAN_CYRILLIC, // = 3 + SUBLANG_SERBIAN_BOSNIA_HERZEGOVINA_LATIN = 6, + SUBLANG_SERBIAN_BOSNIA_HERZEGOVINA_CYRILLIC = 7, + + SUBLANG_SINDHI_AFGHANISTAN = 2, + SUBLANG_SINHALESE_SRI_LANKA = 1, + SUBLANG_SOTHO_NORTHERN_SOUTH_AFRICA = 1, + SUBLANG_SLOVAK_SLOVAKIA = 1, + SUBLANG_SLOVENIAN_SLOVENIA = 1, + + SUBLANG_SPANISH = 1, + SUBLANG_SPANISH_MEXICAN, + SUBLANG_SPANISH_MODERN, + SUBLANG_SPANISH_GUATEMALA, + SUBLANG_SPANISH_COSTA_RICA, + SUBLANG_SPANISH_PANAMA, + SUBLANG_SPANISH_DOMINICAN_REPUBLIC, + SUBLANG_SPANISH_VENEZUELA, + SUBLANG_SPANISH_COLOMBIA, + SUBLANG_SPANISH_PERU, + SUBLANG_SPANISH_ARGENTINA, + SUBLANG_SPANISH_ECUADOR, + SUBLANG_SPANISH_CHILE, + SUBLANG_SPANISH_URUGUAY, + SUBLANG_SPANISH_PARAGUAY, + SUBLANG_SPANISH_BOLIVIA, + SUBLANG_SPANISH_EL_SALVADOR, + SUBLANG_SPANISH_HONDURAS, + SUBLANG_SPANISH_NICARAGUA, + SUBLANG_SPANISH_PUERTO_RICO, + SUBLANG_SPANISH_US, // = 21 + + SUBLANG_SWEDISH = 1, + SUBLANG_SWEDISH_SWEDEN = 1, + SUBLANG_SWEDISH_FINLAND, // = 2 + + SUBLANG_SYRIAC = 1, + SUBLANG_TAJIK_TAJIKISTAN = 1, + SUBLANG_TAMAZIGHT_ALGERIA_LATIN = 2, + SUBLANG_TAMIL_INDIA = 1, + SUBLANG_TATAR_RUSSIA = 1, + SUBLANG_TELUGU_INDIA = 1, + SUBLANG_THAI_THAILAND = 1, + SUBLANG_TIBETAN_PRC = 1, + SUBLANG_TIBETAN_BHUTAN = 2, + SUBLANG_TIGRIGNA_ERITREA = 1, + SUBLANG_TSWANA_SOUTH_AFRICA = 1, + SUBLANG_TURKISH_TURKEY = 1, + SUBLANG_TURKMEN_TURKMENISTAN = 1, + SUBLANG_UIGHUR_PRC = 1, + SUBLANG_UKRAINIAN_UKRAINE = 1, + SUBLANG_UPPER_SORBIAN_GERMANY = 1, + + SUBLANG_URDU_PAKISTAN = 1, + SUBLANG_URDU_INDIA, // = 2 + + SUBLANG_UZBEK_LATIN = 1, + SUBLANG_UZBEK_CYRILLIC, // = 2 + + SUBLANG_VIETNAMESE_VIETNAM = 1, + SUBLANG_WELSH_UNITED_KINGDOM = 1, + SUBLANG_WOLOF_SENEGAL = 1, + SUBLANG_YORUBA_NIGERIA = 1, + SUBLANG_XHOSA_SOUTH_AFRICA = 1, + SUBLANG_YAKUT_RUSSIA = 1, + SUBLANG_YI_PRC = 1, + SUBLANG_ZULU_SOUTH_AFRICA = 1 +} + +// This is not documented on MSDN +enum NLS_VALID_LOCALE_MASK = 1048575; + +// Sorting identifiers +enum : WORD { + SORT_DEFAULT = 0, + SORT_JAPANESE_XJIS = 0, + SORT_JAPANESE_UNICODE = 1, + SORT_CHINESE_BIG5 = 0, + SORT_CHINESE_PRCP = 0, + SORT_CHINESE_UNICODE = 1, + SORT_CHINESE_PRC = 2, + SORT_CHINESE_BOPOMOFO = 3, + SORT_KOREAN_KSC = 0, + SORT_KOREAN_UNICODE = 1, + SORT_GERMAN_PHONE_BOOK = 1, + SORT_HUNGARIAN_DEFAULT = 0, + SORT_HUNGARIAN_TECHNICAL = 1, + SORT_GEORGIAN_TRADITIONAL = 0, + SORT_GEORGIAN_MODERN = 1 +} + +pure nothrow @nogc { + WORD MAKELANGID()(/*USHORT*/uint p, /*USHORT*/ uint s) { return cast(WORD)((s << 10) | p); } + WORD PRIMARYLANGID()(/*WORD*/uint lgid) { return cast(WORD)(lgid & 0x3FF); } + WORD SUBLANGID()(/*WORD*/uint lgid) { return cast(WORD)(lgid >>> 10); } + + DWORD MAKELCID()(/*WORD*/uint lgid, /*WORD*/uint srtid) { return (cast(DWORD) srtid << 16) | cast(DWORD) lgid; } + // ??? + //DWORD MAKESORTLCID()(WORD lgid, WORD srtid, WORD ver) { return (MAKELCID(lgid, srtid)) | ((cast(DWORD)ver) << 20); } + WORD LANGIDFROMLCID()(LCID lcid) { return cast(WORD) lcid; } + WORD SORTIDFROMLCID()(LCID lcid) { return cast(WORD) ((lcid >>> 16) & 0x0F); } + WORD SORTVERSIONFROMLCID()(LCID lcid) { return cast(WORD) ((lcid >>> 20) & 0x0F); } +} + +enum WORD LANG_SYSTEM_DEFAULT = (SUBLANG_SYS_DEFAULT << 10) | LANG_NEUTRAL; +enum WORD LANG_USER_DEFAULT = (SUBLANG_DEFAULT << 10) | LANG_NEUTRAL; +enum DWORD LOCALE_NEUTRAL = (SORT_DEFAULT << 16) + | (SUBLANG_NEUTRAL << 10) | LANG_NEUTRAL; + +// --- +enum : BYTE { + ACL_REVISION = 2, + ACL_REVISION_DS = 4 +} + +// These are not documented on MSDN +enum : BYTE { + ACL_REVISION1 = 1, + ACL_REVISION2, + ACL_REVISION3, + ACL_REVISION4 // = 4 +} + +enum BYTE + MIN_ACL_REVISION = 2, + MAX_ACL_REVISION = 4; + +/+ +// These aren't necessary for D. +enum MINCHAR=0x80; +enum MAXCHAR=0x7f; +enum MINSHORT=0x8000; +enum MAXSHORT=0x7fff; +enum MINLONG=0x80000000; +enum MAXLONG=0x7fffffff; +enum MAXBYTE=0xff; +enum MAXWORD=0xffff; +enum MAXDWORD=0xffffffff; ++/ + +// SYSTEM_INFO.dwProcessorType +enum : DWORD { + PROCESSOR_INTEL_386 = 386, + PROCESSOR_INTEL_486 = 486, + PROCESSOR_INTEL_PENTIUM = 586, + PROCESSOR_MIPS_R4000 = 4000, + PROCESSOR_ALPHA_21064 = 21064, + PROCESSOR_INTEL_IA64 = 2200 +} + +// SYSTEM_INFO.wProcessorArchitecture +enum : WORD { + PROCESSOR_ARCHITECTURE_INTEL, + PROCESSOR_ARCHITECTURE_MIPS, + PROCESSOR_ARCHITECTURE_ALPHA, + PROCESSOR_ARCHITECTURE_PPC, + PROCESSOR_ARCHITECTURE_SHX, + PROCESSOR_ARCHITECTURE_ARM, + PROCESSOR_ARCHITECTURE_IA64, + PROCESSOR_ARCHITECTURE_ALPHA64, + PROCESSOR_ARCHITECTURE_MSIL, + PROCESSOR_ARCHITECTURE_AMD64, + PROCESSOR_ARCHITECTURE_IA32_ON_WIN64, // = 10 + PROCESSOR_ARCHITECTURE_UNKNOWN = 0xFFFF +} + +// IsProcessorFeaturePresent() +enum : DWORD { + PF_FLOATING_POINT_PRECISION_ERRATA, + PF_FLOATING_POINT_EMULATED, + PF_COMPARE_EXCHANGE_DOUBLE, + PF_MMX_INSTRUCTIONS_AVAILABLE, + PF_PPC_MOVEMEM_64BIT_OK, + PF_ALPHA_BYTE_INSTRUCTIONS, + PF_XMMI_INSTRUCTIONS_AVAILABLE, + PF_3DNOW_INSTRUCTIONS_AVAILABLE, + PF_RDTSC_INSTRUCTION_AVAILABLE, + PF_PAE_ENABLED, + PF_XMMI64_INSTRUCTIONS_AVAILABLE +} + +// MinGW: also in ddk/ntifs.h +enum : DWORD { + FILE_ACTION_ADDED = 1, + FILE_ACTION_REMOVED, + FILE_ACTION_MODIFIED, + FILE_ACTION_RENAMED_OLD_NAME, + FILE_ACTION_RENAMED_NEW_NAME, + FILE_ACTION_ADDED_STREAM, + FILE_ACTION_REMOVED_STREAM, + FILE_ACTION_MODIFIED_STREAM, + FILE_ACTION_REMOVED_BY_DELETE, + FILE_ACTION_ID_NOT_TUNNELLED, + FILE_ACTION_TUNNELLED_ID_COLLISION // = 11 +} +// MinGW: end ntifs.h + +enum DWORD + HEAP_NO_SERIALIZE = 0x01, + HEAP_GROWABLE = 0x02, + HEAP_GENERATE_EXCEPTIONS = 0x04, + HEAP_ZERO_MEMORY = 0x08, + HEAP_REALLOC_IN_PLACE_ONLY = 0x10, + HEAP_TAIL_CHECKING_ENABLED = 0x20, + HEAP_FREE_CHECKING_ENABLED = 0x40, + HEAP_DISABLE_COALESCE_ON_FREE = 0x80; + +// These are not documented on MSDN +enum HEAP_CREATE_ALIGN_16 = 0; +enum HEAP_CREATE_ENABLE_TRACING = 0x020000; +enum HEAP_MAXIMUM_TAG = 0x000FFF; +enum HEAP_PSEUDO_TAG_FLAG = 0x008000; +enum HEAP_TAG_SHIFT = 16; +// ??? +//MACRO #define HEAP_MAKE_TAG_FLAGS(b,o) ((DWORD)((b)+(o)<<16))) + +enum ACCESS_MASK + KEY_QUERY_VALUE = 0x000001, + KEY_SET_VALUE = 0x000002, + KEY_CREATE_SUB_KEY = 0x000004, + KEY_ENUMERATE_SUB_KEYS = 0x000008, + KEY_NOTIFY = 0x000010, + KEY_CREATE_LINK = 0x000020, + KEY_WRITE = 0x020006, + KEY_EXECUTE = 0x020019, + KEY_READ = 0x020019, + KEY_ALL_ACCESS = 0x0F003F; + +static if (_WIN32_WINNT >= 0x502) { +enum ACCESS_MASK + KEY_WOW64_64KEY = 0x000100, + KEY_WOW64_32KEY = 0x000200; +} + +enum DWORD + REG_WHOLE_HIVE_VOLATILE = 1, + REG_REFRESH_HIVE = 2, + REG_NO_LAZY_FLUSH = 4; + +enum DWORD + REG_OPTION_RESERVED = 0, + REG_OPTION_NON_VOLATILE = 0, + REG_OPTION_VOLATILE = 1, + REG_OPTION_CREATE_LINK = 2, + REG_OPTION_BACKUP_RESTORE = 4, + REG_OPTION_OPEN_LINK = 8, + REG_LEGAL_OPTION = 15; + +enum SECURITY_INFORMATION + OWNER_SECURITY_INFORMATION = 0x00000001, + GROUP_SECURITY_INFORMATION = 0x00000002, + DACL_SECURITY_INFORMATION = 0x00000004, + SACL_SECURITY_INFORMATION = 0x00000008, + LABEL_SECURITY_INFORMATION = 0x00000010, + UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000, + UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000, + PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000, + PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000; + +enum DWORD MAXIMUM_PROCESSORS = 32; + +// VirtualAlloc(), etc +// ------------------- + +enum : DWORD { + PAGE_NOACCESS = 0x0001, + PAGE_READONLY = 0x0002, + PAGE_READWRITE = 0x0004, + PAGE_WRITECOPY = 0x0008, + PAGE_EXECUTE = 0x0010, + PAGE_EXECUTE_READ = 0x0020, + PAGE_EXECUTE_READWRITE = 0x0040, + PAGE_EXECUTE_WRITECOPY = 0x0080, + PAGE_GUARD = 0x0100, + PAGE_NOCACHE = 0x0200 +} + +enum : DWORD { + MEM_COMMIT = 0x00001000, + MEM_RESERVE = 0x00002000, + MEM_DECOMMIT = 0x00004000, + MEM_RELEASE = 0x00008000, + MEM_FREE = 0x00010000, + MEM_PRIVATE = 0x00020000, + MEM_MAPPED = 0x00040000, + MEM_RESET = 0x00080000, + MEM_TOP_DOWN = 0x00100000, + MEM_WRITE_WATCH = 0x00200000, // MinGW (???): 98/Me + MEM_PHYSICAL = 0x00400000, + MEM_4MB_PAGES = 0x80000000 +} + +// MinGW: also in ddk/ntifs.h +// CreateFileMapping() +enum DWORD + SEC_BASED = 0x00200000, + SEC_NO_CHANGE = 0x00400000, + SEC_FILE = 0x00800000, + SEC_IMAGE = 0x01000000, + SEC_VLM = 0x02000000, + SEC_RESERVE = 0x04000000, + SEC_COMMIT = 0x08000000, + SEC_NOCACHE = 0x10000000, + MEM_IMAGE = SEC_IMAGE; +// MinGW: end ntifs.h + +// ??? +enum ACCESS_MASK + SECTION_QUERY = 0x000001, + SECTION_MAP_WRITE = 0x000002, + SECTION_MAP_READ = 0x000004, + SECTION_MAP_EXECUTE = 0x000008, + SECTION_EXTEND_SIZE = 0x000010, + SECTION_ALL_ACCESS = 0x0F001F; + +// These are not documented on MSDN +enum MESSAGE_RESOURCE_UNICODE = 1; +enum RTL_CRITSECT_TYPE = 0; +enum RTL_RESOURCE_TYPE = 1; + +// COFF file format +// ---------------- + +// IMAGE_FILE_HEADER.Characteristics +enum WORD + IMAGE_FILE_RELOCS_STRIPPED = 0x0001, + IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002, + IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004, + IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008, + IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010, + IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020, + IMAGE_FILE_BYTES_REVERSED_LO = 0x0080, + IMAGE_FILE_32BIT_MACHINE = 0x0100, + IMAGE_FILE_DEBUG_STRIPPED = 0x0200, + IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400, + IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800, + IMAGE_FILE_SYSTEM = 0x1000, + IMAGE_FILE_DLL = 0x2000, + IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000, + IMAGE_FILE_BYTES_REVERSED_HI = 0x8000; + +// IMAGE_FILE_HEADER.Machine +enum : WORD { + IMAGE_FILE_MACHINE_UNKNOWN = 0x0000, + IMAGE_FILE_MACHINE_I386 = 0x014C, + IMAGE_FILE_MACHINE_R3000 = 0x0162, + IMAGE_FILE_MACHINE_R4000 = 0x0166, + IMAGE_FILE_MACHINE_R10000 = 0x0168, + IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x0169, + IMAGE_FILE_MACHINE_ALPHA = 0x0184, + IMAGE_FILE_MACHINE_SH3 = 0x01A2, + IMAGE_FILE_MACHINE_SH3DSP = 0x01A3, + IMAGE_FILE_MACHINE_SH4 = 0x01A6, + IMAGE_FILE_MACHINE_SH5 = 0x01A8, + IMAGE_FILE_MACHINE_ARM = 0x01C0, + IMAGE_FILE_MACHINE_THUMB = 0x01C2, + IMAGE_FILE_MACHINE_AM33 = 0x01D3, + IMAGE_FILE_MACHINE_POWERPC = 0x01F0, + IMAGE_FILE_MACHINE_POWERPCFP = 0x01F1, + IMAGE_FILE_MACHINE_IA64 = 0x0200, + IMAGE_FILE_MACHINE_MIPS16 = 0x0266, + IMAGE_FILE_MACHINE_MIPSFPU = 0x0366, + IMAGE_FILE_MACHINE_MIPSFPU16 = 0x0466, + IMAGE_FILE_MACHINE_EBC = 0x0EBC, + IMAGE_FILE_MACHINE_AMD64 = 0x8664, + IMAGE_FILE_MACHINE_M32R = 0x9041, + IMAGE_FILE_MACHINE_ARM64 = 0xAA64, +} + +// ??? +enum { + IMAGE_DOS_SIGNATURE = 0x5A4D, + IMAGE_OS2_SIGNATURE = 0x454E, + IMAGE_OS2_SIGNATURE_LE = 0x454C, + IMAGE_VXD_SIGNATURE = 0x454C, + IMAGE_NT_SIGNATURE = 0x4550 +} + +// IMAGE_OPTIONAL_HEADER.Magic +enum : WORD { + IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x010B, + IMAGE_ROM_OPTIONAL_HDR_MAGIC = 0x0107, + IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x020B +} + +// IMAGE_OPTIONAL_HEADER.Subsystem +enum : WORD { + IMAGE_SUBSYSTEM_UNKNOWN = 0, + IMAGE_SUBSYSTEM_NATIVE, + IMAGE_SUBSYSTEM_WINDOWS_GUI, + IMAGE_SUBSYSTEM_WINDOWS_CUI, // = 3 + IMAGE_SUBSYSTEM_OS2_CUI = 5, + IMAGE_SUBSYSTEM_POSIX_CUI = 7, + IMAGE_SUBSYSTEM_NATIVE_WINDOWS, + IMAGE_SUBSYSTEM_WINDOWS_CE_GUI, + IMAGE_SUBSYSTEM_EFI_APPLICATION, + IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER, + IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER, + IMAGE_SUBSYSTEM_EFI_ROM, + IMAGE_SUBSYSTEM_XBOX, // = 14 + IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16 +} + +// IMAGE_OPTIONAL_HEADER.DllCharacteristics +enum WORD + IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE = 0x0040, + IMAGE_DLL_CHARACTERISTICS_FORCE_INTEGRITY = 0x0080, + IMAGE_DLL_CHARACTERISTICS_NX_COMPAT = 0x0100, + IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200, + IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400, + IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0800, + IMAGE_DLLCHARACTERISTICS_WDM_DRIVER = 0x2000, + IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000; + +// ??? +enum IMAGE_SEPARATE_DEBUG_SIGNATURE = 0x4944; + +enum size_t + IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16, + IMAGE_SIZEOF_ROM_OPTIONAL_HEADER = 56, + IMAGE_SIZEOF_STD_OPTIONAL_HEADER = 28, + IMAGE_SIZEOF_NT_OPTIONAL_HEADER = 224, + IMAGE_SIZEOF_SHORT_NAME = 8, + IMAGE_SIZEOF_SECTION_HEADER = 40, + IMAGE_SIZEOF_SYMBOL = 18, + IMAGE_SIZEOF_AUX_SYMBOL = 18, + IMAGE_SIZEOF_RELOCATION = 10, + IMAGE_SIZEOF_BASE_RELOCATION = 8, + IMAGE_SIZEOF_LINENUMBER = 6, + IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR = 60, + SIZEOF_RFPO_DATA = 16; + +PIMAGE_SECTION_HEADER IMAGE_FIRST_SECTION(PIMAGE_NT_HEADERS h) { + return cast(PIMAGE_SECTION_HEADER) + (cast(ubyte*) &h.OptionalHeader + h.FileHeader.SizeOfOptionalHeader); +} + +// ImageDirectoryEntryToDataEx() +enum : USHORT { + IMAGE_DIRECTORY_ENTRY_EXPORT = 0, + IMAGE_DIRECTORY_ENTRY_IMPORT, + IMAGE_DIRECTORY_ENTRY_RESOURCE, + IMAGE_DIRECTORY_ENTRY_EXCEPTION, + IMAGE_DIRECTORY_ENTRY_SECURITY, + IMAGE_DIRECTORY_ENTRY_BASERELOC, + IMAGE_DIRECTORY_ENTRY_DEBUG, + IMAGE_DIRECTORY_ENTRY_COPYRIGHT, // = 7 + IMAGE_DIRECTORY_ENTRY_ARCHITECTURE = 7, + IMAGE_DIRECTORY_ENTRY_GLOBALPTR, + IMAGE_DIRECTORY_ENTRY_TLS, + IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, + IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT, + IMAGE_DIRECTORY_ENTRY_IAT, + IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT, + IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR, // = 14 +} + +// IMAGE_SECTION_HEADER.Characteristics +enum DWORD + IMAGE_SCN_TYPE_REG = 0x00000000, + IMAGE_SCN_TYPE_DSECT = 0x00000001, + IMAGE_SCN_TYPE_NOLOAD = 0x00000002, + IMAGE_SCN_TYPE_GROUP = 0x00000004, + IMAGE_SCN_TYPE_NO_PAD = 0x00000008, + IMAGE_SCN_TYPE_COPY = 0x00000010, + IMAGE_SCN_CNT_CODE = 0x00000020, + IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040, + IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080, + IMAGE_SCN_LNK_OTHER = 0x00000100, + IMAGE_SCN_LNK_INFO = 0x00000200, + IMAGE_SCN_TYPE_OVER = 0x00000400, + IMAGE_SCN_LNK_REMOVE = 0x00000800, + IMAGE_SCN_LNK_COMDAT = 0x00001000, + IMAGE_SCN_MEM_FARDATA = 0x00008000, + IMAGE_SCN_GPREL = 0x00008000, + IMAGE_SCN_MEM_PURGEABLE = 0x00020000, + IMAGE_SCN_MEM_16BIT = 0x00020000, + IMAGE_SCN_MEM_LOCKED = 0x00040000, + IMAGE_SCN_MEM_PRELOAD = 0x00080000, + IMAGE_SCN_ALIGN_1BYTES = 0x00100000, + IMAGE_SCN_ALIGN_2BYTES = 0x00200000, + IMAGE_SCN_ALIGN_4BYTES = 0x00300000, + IMAGE_SCN_ALIGN_8BYTES = 0x00400000, + IMAGE_SCN_ALIGN_16BYTES = 0x00500000, + IMAGE_SCN_ALIGN_32BYTES = 0x00600000, + IMAGE_SCN_ALIGN_64BYTES = 0x00700000, + IMAGE_SCN_ALIGN_128BYTES = 0x00800000, + IMAGE_SCN_ALIGN_256BYTES = 0x00900000, + IMAGE_SCN_ALIGN_512BYTES = 0x00A00000, + IMAGE_SCN_ALIGN_1024BYTES = 0x00B00000, + IMAGE_SCN_ALIGN_2048BYTES = 0x00C00000, + IMAGE_SCN_ALIGN_4096BYTES = 0x00D00000, + IMAGE_SCN_ALIGN_8192BYTES = 0x00E00000, + IMAGE_SCN_LNK_NRELOC_OVFL = 0x01000000, + IMAGE_SCN_MEM_DISCARDABLE = 0x02000000, + IMAGE_SCN_MEM_NOT_CACHED = 0x04000000, + IMAGE_SCN_MEM_NOT_PAGED = 0x08000000, + IMAGE_SCN_MEM_SHARED = 0x10000000, + IMAGE_SCN_MEM_EXECUTE = 0x20000000, + IMAGE_SCN_MEM_READ = 0x40000000, + IMAGE_SCN_MEM_WRITE = 0x80000000; + +/* The following constants are mostlydocumented at + * http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/pecoff.doc + * but don't seem to be defined in the HTML docs. + */ +enum : SHORT { + IMAGE_SYM_UNDEFINED = 0, + IMAGE_SYM_ABSOLUTE = -1, + IMAGE_SYM_DEBUG = -2 +} + +enum : ubyte { + IMAGE_SYM_TYPE_NULL, + IMAGE_SYM_TYPE_VOID, + IMAGE_SYM_TYPE_CHAR, + IMAGE_SYM_TYPE_SHORT, + IMAGE_SYM_TYPE_INT, + IMAGE_SYM_TYPE_LONG, + IMAGE_SYM_TYPE_FLOAT, + IMAGE_SYM_TYPE_DOUBLE, + IMAGE_SYM_TYPE_STRUCT, + IMAGE_SYM_TYPE_UNION, + IMAGE_SYM_TYPE_ENUM, + IMAGE_SYM_TYPE_MOE, + IMAGE_SYM_TYPE_BYTE, + IMAGE_SYM_TYPE_WORD, + IMAGE_SYM_TYPE_UINT, + IMAGE_SYM_TYPE_DWORD // = 15 +} +enum IMAGE_SYM_TYPE_PCODE = 32768; // ??? + +enum : ubyte { + IMAGE_SYM_DTYPE_NULL, + IMAGE_SYM_DTYPE_POINTER, + IMAGE_SYM_DTYPE_FUNCTION, + IMAGE_SYM_DTYPE_ARRAY +} + +enum : BYTE { + IMAGE_SYM_CLASS_END_OF_FUNCTION = 0xFF, + IMAGE_SYM_CLASS_NULL = 0, + IMAGE_SYM_CLASS_AUTOMATIC, + IMAGE_SYM_CLASS_EXTERNAL, + IMAGE_SYM_CLASS_STATIC, + IMAGE_SYM_CLASS_REGISTER, + IMAGE_SYM_CLASS_EXTERNAL_DEF, + IMAGE_SYM_CLASS_LABEL, + IMAGE_SYM_CLASS_UNDEFINED_LABEL, + IMAGE_SYM_CLASS_MEMBER_OF_STRUCT, + IMAGE_SYM_CLASS_ARGUMENT, + IMAGE_SYM_CLASS_STRUCT_TAG, + IMAGE_SYM_CLASS_MEMBER_OF_UNION, + IMAGE_SYM_CLASS_UNION_TAG, + IMAGE_SYM_CLASS_TYPE_DEFINITION, + IMAGE_SYM_CLASS_UNDEFINED_STATIC, + IMAGE_SYM_CLASS_ENUM_TAG, + IMAGE_SYM_CLASS_MEMBER_OF_ENUM, + IMAGE_SYM_CLASS_REGISTER_PARAM, + IMAGE_SYM_CLASS_BIT_FIELD, // = 18 + IMAGE_SYM_CLASS_FAR_EXTERNAL = 68, + IMAGE_SYM_CLASS_BLOCK = 100, + IMAGE_SYM_CLASS_FUNCTION, + IMAGE_SYM_CLASS_END_OF_STRUCT, + IMAGE_SYM_CLASS_FILE, + IMAGE_SYM_CLASS_SECTION, + IMAGE_SYM_CLASS_WEAK_EXTERNAL,// = 105 + IMAGE_SYM_CLASS_CLR_TOKEN = 107 +} + +enum : BYTE { + IMAGE_COMDAT_SELECT_NODUPLICATES = 1, + IMAGE_COMDAT_SELECT_ANY, + IMAGE_COMDAT_SELECT_SAME_SIZE, + IMAGE_COMDAT_SELECT_EXACT_MATCH, + IMAGE_COMDAT_SELECT_ASSOCIATIVE, + IMAGE_COMDAT_SELECT_LARGEST, + IMAGE_COMDAT_SELECT_NEWEST // = 7 +} + +enum : DWORD { + IMAGE_WEAK_EXTERN_SEARCH_NOLIBRARY = 1, + IMAGE_WEAK_EXTERN_SEARCH_LIBRARY, + IMAGE_WEAK_EXTERN_SEARCH_ALIAS +} + +enum : WORD { + IMAGE_REL_I386_ABSOLUTE = 0x0000, + IMAGE_REL_I386_DIR16 = 0x0001, + IMAGE_REL_I386_REL16 = 0x0002, + IMAGE_REL_I386_DIR32 = 0x0006, + IMAGE_REL_I386_DIR32NB = 0x0007, + IMAGE_REL_I386_SEG12 = 0x0009, + IMAGE_REL_I386_SECTION = 0x000A, + IMAGE_REL_I386_SECREL = 0x000B, + IMAGE_REL_I386_TOKEN = 0x000C, + IMAGE_REL_I386_SECREL7 = 0x000D, + IMAGE_REL_I386_REL32 = 0x0014 +} + +enum : WORD { + IMAGE_REL_AMD64_ABSOLUTE = 0x0000, + IMAGE_REL_AMD64_ADDR64 = 0x0001, + IMAGE_REL_AMD64_ADDR32 = 0x0002, + IMAGE_REL_AMD64_ADDR32NB = 0x0003, + IMAGE_REL_AMD64_REL32 = 0x0004, + IMAGE_REL_AMD64_REL32_1 = 0x0005, + IMAGE_REL_AMD64_REL32_2 = 0x0006, + IMAGE_REL_AMD64_REL32_3 = 0x0007, + IMAGE_REL_AMD64_REL32_4 = 0x0008, + IMAGE_REL_AMD64_REL32_5 = 0x0009, + IMAGE_REL_AMD64_SECTION = 0x000A, + IMAGE_REL_AMD64_SECREL = 0x000B, + IMAGE_REL_AMD64_SECREL7 = 0x000C, + IMAGE_REL_AMD64_TOKEN = 0x000D, + IMAGE_REL_AMD64_SREL32 = 0x000E, + IMAGE_REL_AMD64_PAIR = 0x000F, + IMAGE_REL_AMD64_SSPAN32 = 0x0010 +} + +enum : WORD { + IMAGE_REL_IA64_ABSOLUTE = 0x0000, + IMAGE_REL_IA64_IMM14 = 0x0001, + IMAGE_REL_IA64_IMM22 = 0x0002, + IMAGE_REL_IA64_IMM64 = 0x0003, + IMAGE_REL_IA64_DIR32 = 0x0004, + IMAGE_REL_IA64_DIR64 = 0x0005, + IMAGE_REL_IA64_PCREL21B = 0x0006, + IMAGE_REL_IA64_PCREL21M = 0x0007, + IMAGE_REL_IA64_PCREL21F = 0x0008, + IMAGE_REL_IA64_GPREL22 = 0x0009, + IMAGE_REL_IA64_LTOFF22 = 0x000A, + IMAGE_REL_IA64_SECTION = 0x000B, + IMAGE_REL_IA64_SECREL22 = 0x000C, + IMAGE_REL_IA64_SECREL64I = 0x000D, + IMAGE_REL_IA64_SECREL32 = 0x000E, + IMAGE_REL_IA64_DIR32NB = 0x0010, + IMAGE_REL_IA64_SREL14 = 0x0011, + IMAGE_REL_IA64_SREL22 = 0x0012, + IMAGE_REL_IA64_SREL32 = 0x0013, + IMAGE_REL_IA64_UREL32 = 0x0014, + IMAGE_REL_IA64_PCREL60X = 0x0015, + IMAGE_REL_IA64_PCREL60B = 0x0016, + IMAGE_REL_IA64_PCREL60F = 0x0017, + IMAGE_REL_IA64_PCREL60I = 0x0018, + IMAGE_REL_IA64_PCREL60M = 0x0019, + IMAGE_REL_IA64_IMMGPREL64 = 0x001A, + IMAGE_REL_IA64_TOKEN = 0x001B, + IMAGE_REL_IA64_GPREL32 = 0x001C, + IMAGE_REL_IA64_ADDEND = 0x001F +} + +enum : WORD { + IMAGE_REL_SH3_ABSOLUTE = 0x0000, + IMAGE_REL_SH3_DIRECT16 = 0x0001, + IMAGE_REL_SH3_DIRECT32 = 0x0002, + IMAGE_REL_SH3_DIRECT8 = 0x0003, + IMAGE_REL_SH3_DIRECT8_WORD = 0x0004, + IMAGE_REL_SH3_DIRECT8_LONG = 0x0005, + IMAGE_REL_SH3_DIRECT4 = 0x0006, + IMAGE_REL_SH3_DIRECT4_WORD = 0x0007, + IMAGE_REL_SH3_DIRECT4_LONG = 0x0008, + IMAGE_REL_SH3_PCREL8_WORD = 0x0009, + IMAGE_REL_SH3_PCREL8_LONG = 0x000A, + IMAGE_REL_SH3_PCREL12_WORD = 0x000B, + IMAGE_REL_SH3_STARTOF_SECTION = 0x000C, + IMAGE_REL_SH3_SIZEOF_SECTION = 0x000D, + IMAGE_REL_SH3_SECTION = 0x000E, + IMAGE_REL_SH3_SECREL = 0x000F, + IMAGE_REL_SH3_DIRECT32_NB = 0x0010, + IMAGE_REL_SH3_GPREL4_LONG = 0x0011, + IMAGE_REL_SH3_TOKEN = 0x0012, + IMAGE_REL_SHM_PCRELPT = 0x0013, + IMAGE_REL_SHM_REFLO = 0x0014, + IMAGE_REL_SHM_REFHALF = 0x0015, + IMAGE_REL_SHM_RELLO = 0x0016, + IMAGE_REL_SHM_RELHALF = 0x0017, + IMAGE_REL_SHM_PAIR = 0x0018, + IMAGE_REL_SHM_NOMODE = 0x8000 +} + +enum : WORD { + IMAGE_REL_M32R_ABSOLUTE = 0x0000, + IMAGE_REL_M32R_ADDR32 = 0x0001, + IMAGE_REL_M32R_ADDR32NB = 0x0002, + IMAGE_REL_M32R_ADDR24 = 0x0003, + IMAGE_REL_M32R_GPREL16 = 0x0004, + IMAGE_REL_M32R_PCREL24 = 0x0005, + IMAGE_REL_M32R_PCREL16 = 0x0006, + IMAGE_REL_M32R_PCREL8 = 0x0007, + IMAGE_REL_M32R_REFHALF = 0x0008, + IMAGE_REL_M32R_REFHI = 0x0009, + IMAGE_REL_M32R_REFLO = 0x000A, + IMAGE_REL_M32R_PAIR = 0x000B, + IMAGE_REL_M32R_SECTION = 0x000C, + IMAGE_REL_M32R_SECREL = 0x000D, + IMAGE_REL_M32R_TOKEN = 0x000E +} + +enum : WORD { + IMAGE_REL_MIPS_ABSOLUTE = 0x0000, + IMAGE_REL_MIPS_REFHALF = 0x0001, + IMAGE_REL_MIPS_REFWORD = 0x0002, + IMAGE_REL_MIPS_JMPADDR = 0x0003, + IMAGE_REL_MIPS_REFHI = 0x0004, + IMAGE_REL_MIPS_REFLO = 0x0005, + IMAGE_REL_MIPS_GPREL = 0x0006, + IMAGE_REL_MIPS_LITERAL = 0x0007, + IMAGE_REL_MIPS_SECTION = 0x000A, + IMAGE_REL_MIPS_SECREL = 0x000B, + IMAGE_REL_MIPS_SECRELLO = 0x000C, + IMAGE_REL_MIPS_SECRELHI = 0x000D, + IMAGE_REL_MIPS_JMPADDR16 = 0x0010, + IMAGE_REL_MIPS_REFWORDNB = 0x0022, + IMAGE_REL_MIPS_PAIR = 0x0025 +} + + +enum : WORD { + IMAGE_REL_ALPHA_ABSOLUTE, + IMAGE_REL_ALPHA_REFLONG, + IMAGE_REL_ALPHA_REFQUAD, + IMAGE_REL_ALPHA_GPREL32, + IMAGE_REL_ALPHA_LITERAL, + IMAGE_REL_ALPHA_LITUSE, + IMAGE_REL_ALPHA_GPDISP, + IMAGE_REL_ALPHA_BRADDR, + IMAGE_REL_ALPHA_HINT, + IMAGE_REL_ALPHA_INLINE_REFLONG, + IMAGE_REL_ALPHA_REFHI, + IMAGE_REL_ALPHA_REFLO, + IMAGE_REL_ALPHA_PAIR, + IMAGE_REL_ALPHA_MATCH, + IMAGE_REL_ALPHA_SECTION, + IMAGE_REL_ALPHA_SECREL, + IMAGE_REL_ALPHA_REFLONGNB, + IMAGE_REL_ALPHA_SECRELLO, + IMAGE_REL_ALPHA_SECRELHI // = 18 +} + +enum : WORD { + IMAGE_REL_PPC_ABSOLUTE, + IMAGE_REL_PPC_ADDR64, + IMAGE_REL_PPC_ADDR32, + IMAGE_REL_PPC_ADDR24, + IMAGE_REL_PPC_ADDR16, + IMAGE_REL_PPC_ADDR14, + IMAGE_REL_PPC_REL24, + IMAGE_REL_PPC_REL14, + IMAGE_REL_PPC_TOCREL16, + IMAGE_REL_PPC_TOCREL14, + IMAGE_REL_PPC_ADDR32NB, + IMAGE_REL_PPC_SECREL, + IMAGE_REL_PPC_SECTION, + IMAGE_REL_PPC_IFGLUE, + IMAGE_REL_PPC_IMGLUE, + IMAGE_REL_PPC_SECREL16, + IMAGE_REL_PPC_REFHI, + IMAGE_REL_PPC_REFLO, + IMAGE_REL_PPC_PAIR // = 18 +} + +// ??? +enum IMAGE_REL_PPC_TYPEMASK = 0x00FF; +enum IMAGE_REL_PPC_NEG = 0x0100; +enum IMAGE_REL_PPC_BRTAKEN = 0x0200; +enum IMAGE_REL_PPC_BRNTAKEN = 0x0400; +enum IMAGE_REL_PPC_TOCDEFN = 0x0800; + +enum { + IMAGE_REL_BASED_ABSOLUTE, + IMAGE_REL_BASED_HIGH, + IMAGE_REL_BASED_LOW, + IMAGE_REL_BASED_HIGHLOW, + IMAGE_REL_BASED_HIGHADJ, + IMAGE_REL_BASED_MIPS_JMPADDR +} +// End of constants documented in pecoff.doc + +enum size_t IMAGE_ARCHIVE_START_SIZE = 8; + +const TCHAR[] + IMAGE_ARCHIVE_START = "!\n", + IMAGE_ARCHIVE_END = "`\n", + IMAGE_ARCHIVE_PAD = "\n", + IMAGE_ARCHIVE_LINKER_MEMBER = "/ ", + IMAGE_ARCHIVE_LONGNAMES_MEMBER = "// "; + +enum IMAGE_ORDINAL_FLAG32 = 0x80000000; + +ulong IMAGE_ORDINAL64()(ulong Ordinal) { return Ordinal & 0xFFFF; } +uint IMAGE_ORDINAL32()(uint Ordinal) { return Ordinal & 0xFFFF; } + +bool IMAGE_SNAP_BY_ORDINAL32(uint Ordinal) { + return (Ordinal & IMAGE_ORDINAL_FLAG32) != 0; +} + +enum ulong IMAGE_ORDINAL_FLAG64 = 0x8000000000000000; + +bool IMAGE_SNAP_BY_ORDINAL64(ulong Ordinal) { + return (Ordinal & IMAGE_ORDINAL_FLAG64) != 0; +} + +// ??? +enum IMAGE_RESOURCE_NAME_IS_STRING = 0x80000000; +enum IMAGE_RESOURCE_DATA_IS_DIRECTORY = 0x80000000; + +enum : DWORD { + IMAGE_DEBUG_TYPE_UNKNOWN, + IMAGE_DEBUG_TYPE_COFF, + IMAGE_DEBUG_TYPE_CODEVIEW, + IMAGE_DEBUG_TYPE_FPO, + IMAGE_DEBUG_TYPE_MISC, + IMAGE_DEBUG_TYPE_EXCEPTION, + IMAGE_DEBUG_TYPE_FIXUP, + IMAGE_DEBUG_TYPE_OMAP_TO_SRC, + IMAGE_DEBUG_TYPE_OMAP_FROM_SRC, + IMAGE_DEBUG_TYPE_BORLAND // = 9 +} + +enum : ubyte { + FRAME_FPO, + FRAME_TRAP, + FRAME_TSS, + FRAME_NONFPO +} + +// ??? +enum IMAGE_DEBUG_MISC_EXENAME = 1; + +// ??? +enum N_BTMASK = 0x000F; +enum N_TMASK = 0x0030; +enum N_TMASK1 = 0x00C0; +enum N_TMASK2 = 0x00F0; +enum N_BTSHFT = 4; +enum N_TSHIFT = 2; + +enum int + IS_TEXT_UNICODE_ASCII16 = 0x0001, + IS_TEXT_UNICODE_STATISTICS = 0x0002, + IS_TEXT_UNICODE_CONTROLS = 0x0004, + IS_TEXT_UNICODE_SIGNATURE = 0x0008, + IS_TEXT_UNICODE_REVERSE_ASCII16 = 0x0010, + IS_TEXT_UNICODE_REVERSE_STATISTICS = 0x0020, + IS_TEXT_UNICODE_REVERSE_CONTROLS = 0x0040, + IS_TEXT_UNICODE_REVERSE_SIGNATURE = 0x0080, + IS_TEXT_UNICODE_ILLEGAL_CHARS = 0x0100, + IS_TEXT_UNICODE_ODD_LENGTH = 0x0200, + IS_TEXT_UNICODE_NULL_BYTES = 0x1000, + IS_TEXT_UNICODE_UNICODE_MASK = 0x000F, + IS_TEXT_UNICODE_REVERSE_MASK = 0x00F0, + IS_TEXT_UNICODE_NOT_UNICODE_MASK = 0x0F00, + IS_TEXT_UNICODE_NOT_ASCII_MASK = 0xF000; + +enum DWORD + SERVICE_KERNEL_DRIVER = 0x0001, + SERVICE_FILE_SYSTEM_DRIVER = 0x0002, + SERVICE_ADAPTER = 0x0004, + SERVICE_RECOGNIZER_DRIVER = 0x0008, + SERVICE_WIN32_OWN_PROCESS = 0x0010, + SERVICE_WIN32_SHARE_PROCESS = 0x0020, + SERVICE_INTERACTIVE_PROCESS = 0x0100, + SERVICE_DRIVER = 0x000B, + SERVICE_WIN32 = 0x0030, + SERVICE_TYPE_ALL = 0x013F; + +enum : DWORD { + SERVICE_BOOT_START = 0, + SERVICE_SYSTEM_START = 1, + SERVICE_AUTO_START = 2, + SERVICE_DEMAND_START = 3, + SERVICE_DISABLED = 4 +} + +enum : DWORD { + SERVICE_ERROR_IGNORE = 0, + SERVICE_ERROR_NORMAL = 1, + SERVICE_ERROR_SEVERE = 2, + SERVICE_ERROR_CRITICAL = 3 +} + + +enum uint + SE_OWNER_DEFAULTED = 0x0001, + SE_GROUP_DEFAULTED = 0x0002, + SE_DACL_PRESENT = 0x0004, + SE_DACL_DEFAULTED = 0x0008, + SE_SACL_PRESENT = 0x0010, + SE_SACL_DEFAULTED = 0x0020, + SE_DACL_AUTO_INHERIT_REQ = 0x0100, + SE_SACL_AUTO_INHERIT_REQ = 0x0200, + SE_DACL_AUTO_INHERITED = 0x0400, + SE_SACL_AUTO_INHERITED = 0x0800, + SE_DACL_PROTECTED = 0x1000, + SE_SACL_PROTECTED = 0x2000, + SE_SELF_RELATIVE = 0x8000; + +enum SECURITY_IMPERSONATION_LEVEL { + SecurityAnonymous, + SecurityIdentification, + SecurityImpersonation, + SecurityDelegation +} +alias SECURITY_IMPERSONATION_LEVEL* PSECURITY_IMPERSONATION_LEVEL; + +alias BOOLEAN SECURITY_CONTEXT_TRACKING_MODE; +alias BOOLEAN* PSECURITY_CONTEXT_TRACKING_MODE; + +enum size_t SECURITY_DESCRIPTOR_MIN_LENGTH = 20; + +enum DWORD + SECURITY_DESCRIPTOR_REVISION = 1, + SECURITY_DESCRIPTOR_REVISION1 = 1; + +enum DWORD + SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001, + SE_PRIVILEGE_ENABLED = 0x00000002, + SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000; + +enum DWORD PRIVILEGE_SET_ALL_NECESSARY = 1; + +enum SECURITY_IMPERSONATION_LEVEL + SECURITY_MAX_IMPERSONATION_LEVEL = SECURITY_IMPERSONATION_LEVEL.SecurityDelegation, + DEFAULT_IMPERSONATION_LEVEL = SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation; + +enum BOOLEAN + SECURITY_DYNAMIC_TRACKING = true, + SECURITY_STATIC_TRACKING = false; + +// also in ddk/ntifs.h +enum DWORD + TOKEN_ASSIGN_PRIMARY = 0x0001, + TOKEN_DUPLICATE = 0x0002, + TOKEN_IMPERSONATE = 0x0004, + TOKEN_QUERY = 0x0008, + TOKEN_QUERY_SOURCE = 0x0010, + TOKEN_ADJUST_PRIVILEGES = 0x0020, + TOKEN_ADJUST_GROUPS = 0x0040, + TOKEN_ADJUST_DEFAULT = 0x0080, + + TOKEN_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED + | TOKEN_ASSIGN_PRIMARY + | TOKEN_DUPLICATE + | TOKEN_IMPERSONATE + | TOKEN_QUERY + | TOKEN_QUERY_SOURCE + | TOKEN_ADJUST_PRIVILEGES + | TOKEN_ADJUST_GROUPS + | TOKEN_ADJUST_DEFAULT, + TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY, + TOKEN_WRITE = STANDARD_RIGHTS_WRITE + | TOKEN_ADJUST_PRIVILEGES + | TOKEN_ADJUST_GROUPS + | TOKEN_ADJUST_DEFAULT, + TOKEN_EXECUTE = STANDARD_RIGHTS_EXECUTE; + +enum size_t TOKEN_SOURCE_LENGTH = 8; +// end ddk/ntifs.h + +enum : DWORD { + DLL_PROCESS_DETACH, + DLL_PROCESS_ATTACH, + DLL_THREAD_ATTACH, + DLL_THREAD_DETACH +} + +enum : DWORD { + DBG_CONTINUE = 0x00010002, + DBG_TERMINATE_THREAD = 0x40010003, + DBG_TERMINATE_PROCESS = 0x40010004, + DBG_CONTROL_C = 0x40010005, + DBG_CONTROL_BREAK = 0x40010008, + DBG_EXCEPTION_NOT_HANDLED = 0x80010001 +} + +enum : DWORD { + TAPE_ABSOLUTE_POSITION, + TAPE_LOGICAL_POSITION, + TAPE_PSEUDO_LOGICAL_POSITION +} + +enum : DWORD { + TAPE_REWIND, + TAPE_ABSOLUTE_BLOCK, + TAPE_LOGICAL_BLOCK, + TAPE_PSEUDO_LOGICAL_BLOCK, + TAPE_SPACE_END_OF_DATA, + TAPE_SPACE_RELATIVE_BLOCKS, + TAPE_SPACE_FILEMARKS, + TAPE_SPACE_SEQUENTIAL_FMKS, + TAPE_SPACE_SETMARKS, + TAPE_SPACE_SEQUENTIAL_SMKS +} + +enum DWORD + TAPE_DRIVE_FIXED = 0x00000001, + TAPE_DRIVE_SELECT = 0x00000002, + TAPE_DRIVE_INITIATOR = 0x00000004, + TAPE_DRIVE_ERASE_SHORT = 0x00000010, + TAPE_DRIVE_ERASE_LONG = 0x00000020, + TAPE_DRIVE_ERASE_BOP_ONLY = 0x00000040, + TAPE_DRIVE_ERASE_IMMEDIATE = 0x00000080, + TAPE_DRIVE_TAPE_CAPACITY = 0x00000100, + TAPE_DRIVE_TAPE_REMAINING = 0x00000200, + TAPE_DRIVE_FIXED_BLOCK = 0x00000400, + TAPE_DRIVE_VARIABLE_BLOCK = 0x00000800, + TAPE_DRIVE_WRITE_PROTECT = 0x00001000, + TAPE_DRIVE_EOT_WZ_SIZE = 0x00002000, + TAPE_DRIVE_ECC = 0x00010000, + TAPE_DRIVE_COMPRESSION = 0x00020000, + TAPE_DRIVE_PADDING = 0x00040000, + TAPE_DRIVE_REPORT_SMKS = 0x00080000, + TAPE_DRIVE_GET_ABSOLUTE_BLK = 0x00100000, + TAPE_DRIVE_GET_LOGICAL_BLK = 0x00200000, + TAPE_DRIVE_SET_EOT_WZ_SIZE = 0x00400000, + TAPE_DRIVE_EJECT_MEDIA = 0x01000000, + TAPE_DRIVE_CLEAN_REQUESTS = 0x02000000, + TAPE_DRIVE_SET_CMP_BOP_ONLY = 0x04000000, + TAPE_DRIVE_RESERVED_BIT = 0x80000000; + +enum DWORD + TAPE_DRIVE_LOAD_UNLOAD = 0x80000001, + TAPE_DRIVE_TENSION = 0x80000002, + TAPE_DRIVE_LOCK_UNLOCK = 0x80000004, + TAPE_DRIVE_REWIND_IMMEDIATE = 0x80000008, + TAPE_DRIVE_SET_BLOCK_SIZE = 0x80000010, + TAPE_DRIVE_LOAD_UNLD_IMMED = 0x80000020, + TAPE_DRIVE_TENSION_IMMED = 0x80000040, + TAPE_DRIVE_LOCK_UNLK_IMMED = 0x80000080, + TAPE_DRIVE_SET_ECC = 0x80000100, + TAPE_DRIVE_SET_COMPRESSION = 0x80000200, + TAPE_DRIVE_SET_PADDING = 0x80000400, + TAPE_DRIVE_SET_REPORT_SMKS = 0x80000800, + TAPE_DRIVE_ABSOLUTE_BLK = 0x80001000, + TAPE_DRIVE_ABS_BLK_IMMED = 0x80002000, + TAPE_DRIVE_LOGICAL_BLK = 0x80004000, + TAPE_DRIVE_LOG_BLK_IMMED = 0x80008000, + TAPE_DRIVE_END_OF_DATA = 0x80010000, + TAPE_DRIVE_RELATIVE_BLKS = 0x80020000, + TAPE_DRIVE_FILEMARKS = 0x80040000, + TAPE_DRIVE_SEQUENTIAL_FMKS = 0x80080000, + TAPE_DRIVE_SETMARKS = 0x80100000, + TAPE_DRIVE_SEQUENTIAL_SMKS = 0x80200000, + TAPE_DRIVE_REVERSE_POSITION = 0x80400000, + TAPE_DRIVE_SPACE_IMMEDIATE = 0x80800000, + TAPE_DRIVE_WRITE_SETMARKS = 0x81000000, + TAPE_DRIVE_WRITE_FILEMARKS = 0x82000000, + TAPE_DRIVE_WRITE_SHORT_FMKS = 0x84000000, + TAPE_DRIVE_WRITE_LONG_FMKS = 0x88000000, + TAPE_DRIVE_WRITE_MARK_IMMED = 0x90000000, + TAPE_DRIVE_FORMAT = 0xA0000000, + TAPE_DRIVE_FORMAT_IMMEDIATE = 0xC0000000, + TAPE_DRIVE_HIGH_FEATURES = 0x80000000; + +enum : DWORD { + TAPE_FIXED_PARTITIONS = 0, + TAPE_SELECT_PARTITIONS = 1, + TAPE_INITIATOR_PARTITIONS = 2 +} + +enum : DWORD { + TAPE_SETMARKS, + TAPE_FILEMARKS, + TAPE_SHORT_FILEMARKS, + TAPE_LONG_FILEMARKS +} + +enum : DWORD { + TAPE_ERASE_SHORT, + TAPE_ERASE_LONG +} + +enum : DWORD { + TAPE_LOAD, + TAPE_UNLOAD, + TAPE_TENSION, + TAPE_LOCK, + TAPE_UNLOCK, + TAPE_FORMAT +} + +enum : ULONG32 { + VER_PLATFORM_WIN32s, + VER_PLATFORM_WIN32_WINDOWS, + VER_PLATFORM_WIN32_NT +} + +enum : UCHAR { + VER_NT_WORKSTATION = 1, + VER_NT_DOMAIN_CONTROLLER, + VER_NT_SERVER +} + +enum USHORT + VER_SUITE_SMALLBUSINESS = 0x0001, + VER_SUITE_ENTERPRISE = 0x0002, + VER_SUITE_BACKOFFICE = 0x0004, + VER_SUITE_TERMINAL = 0x0010, + VER_SUITE_SMALLBUSINESS_RESTRICTED = 0x0020, + VER_SUITE_EMBEDDEDNT = 0x0040, + VER_SUITE_DATACENTER = 0x0080, + VER_SUITE_SINGLEUSERTS = 0x0100, + VER_SUITE_PERSONAL = 0x0200, + VER_SUITE_BLADE = 0x0400, + VER_SUITE_STORAGE_SERVER = 0x2000, + VER_SUITE_COMPUTE_SERVER = 0x4000; + +enum ULONG + WT_EXECUTEDEFAULT = 0x00000000, + WT_EXECUTEINIOTHREAD = 0x00000001, + WT_EXECUTEINWAITTHREAD = 0x00000004, + WT_EXECUTEONLYONCE = 0x00000008, + WT_EXECUTELONGFUNCTION = 0x00000010, + WT_EXECUTEINTIMERTHREAD = 0x00000020, + WT_EXECUTEINPERSISTENTTHREAD = 0x00000080, + WT_TRANSFER_IMPERSONATION = 0x00000100; + +static if (_WIN32_WINNT >= 0x500) { +enum DWORD + VER_MINORVERSION = 0x01, + VER_MAJORVERSION = 0x02, + VER_BUILDNUMBER = 0x04, + VER_PLATFORMID = 0x08, + VER_SERVICEPACKMINOR = 0x10, + VER_SERVICEPACKMAJOR = 0x20, + VER_SUITENAME = 0x40, + VER_PRODUCT_TYPE = 0x80; + + enum : DWORD { + VER_EQUAL = 1, + VER_GREATER, + VER_GREATER_EQUAL, + VER_LESS, + VER_LESS_EQUAL, + VER_AND, + VER_OR // = 7 + } +} + +static if (_WIN32_WINNT >= 0x501) { + enum : ULONG { + ACTIVATION_CONTEXT_SECTION_ASSEMBLY_INFORMATION = 1, + ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION, + ACTIVATION_CONTEXT_SECTION_WINDOW_CLASS_REDIRECTION, + ACTIVATION_CONTEXT_SECTION_COM_SERVER_REDIRECTION, + ACTIVATION_CONTEXT_SECTION_COM_INTERFACE_REDIRECTION, + ACTIVATION_CONTEXT_SECTION_COM_TYPE_LIBRARY_REDIRECTION, + ACTIVATION_CONTEXT_SECTION_COM_PROGID_REDIRECTION, // = 7 + ACTIVATION_CONTEXT_SECTION_CLR_SURROGATES = 9 + } +} + +// Macros +BYTE BTYPE()(BYTE x) { return cast(BYTE) (x & N_BTMASK); } +bool ISPTR()(uint x) { return (x & N_TMASK) == (IMAGE_SYM_DTYPE_POINTER << N_BTSHFT); } +bool ISFCN()(uint x) { return (x & N_TMASK) == (IMAGE_SYM_DTYPE_FUNCTION << N_BTSHFT); } +bool ISARY()(uint x) { return (x & N_TMASK) == (IMAGE_SYM_DTYPE_ARRAY << N_BTSHFT); } +bool ISTAG(uint x) { + return x == IMAGE_SYM_CLASS_STRUCT_TAG + || x == IMAGE_SYM_CLASS_UNION_TAG + || x == IMAGE_SYM_CLASS_ENUM_TAG; +} +uint INCREF(uint x) { + return ((x & ~N_BTMASK) << N_TSHIFT) | (IMAGE_SYM_DTYPE_POINTER << N_BTSHFT) + | (x & N_BTMASK); +} +uint DECREF()(uint x) { return ((x >>> N_TSHIFT) & ~N_BTMASK) | (x & N_BTMASK); } + +enum DWORD TLS_MINIMUM_AVAILABLE = 64; + +enum ULONG + IO_REPARSE_TAG_RESERVED_ZERO = 0, + IO_REPARSE_TAG_RESERVED_ONE = 1, + IO_REPARSE_TAG_RESERVED_RANGE = IO_REPARSE_TAG_RESERVED_ONE, + IO_REPARSE_TAG_SYMBOLIC_LINK = IO_REPARSE_TAG_RESERVED_ZERO, + IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003, + IO_REPARSE_TAG_SYMLINK = 0xA000000C, + IO_REPARSE_TAG_VALID_VALUES = 0xE000FFFF; + +/* Although these are semantically boolean, they are documented and + * implemented to return ULONG; this behaviour is preserved for compatibility + */ +ULONG IsReparseTagMicrosoft()(ULONG x) { return x & 0x80000000; } +ULONG IsReparseTagHighLatency()(ULONG x) { return x & 0x40000000; } +ULONG IsReparseTagNameSurrogate()(ULONG x) { return x & 0x20000000; } + +bool IsReparseTagValid(ULONG x) { + return !(x & ~IO_REPARSE_TAG_VALID_VALUES) && (x > IO_REPARSE_TAG_RESERVED_RANGE); +} + +// Doesn't seem to make sense, but anyway.... +ULONG WT_SET_MAX_THREADPOOL_THREADS(ref ULONG Flags, ushort Limit) { + return Flags |= Limit << 16; +} + +import urt.internal.sys.windows.basetyps; +/* also in urt.internal.sys.windows.basetyps +struct GUID { + uint Data1; + ushort Data2; + ushort Data3; + ubyte Data4[8]; +} +alias GUID* REFGUID, LPGUID; +*/ + +struct GENERIC_MAPPING { + ACCESS_MASK GenericRead; + ACCESS_MASK GenericWrite; + ACCESS_MASK GenericExecute; + ACCESS_MASK GenericAll; +} +alias GENERIC_MAPPING* PGENERIC_MAPPING; + +struct ACE_HEADER { + BYTE AceType; + BYTE AceFlags; + WORD AceSize; +} +alias ACE_HEADER* PACE_HEADER; + +struct ACCESS_ALLOWED_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD SidStart; +} +alias ACCESS_ALLOWED_ACE* PACCESS_ALLOWED_ACE; + +struct ACCESS_DENIED_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD SidStart; +} +alias ACCESS_DENIED_ACE* PACCESS_DENIED_ACE; + +struct SYSTEM_AUDIT_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD SidStart; +} +alias SYSTEM_AUDIT_ACE *PSYSTEM_AUDIT_ACE; + +struct SYSTEM_ALARM_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD SidStart; +} +alias SYSTEM_ALARM_ACE* PSYSTEM_ALARM_ACE; + +struct ACCESS_ALLOWED_OBJECT_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD Flags; + GUID ObjectType; + GUID InheritedObjectType; + DWORD SidStart; +} +alias ACCESS_ALLOWED_OBJECT_ACE* PACCESS_ALLOWED_OBJECT_ACE; + +struct ACCESS_DENIED_OBJECT_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD Flags; + GUID ObjectType; + GUID InheritedObjectType; + DWORD SidStart; +} +alias ACCESS_DENIED_OBJECT_ACE* PACCESS_DENIED_OBJECT_ACE; + +struct SYSTEM_AUDIT_OBJECT_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD Flags; + GUID ObjectType; + GUID InheritedObjectType; + DWORD SidStart; +} +alias SYSTEM_AUDIT_OBJECT_ACE* PSYSTEM_AUDIT_OBJECT_ACE; + +struct SYSTEM_ALARM_OBJECT_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD Flags; + GUID ObjectType; + GUID InheritedObjectType; + DWORD SidStart; +} +alias SYSTEM_ALARM_OBJECT_ACE* PSYSTEM_ALARM_OBJECT_ACE; + +struct ACL { + BYTE AclRevision; + BYTE Sbz1; + WORD AclSize; + WORD AceCount; + WORD Sbz2; +} +alias ACL* PACL; + +struct ACL_REVISION_INFORMATION { + DWORD AclRevision; +} + +struct ACL_SIZE_INFORMATION { + DWORD AceCount; + DWORD AclBytesInUse; + DWORD AclBytesFree; +} + +version (X86) { + // ??? +enum SIZE_OF_80387_REGISTERS = 80; +enum CONTEXT_i386 = 0x010000; +enum CONTEXT_i486 = 0x010000; +enum CONTEXT_CONTROL = CONTEXT_i386 | 0x01; +enum CONTEXT_INTEGER = CONTEXT_i386 | 0x02; +enum CONTEXT_SEGMENTS = CONTEXT_i386 | 0x04; +enum CONTEXT_FLOATING_POINT = CONTEXT_i386 | 0x08; +enum CONTEXT_DEBUG_REGISTERS = CONTEXT_i386 | 0x10; +enum CONTEXT_EXTENDED_REGISTERS = CONTEXT_i386 | 0x20; +enum CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS; +enum CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | + CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | + CONTEXT_EXTENDED_REGISTERS; + +enum MAXIMUM_SUPPORTED_EXTENSION = 512; + + struct FLOATING_SAVE_AREA { + DWORD ControlWord; + DWORD StatusWord; + DWORD TagWord; + DWORD ErrorOffset; + DWORD ErrorSelector; + DWORD DataOffset; + DWORD DataSelector; + BYTE[80] RegisterArea; + DWORD Cr0NpxState; + } + + struct CONTEXT { + DWORD ContextFlags; + DWORD Dr0; + DWORD Dr1; + DWORD Dr2; + DWORD Dr3; + DWORD Dr6; + DWORD Dr7; + FLOATING_SAVE_AREA FloatSave; + DWORD SegGs; + DWORD SegFs; + DWORD SegEs; + DWORD SegDs; + DWORD Edi; + DWORD Esi; + DWORD Ebx; + DWORD Edx; + DWORD Ecx; + DWORD Eax; + DWORD Ebp; + DWORD Eip; + DWORD SegCs; + DWORD EFlags; + DWORD Esp; + DWORD SegSs; + BYTE[MAXIMUM_SUPPORTED_EXTENSION] ExtendedRegisters; + } + +} else version (X86_64) +{ +enum CONTEXT_AMD64 = 0x100000; + +enum CONTEXT_CONTROL = (CONTEXT_AMD64 | 0x1L); +enum CONTEXT_INTEGER = (CONTEXT_AMD64 | 0x2L); +enum CONTEXT_SEGMENTS = (CONTEXT_AMD64 | 0x4L); +enum CONTEXT_FLOATING_POINT = (CONTEXT_AMD64 | 0x8L); +enum CONTEXT_DEBUG_REGISTERS = (CONTEXT_AMD64 | 0x10L); + +enum CONTEXT_FULL = (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT); +enum CONTEXT_ALL = (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS); + +enum CONTEXT_EXCEPTION_ACTIVE = 0x8000000; +enum CONTEXT_SERVICE_ACTIVE = 0x10000000; +enum CONTEXT_EXCEPTION_REQUEST = 0x40000000; +enum CONTEXT_EXCEPTION_REPORTING = 0x80000000; + +enum INITIAL_MXCSR = 0x1f80; +enum INITIAL_FPCSR = 0x027f; + + align(16) struct M128A + { + ULONGLONG Low; + LONGLONG High; + } + alias M128A* PM128A; + + struct XMM_SAVE_AREA32 + { + WORD ControlWord; + WORD StatusWord; + BYTE TagWord; + BYTE Reserved1; + WORD ErrorOpcode; + DWORD ErrorOffset; + WORD ErrorSelector; + WORD Reserved2; + DWORD DataOffset; + WORD DataSelector; + WORD Reserved3; + DWORD MxCsr; + DWORD MxCsr_Mask; + M128A[8] FloatRegisters; + M128A[16] XmmRegisters; + BYTE[96] Reserved4; + } + alias XMM_SAVE_AREA32 PXMM_SAVE_AREA32; +enum LEGACY_SAVE_AREA_LENGTH = XMM_SAVE_AREA32.sizeof; + + align(16) struct CONTEXT + { + DWORD64 P1Home; + DWORD64 P2Home; + DWORD64 P3Home; + DWORD64 P4Home; + DWORD64 P5Home; + DWORD64 P6Home; + DWORD ContextFlags; + DWORD MxCsr; + WORD SegCs; + WORD SegDs; + WORD SegEs; + WORD SegFs; + WORD SegGs; + WORD SegSs; + DWORD EFlags; + DWORD64 Dr0; + DWORD64 Dr1; + DWORD64 Dr2; + DWORD64 Dr3; + DWORD64 Dr6; + DWORD64 Dr7; + DWORD64 Rax; + DWORD64 Rcx; + DWORD64 Rdx; + DWORD64 Rbx; + DWORD64 Rsp; + DWORD64 Rbp; + DWORD64 Rsi; + DWORD64 Rdi; + DWORD64 R8; + DWORD64 R9; + DWORD64 R10; + DWORD64 R11; + DWORD64 R12; + DWORD64 R13; + DWORD64 R14; + DWORD64 R15; + DWORD64 Rip; + union + { + XMM_SAVE_AREA32 FltSave; + XMM_SAVE_AREA32 FloatSave; + struct + { + M128A[2] Header; + M128A[8] Legacy; + M128A Xmm0; + M128A Xmm1; + M128A Xmm2; + M128A Xmm3; + M128A Xmm4; + M128A Xmm5; + M128A Xmm6; + M128A Xmm7; + M128A Xmm8; + M128A Xmm9; + M128A Xmm10; + M128A Xmm11; + M128A Xmm12; + M128A Xmm13; + M128A Xmm14; + M128A Xmm15; + } + } + M128A[26] VectorRegister; + DWORD64 VectorControl; + DWORD64 DebugControl; + DWORD64 LastBranchToRip; + DWORD64 LastBranchFromRip; + DWORD64 LastExceptionToRip; + DWORD64 LastExceptionFromRip; + } + +} else version(AArch64) { + enum CONTEXT_ARM64 = 0x400000; + + enum CONTEXT_CONTROL = (CONTEXT_ARM64 | 0x1L); + enum CONTEXT_INTEGER = (CONTEXT_ARM64 | 0x2L); + enum CONTEXT_SEGMENTS = (CONTEXT_ARM64 | 0x4L); + enum CONTEXT_FLOATING_POINT = (CONTEXT_ARM64 | 0x8L); + enum CONTEXT_DEBUG_REGISTERS = (CONTEXT_ARM64 | 0x10L); + + enum CONTEXT_FULL = (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT); + enum CONTEXT_ALL = (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS); + + enum ARM64_MAX_BREAKPOINTS = 8; + enum ARM64_MAX_WATCHPOINTS = 2; + + union ARM64_NT_NEON128 { + struct { + ULONGLONG Low; + LONGLONG High; + }; + double[2] D; + float[4] S; + WORD[8] H; + BYTE[16] B; + } + alias PARM64_NT_NEON128 = ARM64_NT_NEON128*; + + align(16) struct CONTEXT + { + DWORD ContextFlags; + DWORD Cpsr; + DWORD64[31] X; + DWORD64 Sp; + DWORD64 Pc; + + ARM64_NT_NEON128[32] V; + DWORD Fpcr; + + DWORD Fpsr; + + DWORD[ARM64_MAX_BREAKPOINTS] Bcr; + DWORD64[ARM64_MAX_BREAKPOINTS] Bvr; + DWORD[ARM64_MAX_WATCHPOINTS] Wcr; + DWORD64[ARM64_MAX_WATCHPOINTS] Wvr; + } +} else { + static assert(false, "Unsupported CPU"); + // Versions for PowerPC, Alpha, SHX, and MIPS removed. +} + +alias CONTEXT* PCONTEXT, LPCONTEXT; + +struct EXCEPTION_RECORD { + DWORD ExceptionCode; + DWORD ExceptionFlags; + EXCEPTION_RECORD* ExceptionRecord; + PVOID ExceptionAddress; + DWORD NumberParameters; + ULONG_PTR[EXCEPTION_MAXIMUM_PARAMETERS] ExceptionInformation; +} +alias EXCEPTION_RECORD* PEXCEPTION_RECORD, LPEXCEPTION_RECORD; + +struct EXCEPTION_POINTERS { + PEXCEPTION_RECORD ExceptionRecord; + PCONTEXT ContextRecord; +} +alias EXCEPTION_POINTERS* PEXCEPTION_POINTERS, LPEXCEPTION_POINTERS; + +union LARGE_INTEGER { + struct { + uint LowPart; + int HighPart; + } + long QuadPart; +} +alias LARGE_INTEGER* PLARGE_INTEGER; + +union ULARGE_INTEGER { + struct { + uint LowPart; + uint HighPart; + } + ulong QuadPart; +} +alias ULARGE_INTEGER* PULARGE_INTEGER; + +alias LARGE_INTEGER LUID; +alias LUID* PLUID; + +enum LUID SYSTEM_LUID = { QuadPart:999 }; + +align(4) struct LUID_AND_ATTRIBUTES { + LUID Luid; + DWORD Attributes; +} +alias LUID_AND_ATTRIBUTES* PLUID_AND_ATTRIBUTES; + +align(4) struct PRIVILEGE_SET { + DWORD PrivilegeCount; + DWORD Control; + LUID_AND_ATTRIBUTES _Privilege; + + LUID_AND_ATTRIBUTES* Privilege() return { return &_Privilege; } +} +alias PRIVILEGE_SET* PPRIVILEGE_SET; + +struct SECURITY_ATTRIBUTES { + DWORD nLength; + LPVOID lpSecurityDescriptor; + BOOL bInheritHandle; +} +alias SECURITY_ATTRIBUTES* PSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES; + +struct SECURITY_QUALITY_OF_SERVICE { + DWORD Length; + SECURITY_IMPERSONATION_LEVEL ImpersonationLevel; + SECURITY_CONTEXT_TRACKING_MODE ContextTrackingMode; + BOOLEAN EffectiveOnly; +} +alias SECURITY_QUALITY_OF_SERVICE* PSECURITY_QUALITY_OF_SERVICE; + +alias PVOID PACCESS_TOKEN; + +struct SE_IMPERSONATION_STATE { + PACCESS_TOKEN Token; + BOOLEAN CopyOnOpen; + BOOLEAN EffectiveOnly; + SECURITY_IMPERSONATION_LEVEL Level; +} +alias SE_IMPERSONATION_STATE* PSE_IMPERSONATION_STATE; + +struct SID_IDENTIFIER_AUTHORITY { + BYTE[6] Value; +} +alias SID_IDENTIFIER_AUTHORITY* PSID_IDENTIFIER_AUTHORITY, LPSID_IDENTIFIER_AUTHORITY; + +alias PVOID PSID; + +struct SID { + BYTE Revision; + BYTE SubAuthorityCount; + SID_IDENTIFIER_AUTHORITY IdentifierAuthority; + DWORD _SubAuthority; + + DWORD* SubAuthority() return { return &_SubAuthority; } +} +alias SID* PISID; + +struct SID_AND_ATTRIBUTES { + PSID Sid; + DWORD Attributes; +} +alias SID_AND_ATTRIBUTES* PSID_AND_ATTRIBUTES; + +struct TOKEN_SOURCE { + CHAR[TOKEN_SOURCE_LENGTH] SourceName = 0; + LUID SourceIdentifier; +} +alias TOKEN_SOURCE* PTOKEN_SOURCE; + +struct TOKEN_CONTROL { + LUID TokenId; + LUID AuthenticationId; + LUID ModifiedId; + TOKEN_SOURCE TokenSource; +} +alias TOKEN_CONTROL* PTOKEN_CONTROL; + +struct TOKEN_DEFAULT_DACL { + PACL DefaultDacl; +} +alias TOKEN_DEFAULT_DACL* PTOKEN_DEFAULT_DACL; + +struct TOKEN_GROUPS { + DWORD GroupCount; + SID_AND_ATTRIBUTES _Groups; + + SID_AND_ATTRIBUTES* Groups() return { return &_Groups; } +} +alias TOKEN_GROUPS* PTOKEN_GROUPS, LPTOKEN_GROUPS; + +struct TOKEN_OWNER { + PSID Owner; +} +alias TOKEN_OWNER* PTOKEN_OWNER; +enum SECURITY_MAX_SID_SIZE = 68; + +struct TOKEN_PRIMARY_GROUP { + PSID PrimaryGroup; +} +alias TOKEN_PRIMARY_GROUP* PTOKEN_PRIMARY_GROUP; + +struct TOKEN_PRIVILEGES { + DWORD PrivilegeCount; + LUID_AND_ATTRIBUTES _Privileges; + + LUID_AND_ATTRIBUTES* Privileges() return { return &_Privileges; } +} +alias TOKEN_PRIVILEGES* PTOKEN_PRIVILEGES, LPTOKEN_PRIVILEGES; + +enum TOKEN_TYPE { + TokenPrimary = 1, + TokenImpersonation +} +alias TOKEN_TYPE* PTOKEN_TYPE; + +struct TOKEN_STATISTICS { + LUID TokenId; + LUID AuthenticationId; + LARGE_INTEGER ExpirationTime; + TOKEN_TYPE TokenType; + SECURITY_IMPERSONATION_LEVEL ImpersonationLevel; + DWORD DynamicCharged; + DWORD DynamicAvailable; + DWORD GroupCount; + DWORD PrivilegeCount; + LUID ModifiedId; +} +alias TOKEN_STATISTICS* PTOKEN_STATISTICS; + +struct TOKEN_USER { + SID_AND_ATTRIBUTES User; +} +alias TOKEN_USER* PTOKEN_USER; + +struct TOKEN_MANDATORY_LABEL { + SID_AND_ATTRIBUTES Label; +} +alias PTOKEN_MANDATORY_LABEL = TOKEN_MANDATORY_LABEL*; +alias DWORD SECURITY_INFORMATION; +alias SECURITY_INFORMATION* PSECURITY_INFORMATION; +alias WORD SECURITY_DESCRIPTOR_CONTROL; +alias SECURITY_DESCRIPTOR_CONTROL* PSECURITY_DESCRIPTOR_CONTROL; + +struct SECURITY_DESCRIPTOR { + BYTE Revision; + BYTE Sbz1; + SECURITY_DESCRIPTOR_CONTROL Control; + PSID Owner; + PSID Group; + PACL Sacl; + PACL Dacl; +} +alias SECURITY_DESCRIPTOR* PSECURITY_DESCRIPTOR, PISECURITY_DESCRIPTOR; +enum TOKEN_ELEVATION_TYPE { + TokenElevationTypeDefault = 1, + TokenElevationTypeFull, + TokenElevationTypeLimited +} + +alias PTOKEN_ELEVATION_TYPE = TOKEN_ELEVATION_TYPE*; + +struct TOKEN_ELEVATION { + DWORD TokenIsElevated; +} +alias PTOKEN_ELEVATION = TOKEN_ELEVATION*; + +enum TOKEN_INFORMATION_CLASS { + TokenUser = 1, + TokenGroups, + TokenPrivileges, + TokenOwner, + TokenPrimaryGroup, + TokenDefaultDacl, + TokenSource, + TokenType, + TokenImpersonationLevel, + TokenStatistics, + TokenRestrictedSids, + TokenSessionId, + TokenGroupsAndPrivileges, + TokenSessionReference, + TokenSandBoxInert, + TokenAuditPolicy, + TokenOrigin, + TokenElevationType, + TokenLinkedToken, + TokenElevation, + TokenHasRestrictions, + TokenAccessInformation, + TokenVirtualizationAllowed, + TokenVirtualizationEnabled, + TokenIntegrityLevel, + TokenUIAccess, + TokenMandatoryPolicy, + TokenLogonSid, + TokenIsAppContainer, + TokenCapabilities, + TokenAppContainerSid, + TokenAppContainerNumber, + TokenUserClaimAttributes, + TokenDeviceClaimAttributes, + TokenRestrictedUserClaimAttributes, + TokenRestrictedDeviceClaimAttributes, + TokenDeviceGroups, + TokenRestrictedDeviceGroups, + TokenSecurityAttributes, + TokenIsRestricted, + TokenProcessTrustLevel, + MaxTokenInfoClass // MaxTokenInfoClass should always be the last enum +} + +enum SID_NAME_USE { + SidTypeUser = 1, + SidTypeGroup, + SidTypeDomain, + SidTypeAlias, + SidTypeWellKnownGroup, + SidTypeDeletedAccount, + SidTypeInvalid, + SidTypeUnknown, + SidTypeComputer +} +alias SID_NAME_USE* PSID_NAME_USE; + +enum WELL_KNOWN_SID_TYPE { + WinNullSid = 0, + WinWorldSid = 1, + WinLocalSid = 2, + WinCreatorOwnerSid = 3, + WinCreatorGroupSid = 4, + WinCreatorOwnerServerSid = 5, + WinCreatorGroupServerSid = 6, + WinNtAuthoritySid = 7, + WinDialupSid = 8, + WinNetworkSid = 9, + WinBatchSid = 10, + WinInteractiveSid = 11, + WinServiceSid = 12, + WinAnonymousSid = 13, + WinProxySid = 14, + WinEnterpriseControllersSid = 15, + WinSelfSid = 16, + WinAuthenticatedUserSid = 17, + WinRestrictedCodeSid = 18, + WinTerminalServerSid = 19, + WinRemoteLogonIdSid = 20, + WinLogonIdsSid = 21, + WinLocalSystemSid = 22, + WinLocalServiceSid = 23, + WinNetworkServiceSid = 24, + WinBuiltinDomainSid = 25, + WinBuiltinAdministratorsSid = 26, + WinBuiltinUsersSid = 27, + WinBuiltinGuestsSid = 28, + WinBuiltinPowerUsersSid = 29, + WinBuiltinAccountOperatorsSid = 30, + WinBuiltinSystemOperatorsSid = 31, + WinBuiltinPrintOperatorsSid = 32, + WinBuiltinBackupOperatorsSid = 33, + WinBuiltinReplicatorSid = 34, + WinBuiltinPreWindows2000CompatibleAccessSid = 35, + WinBuiltinRemoteDesktopUsersSid = 36, + WinBuiltinNetworkConfigurationOperatorsSid = 37, + WinAccountAdministratorSid = 38, + WinAccountGuestSid = 39, + WinAccountKrbtgtSid = 40, + WinAccountDomainAdminsSid = 41, + WinAccountDomainUsersSid = 42, + WinAccountDomainGuestsSid = 43, + WinAccountComputersSid = 44, + WinAccountControllersSid = 45, + WinAccountCertAdminsSid = 46, + WinAccountSchemaAdminsSid = 47, + WinAccountEnterpriseAdminsSid = 48, + WinAccountPolicyAdminsSid = 49, + WinAccountRasAndIasServersSid = 50, + WinNTLMAuthenticationSid = 51, + WinDigestAuthenticationSid = 52, + WinSChannelAuthenticationSid = 53, + WinThisOrganizationSid = 54, + WinOtherOrganizationSid = 55, + WinBuiltinIncomingForestTrustBuildersSid = 56, + WinBuiltinPerfMonitoringUsersSid = 57, + WinBuiltinPerfLoggingUsersSid = 58, + WinBuiltinAuthorizationAccessSid = 59, + WinBuiltinTerminalServerLicenseServersSid = 60, + WinBuiltinDCOMUsersSid = 61, + WinBuiltinIUsersSid = 62, + WinIUserSid = 63, + WinBuiltinCryptoOperatorsSid = 64, + WinUntrustedLabelSid = 65, + WinLowLabelSid = 66, + WinMediumLabelSid = 67, + WinHighLabelSid = 68, + WinSystemLabelSid = 69, + WinWriteRestrictedCodeSid = 70, + WinCreatorOwnerRightsSid = 71, + WinCacheablePrincipalsGroupSid = 72, + WinNonCacheablePrincipalsGroupSid = 73, + WinEnterpriseReadonlyControllersSid = 74, + WinAccountReadonlyControllersSid = 75, + WinBuiltinEventLogReadersGroup = 76, + WinNewEnterpriseReadonlyControllersSid = 77, + WinBuiltinCertSvcDComAccessGroup = 78, + WinMediumPlusLabelSid = 79, + WinLocalLogonSid = 80, + WinConsoleLogonSid = 81, + WinThisOrganizationCertificateSid = 82, + WinApplicationPackageAuthoritySid = 83, + WinBuiltinAnyPackageSid = 84, + WinCapabilityInternetClientSid = 85, + WinCapabilityInternetClientServerSid = 86, + WinCapabilityPrivateNetworkClientServerSid = 87, + WinCapabilityPicturesLibrarySid = 88, + WinCapabilityVideosLibrarySid = 89, + WinCapabilityMusicLibrarySid = 90, + WinCapabilityDocumentsLibrarySid = 91, + WinCapabilitySharedUserCertificatesSid = 92, + WinCapabilityEnterpriseAuthenticationSid = 93, + WinCapabilityRemovableStorageSid = 94 +} +struct QUOTA_LIMITS { + SIZE_T PagedPoolLimit; + SIZE_T NonPagedPoolLimit; + SIZE_T MinimumWorkingSetSize; + SIZE_T MaximumWorkingSetSize; + SIZE_T PagefileLimit; + LARGE_INTEGER TimeLimit; +} +alias QUOTA_LIMITS* PQUOTA_LIMITS; + +struct IO_COUNTERS { + ULONGLONG ReadOperationCount; + ULONGLONG WriteOperationCount; + ULONGLONG OtherOperationCount; + ULONGLONG ReadTransferCount; + ULONGLONG WriteTransferCount; + ULONGLONG OtherTransferCount; +} +alias IO_COUNTERS* PIO_COUNTERS; + +struct FILE_NOTIFY_INFORMATION { + DWORD NextEntryOffset; + DWORD Action; + DWORD FileNameLength = 0; + WCHAR _FileName = 0; + + WCHAR* FileName() return { return &_FileName; } +} +alias FILE_NOTIFY_INFORMATION* PFILE_NOTIFY_INFORMATION; + +struct TAPE_ERASE { + DWORD Type; + BOOLEAN Immediate; +} +alias TAPE_ERASE* PTAPE_ERASE; + +struct TAPE_GET_DRIVE_PARAMETERS { + BOOLEAN ECC; + BOOLEAN Compression; + BOOLEAN DataPadding; + BOOLEAN ReportSetmarks; + DWORD DefaultBlockSize; + DWORD MaximumBlockSize; + DWORD MinimumBlockSize; + DWORD MaximumPartitionCount; + DWORD FeaturesLow; + DWORD FeaturesHigh; + DWORD EOTWarningZoneSize; +} +alias TAPE_GET_DRIVE_PARAMETERS* PTAPE_GET_DRIVE_PARAMETERS; + +struct TAPE_GET_MEDIA_PARAMETERS { + LARGE_INTEGER Capacity; + LARGE_INTEGER Remaining; + DWORD BlockSize; + DWORD PartitionCount; + BOOLEAN WriteProtected; +} +alias TAPE_GET_MEDIA_PARAMETERS* PTAPE_GET_MEDIA_PARAMETERS; + +struct TAPE_GET_POSITION { + ULONG Type; + ULONG Partition; + ULONG OffsetLow; + ULONG OffsetHigh; +} +alias TAPE_GET_POSITION* PTAPE_GET_POSITION; + +struct TAPE_PREPARE { + DWORD Operation; + BOOLEAN Immediate; +} +alias TAPE_PREPARE* PTAPE_PREPARE; + +struct TAPE_SET_DRIVE_PARAMETERS { + BOOLEAN ECC; + BOOLEAN Compression; + BOOLEAN DataPadding; + BOOLEAN ReportSetmarks; + ULONG EOTWarningZoneSize; +} +alias TAPE_SET_DRIVE_PARAMETERS* PTAPE_SET_DRIVE_PARAMETERS; + +struct TAPE_SET_MEDIA_PARAMETERS { + ULONG BlockSize; +} +alias TAPE_SET_MEDIA_PARAMETERS* PTAPE_SET_MEDIA_PARAMETERS; + +struct TAPE_SET_POSITION { + DWORD Method; + DWORD Partition; + LARGE_INTEGER Offset; + BOOLEAN Immediate; +} +alias TAPE_SET_POSITION* PTAPE_SET_POSITION; + +struct TAPE_WRITE_MARKS { + DWORD Type; + DWORD Count; + BOOLEAN Immediate; +} +alias TAPE_WRITE_MARKS* PTAPE_WRITE_MARKS; + +struct TAPE_CREATE_PARTITION { + DWORD Method; + DWORD Count; + DWORD Size; +} +alias TAPE_CREATE_PARTITION* PTAPE_CREATE_PARTITION; + +struct MEMORY_BASIC_INFORMATION { + PVOID BaseAddress; + PVOID AllocationBase; + DWORD AllocationProtect; + SIZE_T RegionSize; + DWORD State; + DWORD Protect; + DWORD Type; +} +alias MEMORY_BASIC_INFORMATION* PMEMORY_BASIC_INFORMATION; + +struct MESSAGE_RESOURCE_ENTRY { + WORD Length; + WORD Flags; + BYTE _Text; + + BYTE* Text() return { return &_Text; } +} +alias MESSAGE_RESOURCE_ENTRY* PMESSAGE_RESOURCE_ENTRY; + +struct MESSAGE_RESOURCE_BLOCK { + DWORD LowId; + DWORD HighId; + DWORD OffsetToEntries; +} +alias MESSAGE_RESOURCE_BLOCK* PMESSAGE_RESOURCE_BLOCK; + +struct MESSAGE_RESOURCE_DATA { + DWORD NumberOfBlocks; + MESSAGE_RESOURCE_BLOCK _Blocks; + + MESSAGE_RESOURCE_BLOCK* Blocks() return { return &_Blocks; } +} +alias MESSAGE_RESOURCE_DATA* PMESSAGE_RESOURCE_DATA; + +struct LIST_ENTRY { + LIST_ENTRY* Flink; + LIST_ENTRY* Blink; +} +alias LIST_ENTRY* PLIST_ENTRY; +alias LIST_ENTRY _LIST_ENTRY; + +struct SINGLE_LIST_ENTRY { + SINGLE_LIST_ENTRY* Next; +} + +version (Win64) { + align (16) + struct SLIST_ENTRY { + SLIST_ENTRY* Next; + } +} else { + alias SINGLE_LIST_ENTRY SLIST_ENTRY; +} +alias SINGLE_LIST_ENTRY* PSINGLE_LIST_ENTRY, PSLIST_ENTRY; + +union SLIST_HEADER { + ULONGLONG Alignment; + struct { + SLIST_ENTRY Next; + WORD Depth; + WORD Sequence; + } +} +alias SLIST_HEADER* PSLIST_HEADER; + +struct RTL_CRITICAL_SECTION_DEBUG { + WORD Type; + WORD CreatorBackTraceIndex; + RTL_CRITICAL_SECTION* CriticalSection; + LIST_ENTRY ProcessLocksList; + DWORD EntryCount; + DWORD ContentionCount; + DWORD[2] Spare; +} +alias RTL_CRITICAL_SECTION_DEBUG* PRTL_CRITICAL_SECTION_DEBUG; +alias RTL_CRITICAL_SECTION_DEBUG _RTL_CRITICAL_SECTION_DEBUG; + +struct RTL_CRITICAL_SECTION { + PRTL_CRITICAL_SECTION_DEBUG DebugInfo; + LONG LockCount; + LONG RecursionCount; + HANDLE OwningThread; + HANDLE LockSemaphore; + ULONG_PTR SpinCount; + alias Reserved = SpinCount; +} +alias RTL_CRITICAL_SECTION* PRTL_CRITICAL_SECTION; +alias RTL_CRITICAL_SECTION _RTL_CRITICAL_SECTION; + +struct EVENTLOGRECORD { + DWORD Length; + DWORD Reserved; + DWORD RecordNumber; + DWORD TimeGenerated; + DWORD TimeWritten; + DWORD EventID; + WORD EventType; + WORD NumStrings; + WORD EventCategory; + WORD ReservedFlags; + DWORD ClosingRecordNumber; + DWORD StringOffset; + DWORD UserSidLength; + DWORD UserSidOffset; + DWORD DataLength; + DWORD DataOffset; +} +alias EVENTLOGRECORD* PEVENTLOGRECORD; + +struct OSVERSIONINFOA { + DWORD dwOSVersionInfoSize = OSVERSIONINFOA.sizeof; + DWORD dwMajorVersion; + DWORD dwMinorVersion; + DWORD dwBuildNumber; + DWORD dwPlatformId; + CHAR[128] szCSDVersion = 0; +} +alias OSVERSIONINFOA* POSVERSIONINFOA, LPOSVERSIONINFOA; + +struct OSVERSIONINFOW { + DWORD dwOSVersionInfoSize = OSVERSIONINFOW.sizeof; + DWORD dwMajorVersion; + DWORD dwMinorVersion; + DWORD dwBuildNumber; + DWORD dwPlatformId; + WCHAR[128] szCSDVersion = 0; +} +alias OSVERSIONINFOW* POSVERSIONINFOW, LPOSVERSIONINFOW; + +struct OSVERSIONINFOEXA { + DWORD dwOSVersionInfoSize; + DWORD dwMajorVersion; + DWORD dwMinorVersion; + DWORD dwBuildNumber; + DWORD dwPlatformId; + CHAR[128] szCSDVersion = 0; + WORD wServicePackMajor; + WORD wServicePackMinor; + WORD wSuiteMask; + BYTE wProductType; + BYTE wReserved; +} +alias OSVERSIONINFOEXA* POSVERSIONINFOEXA, LPOSVERSIONINFOEXA; + +struct OSVERSIONINFOEXW { + DWORD dwOSVersionInfoSize; + DWORD dwMajorVersion; + DWORD dwMinorVersion; + DWORD dwBuildNumber; + DWORD dwPlatformId; + WCHAR[128] szCSDVersion = 0; + WORD wServicePackMajor; + WORD wServicePackMinor; + WORD wSuiteMask; + BYTE wProductType; + BYTE wReserved; +} +alias OSVERSIONINFOEXW* POSVERSIONINFOEXW, LPOSVERSIONINFOEXW; + +align(2) struct IMAGE_VXD_HEADER { + WORD e32_magic; + BYTE e32_border; + BYTE e32_worder; + DWORD e32_level; + WORD e32_cpu; + WORD e32_os; + DWORD e32_ver; + DWORD e32_mflags; + DWORD e32_mpages; + DWORD e32_startobj; + DWORD e32_eip; + DWORD e32_stackobj; + DWORD e32_esp; + DWORD e32_pagesize; + DWORD e32_lastpagesize; + DWORD e32_fixupsize; + DWORD e32_fixupsum; + DWORD e32_ldrsize; + DWORD e32_ldrsum; + DWORD e32_objtab; + DWORD e32_objcnt; + DWORD e32_objmap; + DWORD e32_itermap; + DWORD e32_rsrctab; + DWORD e32_rsrccnt; + DWORD e32_restab; + DWORD e32_enttab; + DWORD e32_dirtab; + DWORD e32_dircnt; + DWORD e32_fpagetab; + DWORD e32_frectab; + DWORD e32_impmod; + DWORD e32_impmodcnt; + DWORD e32_impproc; + DWORD e32_pagesum; + DWORD e32_datapage; + DWORD e32_preload; + DWORD e32_nrestab; + DWORD e32_cbnrestab; + DWORD e32_nressum; + DWORD e32_autodata; + DWORD e32_debuginfo; + DWORD e32_debuglen; + DWORD e32_instpreload; + DWORD e32_instdemand; + DWORD e32_heapsize; + BYTE[12] e32_res3; + DWORD e32_winresoff; + DWORD e32_winreslen; + WORD e32_devid; + WORD e32_ddkver; +} +alias IMAGE_VXD_HEADER* PIMAGE_VXD_HEADER; + +align(4): +struct IMAGE_FILE_HEADER { + WORD Machine; + WORD NumberOfSections; + DWORD TimeDateStamp; + DWORD PointerToSymbolTable; + DWORD NumberOfSymbols; + WORD SizeOfOptionalHeader; + WORD Characteristics; +} +alias IMAGE_FILE_HEADER* PIMAGE_FILE_HEADER; +// const IMAGE_SIZEOF_FILE_HEADER = IMAGE_FILE_HEADER.sizeof; + +struct IMAGE_DATA_DIRECTORY { + DWORD VirtualAddress; + DWORD Size; +} +alias IMAGE_DATA_DIRECTORY* PIMAGE_DATA_DIRECTORY; + +struct IMAGE_OPTIONAL_HEADER32 { + WORD Magic; + BYTE MajorLinkerVersion; + BYTE MinorLinkerVersion; + DWORD SizeOfCode; + DWORD SizeOfInitializedData; + DWORD SizeOfUninitializedData; + DWORD AddressOfEntryPoint; + DWORD BaseOfCode; + DWORD BaseOfData; + DWORD ImageBase; + DWORD SectionAlignment; + DWORD FileAlignment; + WORD MajorOperatingSystemVersion; + WORD MinorOperatingSystemVersion; + WORD MajorImageVersion; + WORD MinorImageVersion; + WORD MajorSubsystemVersion; + WORD MinorSubsystemVersion; + DWORD Win32VersionValue; + DWORD SizeOfImage; + DWORD SizeOfHeaders; + DWORD CheckSum; + WORD Subsystem; + WORD DllCharacteristics; + DWORD SizeOfStackReserve; + DWORD SizeOfStackCommit; + DWORD SizeOfHeapReserve; + DWORD SizeOfHeapCommit; + DWORD LoaderFlags; + DWORD NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] DataDirectory; +} +alias IMAGE_OPTIONAL_HEADER32* PIMAGE_OPTIONAL_HEADER32; + +struct IMAGE_OPTIONAL_HEADER64 { + WORD Magic; + BYTE MajorLinkerVersion; + BYTE MinorLinkerVersion; + DWORD SizeOfCode; + DWORD SizeOfInitializedData; + DWORD SizeOfUninitializedData; + DWORD AddressOfEntryPoint; + DWORD BaseOfCode; + ULONGLONG ImageBase; + DWORD SectionAlignment; + DWORD FileAlignment; + WORD MajorOperatingSystemVersion; + WORD MinorOperatingSystemVersion; + WORD MajorImageVersion; + WORD MinorImageVersion; + WORD MajorSubsystemVersion; + WORD MinorSubsystemVersion; + DWORD Win32VersionValue; + DWORD SizeOfImage; + DWORD SizeOfHeaders; + DWORD CheckSum; + WORD Subsystem; + WORD DllCharacteristics; + ULONGLONG SizeOfStackReserve; + ULONGLONG SizeOfStackCommit; + ULONGLONG SizeOfHeapReserve; + ULONGLONG SizeOfHeapCommit; + DWORD LoaderFlags; + DWORD NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] DataDirectory; +} +alias IMAGE_OPTIONAL_HEADER64* PIMAGE_OPTIONAL_HEADER64; + +struct IMAGE_ROM_OPTIONAL_HEADER { + WORD Magic; + BYTE MajorLinkerVersion; + BYTE MinorLinkerVersion; + DWORD SizeOfCode; + DWORD SizeOfInitializedData; + DWORD SizeOfUninitializedData; + DWORD AddressOfEntryPoint; + DWORD BaseOfCode; + DWORD BaseOfData; + DWORD BaseOfBss; + DWORD GprMask; + DWORD[4] CprMask; + DWORD GpValue; +} +alias IMAGE_ROM_OPTIONAL_HEADER* PIMAGE_ROM_OPTIONAL_HEADER; + +align(2): +struct IMAGE_DOS_HEADER { + WORD e_magic; + WORD e_cblp; + WORD e_cp; + WORD e_crlc; + WORD e_cparhdr; + WORD e_minalloc; + WORD e_maxalloc; + WORD e_ss; + WORD e_sp; + WORD e_csum; + WORD e_ip; + WORD e_cs; + WORD e_lfarlc; + WORD e_ovno; + WORD[4] e_res; + WORD e_oemid; + WORD e_oeminfo; + WORD[10] e_res2; + LONG e_lfanew; +} +alias IMAGE_DOS_HEADER* PIMAGE_DOS_HEADER; + +struct IMAGE_OS2_HEADER { + WORD ne_magic; + CHAR ne_ver = 0; + CHAR ne_rev = 0; + WORD ne_enttab; + WORD ne_cbenttab; + LONG ne_crc; + WORD ne_flags; + WORD ne_autodata; + WORD ne_heap; + WORD ne_stack; + LONG ne_csip; + LONG ne_sssp; + WORD ne_cseg; + WORD ne_cmod; + WORD ne_cbnrestab; + WORD ne_segtab; + WORD ne_rsrctab; + WORD ne_restab; + WORD ne_modtab; + WORD ne_imptab; + LONG ne_nrestab; + WORD ne_cmovent; + WORD ne_align; + WORD ne_cres; + BYTE ne_exetyp; + BYTE ne_flagsothers; + WORD ne_pretthunks; + WORD ne_psegrefbytes; + WORD ne_swaparea; + WORD ne_expver; +} +alias IMAGE_OS2_HEADER* PIMAGE_OS2_HEADER; + +align(4) struct IMAGE_NT_HEADERS32 { + DWORD Signature; + IMAGE_FILE_HEADER FileHeader; + IMAGE_OPTIONAL_HEADER32 OptionalHeader; +} +alias IMAGE_NT_HEADERS32* PIMAGE_NT_HEADERS32; + +align(4) struct IMAGE_NT_HEADERS64 { + DWORD Signature; + IMAGE_FILE_HEADER FileHeader; + IMAGE_OPTIONAL_HEADER64 OptionalHeader; +} +alias IMAGE_NT_HEADERS64* PIMAGE_NT_HEADERS64; + +struct IMAGE_ROM_HEADERS { + IMAGE_FILE_HEADER FileHeader; + IMAGE_ROM_OPTIONAL_HEADER OptionalHeader; +} +alias IMAGE_ROM_HEADERS* PIMAGE_ROM_HEADERS; + +struct IMAGE_SECTION_HEADER { + BYTE[IMAGE_SIZEOF_SHORT_NAME] Name; + union _Misc { + DWORD PhysicalAddress; + DWORD VirtualSize; + } + _Misc Misc; + DWORD VirtualAddress; + DWORD SizeOfRawData; + DWORD PointerToRawData; + DWORD PointerToRelocations; + DWORD PointerToLinenumbers; + WORD NumberOfRelocations; + WORD NumberOfLinenumbers; + DWORD Characteristics; +} +alias IMAGE_SECTION_HEADER* PIMAGE_SECTION_HEADER; + +struct IMAGE_SYMBOL { + union _N { + BYTE[8] ShortName; + struct _Name { + DWORD Short; + DWORD Long; + } + _Name Name; + DWORD[2] LongName; // PBYTE[2] + } + _N N; + DWORD Value; + SHORT SectionNumber; + WORD Type; + BYTE StorageClass; + BYTE NumberOfAuxSymbols; +} +alias IMAGE_SYMBOL* PIMAGE_SYMBOL; + +union IMAGE_AUX_SYMBOL { + struct _Sym { + DWORD TagIndex; + union _Misc { + struct _LnSz { + WORD Linenumber; + WORD Size; + } + _LnSz LnSz; + DWORD TotalSize; + } + _Misc Misc; + union _FcnAry { + struct _Function { + DWORD PointerToLinenumber; + DWORD PointerToNextFunction; + } + _Function Function; + struct _Array { + WORD[4] Dimension; + } + _Array Array; + } + _FcnAry FcnAry; + WORD TvIndex; + } + _Sym Sym; + struct _File { + BYTE[IMAGE_SIZEOF_SYMBOL] Name; + } + _File File; + struct _Section { + DWORD Length; + WORD NumberOfRelocations; + WORD NumberOfLinenumbers; + DWORD CheckSum; + SHORT Number; + BYTE Selection; + } + _Section Section; +} +alias IMAGE_AUX_SYMBOL* PIMAGE_AUX_SYMBOL; + +struct IMAGE_COFF_SYMBOLS_HEADER { + DWORD NumberOfSymbols; + DWORD LvaToFirstSymbol; + DWORD NumberOfLinenumbers; + DWORD LvaToFirstLinenumber; + DWORD RvaToFirstByteOfCode; + DWORD RvaToLastByteOfCode; + DWORD RvaToFirstByteOfData; + DWORD RvaToLastByteOfData; +} +alias IMAGE_COFF_SYMBOLS_HEADER* PIMAGE_COFF_SYMBOLS_HEADER; + +struct IMAGE_RELOCATION { + union { + DWORD VirtualAddress; + DWORD RelocCount; + } + DWORD SymbolTableIndex; + WORD Type; +} +alias IMAGE_RELOCATION* PIMAGE_RELOCATION; + +align(4) struct IMAGE_BASE_RELOCATION { + DWORD VirtualAddress; + DWORD SizeOfBlock; +} +alias IMAGE_BASE_RELOCATION* PIMAGE_BASE_RELOCATION; + +align(2) struct IMAGE_LINENUMBER { + union _Type { + DWORD SymbolTableIndex; + DWORD VirtualAddress; + } + _Type Type; + WORD Linenumber; +} +alias IMAGE_LINENUMBER* PIMAGE_LINENUMBER; + +align(4): +struct IMAGE_ARCHIVE_MEMBER_HEADER { + BYTE[16] Name; + BYTE[12] Date; + BYTE[6] UserID; + BYTE[6] GroupID; + BYTE[8] Mode; + BYTE[10] Size; + BYTE[2] EndHeader; +} +alias IMAGE_ARCHIVE_MEMBER_HEADER* PIMAGE_ARCHIVE_MEMBER_HEADER; + +struct IMAGE_EXPORT_DIRECTORY { + DWORD Characteristics; + DWORD TimeDateStamp; + WORD MajorVersion; + WORD MinorVersion; + DWORD Name; + DWORD Base; + DWORD NumberOfFunctions; + DWORD NumberOfNames; + DWORD AddressOfFunctions; + DWORD AddressOfNames; + DWORD AddressOfNameOrdinals; +} +alias IMAGE_EXPORT_DIRECTORY* PIMAGE_EXPORT_DIRECTORY; + +struct IMAGE_IMPORT_BY_NAME { + WORD Hint; + BYTE _Name; + + BYTE* Name() return { return &_Name; } +} +alias IMAGE_IMPORT_BY_NAME* PIMAGE_IMPORT_BY_NAME; + +struct IMAGE_THUNK_DATA32 { + union _u1 { + DWORD ForwarderString; + DWORD Function; + DWORD Ordinal; + DWORD AddressOfData; + } + _u1 u1; +} +alias IMAGE_THUNK_DATA32* PIMAGE_THUNK_DATA32; + +struct IMAGE_THUNK_DATA64 { + union _u1 { + ULONGLONG ForwarderString; + ULONGLONG Function; + ULONGLONG Ordinal; + ULONGLONG AddressOfData; + } + _u1 u1; +} +alias IMAGE_THUNK_DATA64* PIMAGE_THUNK_DATA64; + +struct IMAGE_IMPORT_DESCRIPTOR { + union { + DWORD Characteristics; + DWORD OriginalFirstThunk; + } + DWORD TimeDateStamp; + DWORD ForwarderChain; + DWORD Name; + DWORD FirstThunk; +} +alias IMAGE_IMPORT_DESCRIPTOR* PIMAGE_IMPORT_DESCRIPTOR; + +struct IMAGE_BOUND_IMPORT_DESCRIPTOR { + DWORD TimeDateStamp; + WORD OffsetModuleName; + WORD NumberOfModuleForwarderRefs; +} +alias IMAGE_BOUND_IMPORT_DESCRIPTOR* PIMAGE_BOUND_IMPORT_DESCRIPTOR; + +struct IMAGE_BOUND_FORWARDER_REF { + DWORD TimeDateStamp; + WORD OffsetModuleName; + WORD Reserved; +} +alias IMAGE_BOUND_FORWARDER_REF* PIMAGE_BOUND_FORWARDER_REF; + +struct IMAGE_TLS_DIRECTORY32 { + DWORD StartAddressOfRawData; + DWORD EndAddressOfRawData; + DWORD AddressOfIndex; + DWORD AddressOfCallBacks; + DWORD SizeOfZeroFill; + DWORD Characteristics; +} +alias IMAGE_TLS_DIRECTORY32* PIMAGE_TLS_DIRECTORY32; + +struct IMAGE_TLS_DIRECTORY64 { + ULONGLONG StartAddressOfRawData; + ULONGLONG EndAddressOfRawData; + ULONGLONG AddressOfIndex; + ULONGLONG AddressOfCallBacks; + DWORD SizeOfZeroFill; + DWORD Characteristics; +} +alias IMAGE_TLS_DIRECTORY64* PIMAGE_TLS_DIRECTORY64; + +struct IMAGE_RESOURCE_DIRECTORY { + DWORD Characteristics; + DWORD TimeDateStamp; + WORD MajorVersion; + WORD MinorVersion; + WORD NumberOfNamedEntries; + WORD NumberOfIdEntries; +} +alias IMAGE_RESOURCE_DIRECTORY* PIMAGE_RESOURCE_DIRECTORY; + +struct IMAGE_RESOURCE_DIRECTORY_ENTRY { + union { + /+struct { + DWORD NameOffset:31; + DWORD NameIsString:1; + }+/ + DWORD Name; + WORD Id; + } + DWORD OffsetToData; + /+struct { + DWORD OffsetToDirectory:31; + DWORD DataIsDirectory:1; + }+/ + + uint NameOffset() { return Name & 0x7FFFFFFF; } + bool NameIsString() { return cast(bool)(Name & 0x80000000); } + uint OffsetToDirectory()() { return OffsetToData & 0x7FFFFFFF; } + bool DataIsDirectory() { return cast(bool)(OffsetToData & 0x80000000); } + + uint NameOffset(uint n) { + Name = (Name & 0x80000000) | (n & 0x7FFFFFFF); + return n & 0x7FFFFFFF; + } + + bool NameIsString(bool n) { + Name = (Name & 0x7FFFFFFF) | (n << 31); return n; + } + + uint OffsetToDirectory(uint o) { + OffsetToData = (OffsetToData & 0x80000000) | (o & 0x7FFFFFFF); + return o & 0x7FFFFFFF; + } + + bool DataIsDirectory(bool d) { + OffsetToData = (OffsetToData & 0x7FFFFFFF) | (d << 31); return d; + } +} +alias IMAGE_RESOURCE_DIRECTORY_ENTRY* PIMAGE_RESOURCE_DIRECTORY_ENTRY; + +struct IMAGE_RESOURCE_DIRECTORY_STRING { + WORD Length; + CHAR _NameString = 0; + + CHAR* NameString() return { return &_NameString; } +} +alias IMAGE_RESOURCE_DIRECTORY_STRING* PIMAGE_RESOURCE_DIRECTORY_STRING; + +struct IMAGE_RESOURCE_DIR_STRING_U { + WORD Length; + WCHAR _NameString = 0; + + WCHAR* NameString() return { return &_NameString; } +} +alias IMAGE_RESOURCE_DIR_STRING_U* PIMAGE_RESOURCE_DIR_STRING_U; + +struct IMAGE_RESOURCE_DATA_ENTRY { + DWORD OffsetToData; + DWORD Size; + DWORD CodePage; + DWORD Reserved; +} +alias IMAGE_RESOURCE_DATA_ENTRY* PIMAGE_RESOURCE_DATA_ENTRY; + +struct IMAGE_LOAD_CONFIG_DIRECTORY32 { + DWORD Characteristics; + DWORD TimeDateStamp; + WORD MajorVersion; + WORD MinorVersion; + DWORD GlobalFlagsClear; + DWORD GlobalFlagsSet; + DWORD CriticalSectionDefaultTimeout; + DWORD DeCommitFreeBlockThreshold; + DWORD DeCommitTotalFreeThreshold; + PVOID LockPrefixTable; + DWORD MaximumAllocationSize; + DWORD VirtualMemoryThreshold; + DWORD ProcessHeapFlags; + DWORD[4] Reserved; +} +alias IMAGE_LOAD_CONFIG_DIRECTORY32* PIMAGE_LOAD_CONFIG_DIRECTORY32; + +struct IMAGE_LOAD_CONFIG_DIRECTORY64 { + DWORD Characteristics; + DWORD TimeDateStamp; + WORD MajorVersion; + WORD MinorVersion; + DWORD GlobalFlagsClear; + DWORD GlobalFlagsSet; + DWORD CriticalSectionDefaultTimeout; + ULONGLONG DeCommitFreeBlockThreshold; + ULONGLONG DeCommitTotalFreeThreshold; + ULONGLONG LockPrefixTable; + ULONGLONG MaximumAllocationSize; + ULONGLONG VirtualMemoryThreshold; + ULONGLONG ProcessAffinityMask; + DWORD ProcessHeapFlags; + WORD CSDFlags; + WORD Reserved1; + ULONGLONG EditList; + DWORD[2] Reserved; +} +alias IMAGE_LOAD_CONFIG_DIRECTORY64* PIMAGE_LOAD_CONFIG_DIRECTORY64; + +version (Win64) { + alias IMAGE_LOAD_CONFIG_DIRECTORY64 IMAGE_LOAD_CONFIG_DIRECTORY; +} else { + alias IMAGE_LOAD_CONFIG_DIRECTORY32 IMAGE_LOAD_CONFIG_DIRECTORY; +} +alias IMAGE_LOAD_CONFIG_DIRECTORY* PIMAGE_LOAD_CONFIG_DIRECTORY; + +// Note versions for Alpha, Alpha64, ARM removed. +struct IMAGE_RUNTIME_FUNCTION_ENTRY { + DWORD BeginAddress; + DWORD EndAddress; + union { + DWORD UnwindInfoAddress; + DWORD UnwindData; + } +} +alias IMAGE_RUNTIME_FUNCTION_ENTRY* PIMAGE_RUNTIME_FUNCTION_ENTRY; + +struct IMAGE_CE_RUNTIME_FUNCTION_ENTRY { + uint FuncStart; + union { + ubyte PrologLen; + uint _bf; + } +/+ + unsigned int FuncLen:22; + unsigned int ThirtyTwoBit:1; + unsigned int ExceptionFlag:1; ++/ + uint FuncLen() { return (_bf >> 8) & 0x3FFFFF; } + bool ThirtyTwoBit() { return cast(bool)(_bf & 0x40000000); } + bool ExceptionFlag()() { return cast(bool)(_bf & 0x80000000); } + + uint FuncLen(uint f) { + _bf = (_bf & ~0x3FFFFF00) | ((f & 0x3FFFFF) << 8); return f & 0x3FFFFF; + } + + bool ThirtyTwoBit(bool t) { + _bf = (_bf & ~0x40000000) | (t << 30); return t; + } + + bool ExceptionFlag(bool e) { + _bf = (_bf & ~0x80000000) | (e << 31); return e; + } +} +alias IMAGE_CE_RUNTIME_FUNCTION_ENTRY* PIMAGE_CE_RUNTIME_FUNCTION_ENTRY; + +struct IMAGE_DEBUG_DIRECTORY { + DWORD Characteristics; + DWORD TimeDateStamp; + WORD MajorVersion; + WORD MinorVersion; + DWORD Type; + DWORD SizeOfData; + DWORD AddressOfRawData; + DWORD PointerToRawData; +} +alias IMAGE_DEBUG_DIRECTORY* PIMAGE_DEBUG_DIRECTORY; + +struct FPO_DATA { + DWORD ulOffStart; + DWORD cbProcSize; + DWORD cdwLocals; + WORD cdwParams; + ubyte cbProlog; + ubyte _bf; +/+ + WORD cbRegs:3; + WORD fHasSEH:1; + WORD fUseBP:1; + WORD reserved:1; + WORD cbFrame:2; ++/ + ubyte cbRegs() { return cast(ubyte)(_bf & 0x07); } + bool fHasSEH() { return cast(bool)(_bf & 0x08); } + bool fUseBP() { return cast(bool)(_bf & 0x10); } + bool reserved()() { return cast(bool)(_bf & 0x20); } + ubyte cbFrame()() { return cast(ubyte)(_bf >> 6); } + + ubyte cbRegs(ubyte c) { + _bf = cast(ubyte) ((_bf & ~0x07) | (c & 0x07)); + return cast(ubyte)(c & 0x07); + } + + bool fHasSEH(bool f) { _bf = cast(ubyte)((_bf & ~0x08) | (f << 3)); return f; } + bool fUseBP(bool f) { _bf = cast(ubyte)((_bf & ~0x10) | (f << 4)); return f; } + bool reserved(bool r) { _bf = cast(ubyte)((_bf & ~0x20) | (r << 5)); return r; } + + ubyte cbFrame(ubyte c) { + _bf = cast(ubyte) ((_bf & ~0xC0) | ((c & 0x03) << 6)); + return cast(ubyte)(c & 0x03); + } +} +alias FPO_DATA* PFPO_DATA; + +struct IMAGE_DEBUG_MISC { + DWORD DataType; + DWORD Length; + BOOLEAN Unicode; + BYTE[3] Reserved; + BYTE _Data; + + BYTE* Data() return { return &_Data; } +} +alias IMAGE_DEBUG_MISC* PIMAGE_DEBUG_MISC; + +struct IMAGE_FUNCTION_ENTRY { + DWORD StartingAddress; + DWORD EndingAddress; + DWORD EndOfPrologue; +} +alias IMAGE_FUNCTION_ENTRY* PIMAGE_FUNCTION_ENTRY; + +struct IMAGE_FUNCTION_ENTRY64 { + ULONGLONG StartingAddress; + ULONGLONG EndingAddress; + union { + ULONGLONG EndOfPrologue; + ULONGLONG UnwindInfoAddress; + } +} +alias IMAGE_FUNCTION_ENTRY64* PIMAGE_FUNCTION_ENTRY64; + +struct IMAGE_SEPARATE_DEBUG_HEADER { + WORD Signature; + WORD Flags; + WORD Machine; + WORD Characteristics; + DWORD TimeDateStamp; + DWORD CheckSum; + DWORD ImageBase; + DWORD SizeOfImage; + DWORD NumberOfSections; + DWORD ExportedNamesSize; + DWORD DebugDirectorySize; + DWORD SectionAlignment; + DWORD[2] Reserved; +} +alias IMAGE_SEPARATE_DEBUG_HEADER* PIMAGE_SEPARATE_DEBUG_HEADER; + +enum SERVICE_NODE_TYPE { + DriverType = SERVICE_KERNEL_DRIVER, + FileSystemType = SERVICE_FILE_SYSTEM_DRIVER, + Win32ServiceOwnProcess = SERVICE_WIN32_OWN_PROCESS, + Win32ServiceShareProcess = SERVICE_WIN32_SHARE_PROCESS, + AdapterType = SERVICE_ADAPTER, + RecognizerType = SERVICE_RECOGNIZER_DRIVER +} + +enum SERVICE_LOAD_TYPE { + BootLoad = SERVICE_BOOT_START, + SystemLoad = SERVICE_SYSTEM_START, + AutoLoad = SERVICE_AUTO_START, + DemandLoad = SERVICE_DEMAND_START, + DisableLoad = SERVICE_DISABLED +} + +enum SERVICE_ERROR_TYPE { + IgnoreError = SERVICE_ERROR_IGNORE, + NormalError = SERVICE_ERROR_NORMAL, + SevereError = SERVICE_ERROR_SEVERE, + CriticalError = SERVICE_ERROR_CRITICAL +} +alias SERVICE_ERROR_TYPE _CM_ERROR_CONTROL_TYPE; + +//DAC: According to MSJ, 'UnderTheHood', May 1996, this +// structure is not documented in any official Microsoft header file. +alias void EXCEPTION_REGISTRATION_RECORD; + +align: +struct NT_TIB { + EXCEPTION_REGISTRATION_RECORD *ExceptionList; + PVOID StackBase; + PVOID StackLimit; + PVOID SubSystemTib; + union { + PVOID FiberData; + DWORD Version; + } + PVOID ArbitraryUserPointer; + NT_TIB *Self; +} +alias NT_TIB* PNT_TIB; + +struct REPARSE_DATA_BUFFER { + DWORD ReparseTag; + WORD ReparseDataLength; + WORD Reserved; + union { + struct _GenericReparseBuffer { + BYTE _DataBuffer; + + BYTE* DataBuffer() return { return &_DataBuffer; } + } + _GenericReparseBuffer GenericReparseBuffer; + struct _SymbolicLinkReparseBuffer { + WORD SubstituteNameOffset; + WORD SubstituteNameLength; + WORD PrintNameOffset; + WORD PrintNameLength; + // ??? This is in MinGW, but absent in MSDN docs + ULONG Flags; + WCHAR _PathBuffer = 0; + + WCHAR* PathBuffer() return { return &_PathBuffer; } + } + _SymbolicLinkReparseBuffer SymbolicLinkReparseBuffer; + struct _MountPointReparseBuffer { + WORD SubstituteNameOffset; + WORD SubstituteNameLength; + WORD PrintNameOffset; + WORD PrintNameLength; + WCHAR _PathBuffer = 0; + + WCHAR* PathBuffer() return { return &_PathBuffer; } + } + _MountPointReparseBuffer MountPointReparseBuffer; + } +} +alias REPARSE_DATA_BUFFER *PREPARSE_DATA_BUFFER; + +struct REPARSE_GUID_DATA_BUFFER { + DWORD ReparseTag; + WORD ReparseDataLength; + WORD Reserved; + GUID ReparseGuid; + struct _GenericReparseBuffer { + BYTE _DataBuffer; + + BYTE* DataBuffer() return { return &_DataBuffer; } + } + _GenericReparseBuffer GenericReparseBuffer; +} +alias REPARSE_GUID_DATA_BUFFER* PREPARSE_GUID_DATA_BUFFER; + +enum size_t + REPARSE_DATA_BUFFER_HEADER_SIZE = REPARSE_DATA_BUFFER.GenericReparseBuffer.offsetof, + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE = REPARSE_GUID_DATA_BUFFER.GenericReparseBuffer.offsetof, + MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384; + + +struct REPARSE_POINT_INFORMATION { + WORD ReparseDataLength; + WORD UnparsedNameLength; +} +alias REPARSE_POINT_INFORMATION* PREPARSE_POINT_INFORMATION; + +union FILE_SEGMENT_ELEMENT { + PVOID64 Buffer; + ULONGLONG Alignment; +} +alias FILE_SEGMENT_ELEMENT* PFILE_SEGMENT_ELEMENT; + +// JOBOBJECT_BASIC_LIMIT_INFORMATION.LimitFlags constants +enum DWORD + JOB_OBJECT_LIMIT_WORKINGSET = 0x0001, + JOB_OBJECT_LIMIT_PROCESS_TIME = 0x0002, + JOB_OBJECT_LIMIT_JOB_TIME = 0x0004, + JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 0x0008, + JOB_OBJECT_LIMIT_AFFINITY = 0x0010, + JOB_OBJECT_LIMIT_PRIORITY_CLASS = 0x0020, + JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME = 0x0040, + JOB_OBJECT_LIMIT_SCHEDULING_CLASS = 0x0080, + JOB_OBJECT_LIMIT_PROCESS_MEMORY = 0x0100, + JOB_OBJECT_LIMIT_JOB_MEMORY = 0x0200, + JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION = 0x0400, + JOB_OBJECT_BREAKAWAY_OK = 0x0800, + JOB_OBJECT_SILENT_BREAKAWAY = 0x1000; + +// JOBOBJECT_BASIC_UI_RESTRICTIONS.UIRestrictionsClass constants +enum DWORD + JOB_OBJECT_UILIMIT_HANDLES = 0x0001, + JOB_OBJECT_UILIMIT_READCLIPBOARD = 0x0002, + JOB_OBJECT_UILIMIT_WRITECLIPBOARD = 0x0004, + JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS = 0x0008, + JOB_OBJECT_UILIMIT_DISPLAYSETTINGS = 0x0010, + JOB_OBJECT_UILIMIT_GLOBALATOMS = 0x0020, + JOB_OBJECT_UILIMIT_DESKTOP = 0x0040, + JOB_OBJECT_UILIMIT_EXITWINDOWS = 0x0080; + +// JOBOBJECT_SECURITY_LIMIT_INFORMATION.SecurityLimitFlags constants +enum DWORD + JOB_OBJECT_SECURITY_NO_ADMIN = 0x0001, + JOB_OBJECT_SECURITY_RESTRICTED_TOKEN = 0x0002, + JOB_OBJECT_SECURITY_ONLY_TOKEN = 0x0004, + JOB_OBJECT_SECURITY_FILTER_TOKENS = 0x0008; + +// JOBOBJECT_END_OF_JOB_TIME_INFORMATION.EndOfJobTimeAction constants +enum : DWORD { + JOB_OBJECT_TERMINATE_AT_END_OF_JOB, + JOB_OBJECT_POST_AT_END_OF_JOB +} + +enum : DWORD { + JOB_OBJECT_MSG_END_OF_JOB_TIME = 1, + JOB_OBJECT_MSG_END_OF_PROCESS_TIME, + JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT, + JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO, + JOB_OBJECT_MSG_NEW_PROCESS, + JOB_OBJECT_MSG_EXIT_PROCESS, + JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS, + JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT, + JOB_OBJECT_MSG_JOB_MEMORY_LIMIT +} + +enum JOBOBJECTINFOCLASS { + JobObjectBasicAccountingInformation = 1, + JobObjectBasicLimitInformation, + JobObjectBasicProcessIdList, + JobObjectBasicUIRestrictions, + JobObjectSecurityLimitInformation, + JobObjectEndOfJobTimeInformation, + JobObjectAssociateCompletionPortInformation, + JobObjectBasicAndIoAccountingInformation, + JobObjectExtendedLimitInformation, + JobObjectJobSetInformation, + MaxJobObjectInfoClass +} + +struct JOBOBJECT_BASIC_ACCOUNTING_INFORMATION { + LARGE_INTEGER TotalUserTime; + LARGE_INTEGER TotalKernelTime; + LARGE_INTEGER ThisPeriodTotalUserTime; + LARGE_INTEGER ThisPeriodTotalKernelTime; + DWORD TotalPageFaultCount; + DWORD TotalProcesses; + DWORD ActiveProcesses; + DWORD TotalTerminatedProcesses; +} +alias JOBOBJECT_BASIC_ACCOUNTING_INFORMATION* PJOBOBJECT_BASIC_ACCOUNTING_INFORMATION; + +struct JOBOBJECT_BASIC_LIMIT_INFORMATION { + LARGE_INTEGER PerProcessUserTimeLimit; + LARGE_INTEGER PerJobUserTimeLimit; + DWORD LimitFlags; + SIZE_T MinimumWorkingSetSize; + SIZE_T MaximumWorkingSetSize; + DWORD ActiveProcessLimit; + ULONG_PTR Affinity; + DWORD PriorityClass; + DWORD SchedulingClass; +} +alias JOBOBJECT_BASIC_LIMIT_INFORMATION* PJOBOBJECT_BASIC_LIMIT_INFORMATION; + +struct JOBOBJECT_BASIC_PROCESS_ID_LIST { + DWORD NumberOfAssignedProcesses; + DWORD NumberOfProcessIdsInList; + ULONG_PTR _ProcessIdList; + + ULONG_PTR* ProcessIdList() return { return &_ProcessIdList; } +} +alias JOBOBJECT_BASIC_PROCESS_ID_LIST* PJOBOBJECT_BASIC_PROCESS_ID_LIST; + +struct JOBOBJECT_BASIC_UI_RESTRICTIONS { + DWORD UIRestrictionsClass; +} +alias JOBOBJECT_BASIC_UI_RESTRICTIONS* PJOBOBJECT_BASIC_UI_RESTRICTIONS; + +struct JOBOBJECT_SECURITY_LIMIT_INFORMATION { + DWORD SecurityLimitFlags; + HANDLE JobToken; + PTOKEN_GROUPS SidsToDisable; + PTOKEN_PRIVILEGES PrivilegesToDelete; + PTOKEN_GROUPS RestrictedSids; +} +alias JOBOBJECT_SECURITY_LIMIT_INFORMATION* PJOBOBJECT_SECURITY_LIMIT_INFORMATION; + +struct JOBOBJECT_END_OF_JOB_TIME_INFORMATION { + DWORD EndOfJobTimeAction; +} +alias JOBOBJECT_END_OF_JOB_TIME_INFORMATION* PJOBOBJECT_END_OF_JOB_TIME_INFORMATION; + +struct JOBOBJECT_ASSOCIATE_COMPLETION_PORT { + PVOID CompletionKey; + HANDLE CompletionPort; +} +alias JOBOBJECT_ASSOCIATE_COMPLETION_PORT* PJOBOBJECT_ASSOCIATE_COMPLETION_PORT; + +struct JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION { + JOBOBJECT_BASIC_ACCOUNTING_INFORMATION BasicInfo; + IO_COUNTERS IoInfo; +} +alias JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION *PJOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION; + +struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION { + JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; + IO_COUNTERS IoInfo; + SIZE_T ProcessMemoryLimit; + SIZE_T JobMemoryLimit; + SIZE_T PeakProcessMemoryUsed; + SIZE_T PeakJobMemoryUsed; +} +alias JOBOBJECT_EXTENDED_LIMIT_INFORMATION* PJOBOBJECT_EXTENDED_LIMIT_INFORMATION; + +struct JOBOBJECT_JOBSET_INFORMATION { + DWORD MemberLevel; +} +alias JOBOBJECT_JOBSET_INFORMATION* PJOBOBJECT_JOBSET_INFORMATION; + +// MinGW: Making these defines conditional on _WIN32_WINNT will break ddk includes +//static if (_WIN32_WINNT >= 0x500) { + +enum DWORD + ES_SYSTEM_REQUIRED = 0x00000001, + ES_DISPLAY_REQUIRED = 0x00000002, + ES_USER_PRESENT = 0x00000004, + ES_AWAYMODE_REQUIRED = 0x00000040, + ES_CONTINUOUS = 0x80000000; + +enum LATENCY_TIME { + LT_DONT_CARE, + LT_LOWEST_LATENCY +} +alias LATENCY_TIME* PLATENCY_TIME; + +enum SYSTEM_POWER_STATE { + PowerSystemUnspecified, + PowerSystemWorking, + PowerSystemSleeping1, + PowerSystemSleeping2, + PowerSystemSleeping3, + PowerSystemHibernate, + PowerSystemShutdown, + PowerSystemMaximum +} +alias SYSTEM_POWER_STATE* PSYSTEM_POWER_STATE; + +enum POWER_SYSTEM_MAXIMUM = SYSTEM_POWER_STATE.PowerSystemMaximum; + +enum POWER_ACTION { + PowerActionNone, + PowerActionReserved, + PowerActionSleep, + PowerActionHibernate, + PowerActionShutdown, + PowerActionShutdownReset, + PowerActionShutdownOff, + PowerActionWarmEject +} +alias POWER_ACTION* PPOWER_ACTION; + +static if (_WIN32_WINNT >= 0x600) { + enum SYSTEM_POWER_CONDITION { + PoAc, + PoDc, + PoHot, + PoConditionMaximum + } + alias SYSTEM_POWER_CONDITION* PSYSTEM_POWER_CONDITION; +} + +enum DEVICE_POWER_STATE { + PowerDeviceUnspecified, + PowerDeviceD0, + PowerDeviceD1, + PowerDeviceD2, + PowerDeviceD3, + PowerDeviceMaximum +} +alias DEVICE_POWER_STATE* PDEVICE_POWER_STATE; + +align(4): +struct BATTERY_REPORTING_SCALE { + DWORD Granularity; + DWORD Capacity; +} +alias BATTERY_REPORTING_SCALE* PBATTERY_REPORTING_SCALE; + +struct POWER_ACTION_POLICY { + POWER_ACTION Action; + ULONG Flags; + ULONG EventCode; +} +alias POWER_ACTION_POLICY* PPOWER_ACTION_POLICY; + +// POWER_ACTION_POLICY.Flags constants +enum ULONG + POWER_ACTION_QUERY_ALLOWED = 0x00000001, + POWER_ACTION_UI_ALLOWED = 0x00000002, + POWER_ACTION_OVERRIDE_APPS = 0x00000004, + POWER_ACTION_LIGHTEST_FIRST = 0x10000000, + POWER_ACTION_LOCK_CONSOLE = 0x20000000, + POWER_ACTION_DISABLE_WAKES = 0x40000000, + POWER_ACTION_CRITICAL = 0x80000000; + +// POWER_ACTION_POLICY.EventCode constants +enum ULONG + POWER_LEVEL_USER_NOTIFY_TEXT = 0x00000001, + POWER_LEVEL_USER_NOTIFY_SOUND = 0x00000002, + POWER_LEVEL_USER_NOTIFY_EXEC = 0x00000004, + POWER_USER_NOTIFY_BUTTON = 0x00000008, + POWER_USER_NOTIFY_SHUTDOWN = 0x00000010, + POWER_FORCE_TRIGGER_RESET = 0x80000000; + +enum size_t + DISCHARGE_POLICY_CRITICAL = 0, + DISCHARGE_POLICY_LOW = 1, + NUM_DISCHARGE_POLICIES = 4; + +enum : BYTE { + PO_THROTTLE_NONE, + PO_THROTTLE_CONSTANT, + PO_THROTTLE_DEGRADE, + PO_THROTTLE_ADAPTIVE, + PO_THROTTLE_MAXIMUM +} + +struct SYSTEM_POWER_LEVEL { + BOOLEAN Enable; + UCHAR[3] Spare; + ULONG BatteryLevel; + POWER_ACTION_POLICY PowerPolicy; + SYSTEM_POWER_STATE MinSystemState; +} +alias SYSTEM_POWER_LEVEL* PSYSTEM_POWER_LEVEL; + +struct SYSTEM_POWER_POLICY { + ULONG Revision; + POWER_ACTION_POLICY PowerButton; + POWER_ACTION_POLICY SleepButton; + POWER_ACTION_POLICY LidClose; + SYSTEM_POWER_STATE LidOpenWake; + ULONG Reserved; + POWER_ACTION_POLICY Idle; + ULONG IdleTimeout; + UCHAR IdleSensitivity; + UCHAR DynamicThrottle; + UCHAR[2] Spare2; + SYSTEM_POWER_STATE MinSleep; + SYSTEM_POWER_STATE MaxSleep; + SYSTEM_POWER_STATE ReducedLatencySleep; + ULONG WinLogonFlags; + ULONG Spare3; + ULONG DozeS4Timeout; + ULONG BroadcastCapacityResolution; + SYSTEM_POWER_LEVEL[NUM_DISCHARGE_POLICIES] DischargePolicy; + ULONG VideoTimeout; + BOOLEAN VideoDimDisplay; + ULONG[3] VideoReserved; + ULONG SpindownTimeout; + BOOLEAN OptimizeForPower; + UCHAR FanThrottleTolerance; + UCHAR ForcedThrottle; + UCHAR MinThrottle; + POWER_ACTION_POLICY OverThrottled; +} +alias SYSTEM_POWER_POLICY* PSYSTEM_POWER_POLICY; + +struct SYSTEM_POWER_CAPABILITIES { + BOOLEAN PowerButtonPresent; + BOOLEAN SleepButtonPresent; + BOOLEAN LidPresent; + BOOLEAN SystemS1; + BOOLEAN SystemS2; + BOOLEAN SystemS3; + BOOLEAN SystemS4; + BOOLEAN SystemS5; + BOOLEAN HiberFilePresent; + BOOLEAN FullWake; + BOOLEAN VideoDimPresent; + BOOLEAN ApmPresent; + BOOLEAN UpsPresent; + BOOLEAN ThermalControl; + BOOLEAN ProcessorThrottle; + UCHAR ProcessorMinThrottle; + UCHAR ProcessorMaxThrottle; + UCHAR[4] spare2; + BOOLEAN DiskSpinDown; + UCHAR[8] spare3; + BOOLEAN SystemBatteriesPresent; + BOOLEAN BatteriesAreShortTerm; + BATTERY_REPORTING_SCALE[3] BatteryScale; + SYSTEM_POWER_STATE AcOnLineWake; + SYSTEM_POWER_STATE SoftLidWake; + SYSTEM_POWER_STATE RtcWake; + SYSTEM_POWER_STATE MinDeviceWakeState; + SYSTEM_POWER_STATE DefaultLowLatencyWake; +} +alias SYSTEM_POWER_CAPABILITIES* PSYSTEM_POWER_CAPABILITIES; + +struct SYSTEM_BATTERY_STATE { + BOOLEAN AcOnLine; + BOOLEAN BatteryPresent; + BOOLEAN Charging; + BOOLEAN Discharging; + BOOLEAN[4] Spare1; + ULONG MaxCapacity; + ULONG RemainingCapacity; + ULONG Rate; + ULONG EstimatedTime; + ULONG DefaultAlert1; + ULONG DefaultAlert2; +} +alias SYSTEM_BATTERY_STATE* PSYSTEM_BATTERY_STATE; + +enum POWER_INFORMATION_LEVEL { + SystemPowerPolicyAc, + SystemPowerPolicyDc, + VerifySystemPolicyAc, + VerifySystemPolicyDc, + SystemPowerCapabilities, + SystemBatteryState, + SystemPowerStateHandler, + ProcessorStateHandler, + SystemPowerPolicyCurrent, + AdministratorPowerPolicy, + SystemReserveHiberFile, + ProcessorInformation, + SystemPowerInformation, + ProcessorStateHandler2, + LastWakeTime, + LastSleepTime, + SystemExecutionState, + SystemPowerStateNotifyHandler, + ProcessorPowerPolicyAc, + ProcessorPowerPolicyDc, + VerifyProcessorPowerPolicyAc, + VerifyProcessorPowerPolicyDc, + ProcessorPowerPolicyCurrent +} + +//#if 1 /* (WIN32_WINNT >= 0x0500) */ +struct SYSTEM_POWER_INFORMATION { + ULONG MaxIdlenessAllowed; + ULONG Idleness; + ULONG TimeRemaining; + UCHAR CoolingMode; +} +alias SYSTEM_POWER_INFORMATION* PSYSTEM_POWER_INFORMATION; +//#endif + +struct PROCESSOR_POWER_POLICY_INFO { + ULONG TimeCheck; + ULONG DemoteLimit; + ULONG PromoteLimit; + UCHAR DemotePercent; + UCHAR PromotePercent; + UCHAR[2] Spare; + uint _bf; + + bool AllowDemotion() { return cast(bool)(_bf & 1); } + bool AllowPromotion()() { return cast(bool)(_bf & 2); } + + bool AllowDemotion(bool a) { _bf = (_bf & ~1) | a; return a; } + bool AllowPromotion(bool a) { _bf = (_bf & ~2) | (a << 1); return a; } +/+ + ULONG AllowDemotion : 1; + ULONG AllowPromotion : 1; + ULONG Reserved : 30; ++/ +} +alias PROCESSOR_POWER_POLICY_INFO* PPROCESSOR_POWER_POLICY_INFO; + +struct PROCESSOR_POWER_POLICY { + ULONG Revision; + UCHAR DynamicThrottle; + UCHAR[3] Spare; + ULONG Reserved; + ULONG PolicyCount; + PROCESSOR_POWER_POLICY_INFO[3] Policy; +} +alias PROCESSOR_POWER_POLICY* PPROCESSOR_POWER_POLICY; + +struct ADMINISTRATOR_POWER_POLICY { + SYSTEM_POWER_STATE MinSleep; + SYSTEM_POWER_STATE MaxSleep; + ULONG MinVideoTimeout; + ULONG MaxVideoTimeout; + ULONG MinSpindownTimeout; + ULONG MaxSpindownTimeout; +} +alias ADMINISTRATOR_POWER_POLICY* PADMINISTRATOR_POWER_POLICY; + +//}//#endif /* _WIN32_WINNT >= 0x500 */ + +extern (Windows) { + alias void function(PVOID, DWORD, PVOID) PIMAGE_TLS_CALLBACK; + + static if (_WIN32_WINNT >= 0x500) { + alias LONG function(PEXCEPTION_POINTERS) PVECTORED_EXCEPTION_HANDLER; + alias void function(PVOID, BOOLEAN) WAITORTIMERCALLBACKFUNC; + } +} + +static if (_WIN32_WINNT >= 0x501) { + enum HEAP_INFORMATION_CLASS { + HeapCompatibilityInformation + } + + enum ACTIVATION_CONTEXT_INFO_CLASS { + ActivationContextBasicInformation = 1, + ActivationContextDetailedInformation, + AssemblyDetailedInformationInActivationContext, + FileInformationInAssemblyOfAssemblyInActivationContext + } + + align struct ACTIVATION_CONTEXT_ASSEMBLY_DETAILED_INFORMATION { + DWORD ulFlags; + DWORD ulEncodedAssemblyIdentityLength; + DWORD ulManifestPathType; + DWORD ulManifestPathLength; + LARGE_INTEGER liManifestLastWriteTime; + DWORD ulPolicyPathType; + DWORD ulPolicyPathLength; + LARGE_INTEGER liPolicyLastWriteTime; + DWORD ulMetadataSatelliteRosterIndex; + DWORD ulManifestVersionMajor; + DWORD ulManifestVersionMinor; + DWORD ulPolicyVersionMajor; + DWORD ulPolicyVersionMinor; + DWORD ulAssemblyDirectoryNameLength; + PCWSTR lpAssemblyEncodedAssemblyIdentity; + PCWSTR lpAssemblyManifestPath; + PCWSTR lpAssemblyPolicyPath; + PCWSTR lpAssemblyDirectoryName; + } + alias ACTIVATION_CONTEXT_ASSEMBLY_DETAILED_INFORMATION* + PACTIVATION_CONTEXT_ASSEMBLY_DETAILED_INFORMATION; + alias const(ACTIVATION_CONTEXT_ASSEMBLY_DETAILED_INFORMATION)* + PCACTIVATION_CONTEXT_ASSEMBLY_DETAILED_INFORMATION; + + struct ACTIVATION_CONTEXT_DETAILED_INFORMATION { + DWORD dwFlags; + DWORD ulFormatVersion; + DWORD ulAssemblyCount; + DWORD ulRootManifestPathType; + DWORD ulRootManifestPathChars; + DWORD ulRootConfigurationPathType; + DWORD ulRootConfigurationPathChars; + DWORD ulAppDirPathType; + DWORD ulAppDirPathChars; + PCWSTR lpRootManifestPath; + PCWSTR lpRootConfigurationPath; + PCWSTR lpAppDirPath; + } + alias ACTIVATION_CONTEXT_DETAILED_INFORMATION* + PACTIVATION_CONTEXT_DETAILED_INFORMATION; + alias const(ACTIVATION_CONTEXT_DETAILED_INFORMATION)* + PCACTIVATION_CONTEXT_DETAILED_INFORMATION; + + struct ACTIVATION_CONTEXT_QUERY_INDEX { + ULONG ulAssemblyIndex; + ULONG ulFileIndexInAssembly; + } + alias ACTIVATION_CONTEXT_QUERY_INDEX* PACTIVATION_CONTEXT_QUERY_INDEX; + alias const(ACTIVATION_CONTEXT_QUERY_INDEX)* PCACTIVATION_CONTEXT_QUERY_INDEX; + + struct ASSEMBLY_FILE_DETAILED_INFORMATION { + DWORD ulFlags; + DWORD ulFilenameLength; + DWORD ulPathLength; + PCWSTR lpFileName; + PCWSTR lpFilePath; + } + alias ASSEMBLY_FILE_DETAILED_INFORMATION* + PASSEMBLY_FILE_DETAILED_INFORMATION; + alias const(ASSEMBLY_FILE_DETAILED_INFORMATION)* + PCASSEMBLY_FILE_DETAILED_INFORMATION; +} + +version (Unicode) { + alias OSVERSIONINFOW OSVERSIONINFO; + alias OSVERSIONINFOEXW OSVERSIONINFOEX; +} else { + alias OSVERSIONINFOA OSVERSIONINFO; + alias OSVERSIONINFOEXA OSVERSIONINFOEX; +} + +alias OSVERSIONINFO* POSVERSIONINFO, LPOSVERSIONINFO; +alias OSVERSIONINFOEX* POSVERSIONINFOEX, LPOSVERSIONINFOEX; + + +static if (_WIN32_WINNT >= 0x500) { + extern (Windows) ULONGLONG VerSetConditionMask(ULONGLONG, DWORD, BYTE); +} + +version (Win64) { +enum WORD IMAGE_NT_OPTIONAL_HDR_MAGIC = IMAGE_NT_OPTIONAL_HDR64_MAGIC; + + alias IMAGE_ORDINAL_FLAG64 IMAGE_ORDINAL_FLAG; + alias IMAGE_SNAP_BY_ORDINAL64 IMAGE_SNAP_BY_ORDINAL; + alias IMAGE_ORDINAL64 IMAGE_ORDINAL; + alias IMAGE_OPTIONAL_HEADER64 IMAGE_OPTIONAL_HEADER; + alias IMAGE_NT_HEADERS64 IMAGE_NT_HEADERS; + alias IMAGE_THUNK_DATA64 IMAGE_THUNK_DATA; + alias IMAGE_TLS_DIRECTORY64 IMAGE_TLS_DIRECTORY; +} else { +enum WORD IMAGE_NT_OPTIONAL_HDR_MAGIC = IMAGE_NT_OPTIONAL_HDR32_MAGIC; + + alias IMAGE_ORDINAL_FLAG32 IMAGE_ORDINAL_FLAG; + alias IMAGE_ORDINAL32 IMAGE_ORDINAL; + alias IMAGE_SNAP_BY_ORDINAL32 IMAGE_SNAP_BY_ORDINAL; + alias IMAGE_OPTIONAL_HEADER32 IMAGE_OPTIONAL_HEADER; + alias IMAGE_NT_HEADERS32 IMAGE_NT_HEADERS; + alias IMAGE_THUNK_DATA32 IMAGE_THUNK_DATA; + alias IMAGE_TLS_DIRECTORY32 IMAGE_TLS_DIRECTORY; +} + +alias IMAGE_OPTIONAL_HEADER* PIMAGE_OPTIONAL_HEADER; +alias IMAGE_NT_HEADERS* PIMAGE_NT_HEADERS; +alias IMAGE_THUNK_DATA* PIMAGE_THUNK_DATA; +alias IMAGE_TLS_DIRECTORY* PIMAGE_TLS_DIRECTORY; + +// TODO: MinGW implements these in assembly. How to translate? +PVOID GetCurrentFiber(); +PVOID GetFiberData(); diff --git a/src/urt/internal/sys/windows/winsock2.d b/src/urt/internal/sys/windows/winsock2.d new file mode 100644 index 0000000..9ea6983 --- /dev/null +++ b/src/urt/internal/sys/windows/winsock2.d @@ -0,0 +1,766 @@ +/* + Written by Christopher E. Miller + $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). +*/ + + +module urt.internal.sys.windows.winsock2; +version (Windows): + +pragma(lib, "ws2_32"); + +extern(Windows): +nothrow: + +alias SOCKET = size_t; +alias socklen_t = int; + +enum SOCKET INVALID_SOCKET = cast(SOCKET)~0; +enum int SOCKET_ERROR = -1; + +enum WSADESCRIPTION_LEN = 256; +enum WSASYS_STATUS_LEN = 128; + +struct WSADATA +{ + ushort wVersion; + ushort wHighVersion; + char[WSADESCRIPTION_LEN + 1] szDescription = 0; + char[WSASYS_STATUS_LEN + 1] szSystemStatus = 0; + ushort iMaxSockets; + ushort iMaxUdpDg; + char* lpVendorInfo; +} +alias LPWSADATA = WSADATA*; + + +enum int IOCPARM_MASK = 0x7F; +enum int IOC_IN = cast(int)0x80000000; +enum int FIONBIO = cast(int)(IOC_IN | ((uint.sizeof & IOCPARM_MASK) << 16) | (102 << 8) | 126); + +enum NI_MAXHOST = 1025; +enum NI_MAXSERV = 32; + +@nogc +{ +int WSAStartup(ushort wVersionRequested, LPWSADATA lpWSAData); +@trusted int WSACleanup(); +@trusted SOCKET socket(int af, int type, int protocol); +int ioctlsocket(SOCKET s, int cmd, uint* argp); +int bind(SOCKET s, const(sockaddr)* name, socklen_t namelen); +int connect(SOCKET s, const(sockaddr)* name, socklen_t namelen); +@trusted int listen(SOCKET s, int backlog); +SOCKET accept(SOCKET s, sockaddr* addr, socklen_t* addrlen); +@trusted int closesocket(SOCKET s); +@trusted int shutdown(SOCKET s, int how); +int getpeername(SOCKET s, sockaddr* name, socklen_t* namelen); +int getsockname(SOCKET s, sockaddr* name, socklen_t* namelen); +int send(SOCKET s, const(void)* buf, int len, int flags); +int sendto(SOCKET s, const(void)* buf, int len, int flags, const(sockaddr)* to, socklen_t tolen); +int recv(SOCKET s, void* buf, int len, int flags); +int recvfrom(SOCKET s, void* buf, int len, int flags, sockaddr* from, socklen_t* fromlen); +int getsockopt(SOCKET s, int level, int optname, void* optval, socklen_t* optlen); +int setsockopt(SOCKET s, int level, int optname, const(void)* optval, socklen_t optlen); +uint inet_addr(const char* cp); +int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* errorfds, const(timeval)* timeout); +char* inet_ntoa(in_addr ina); +hostent* gethostbyname(const char* name); +hostent* gethostbyaddr(const(void)* addr, int len, int type); +protoent* getprotobyname(const char* name); +protoent* getprotobynumber(int number); +servent* getservbyname(const char* name, const char* proto); +servent* getservbyport(int port, const char* proto); +} + +enum: int +{ + NI_NOFQDN = 0x01, + NI_NUMERICHOST = 0x02, + NI_NAMEREQD = 0x04, + NI_NUMERICSERV = 0x08, + NI_DGRAM = 0x10, +} + +@nogc +{ +int gethostname(const char* name, int namelen); +int getaddrinfo(const(char)* nodename, const(char)* servname, const(addrinfo)* hints, addrinfo** res); +void freeaddrinfo(addrinfo* ai); +int getnameinfo(const(sockaddr)* sa, socklen_t salen, char* host, uint hostlen, char* serv, uint servlen, int flags); +} + +enum WSABASEERR = 10000; + +enum: int +{ + /* + * Windows Sockets definitions of regular Microsoft C error constants + */ + WSAEINTR = (WSABASEERR+4), + WSAEBADF = (WSABASEERR+9), + WSAEACCES = (WSABASEERR+13), + WSAEFAULT = (WSABASEERR+14), + WSAEINVAL = (WSABASEERR+22), + WSAEMFILE = (WSABASEERR+24), + + /* + * Windows Sockets definitions of regular Berkeley error constants + */ + WSAEWOULDBLOCK = (WSABASEERR+35), + WSAEINPROGRESS = (WSABASEERR+36), + WSAEALREADY = (WSABASEERR+37), + WSAENOTSOCK = (WSABASEERR+38), + WSAEDESTADDRREQ = (WSABASEERR+39), + WSAEMSGSIZE = (WSABASEERR+40), + WSAEPROTOTYPE = (WSABASEERR+41), + WSAENOPROTOOPT = (WSABASEERR+42), + WSAEPROTONOSUPPORT = (WSABASEERR+43), + WSAESOCKTNOSUPPORT = (WSABASEERR+44), + WSAEOPNOTSUPP = (WSABASEERR+45), + WSAEPFNOSUPPORT = (WSABASEERR+46), + WSAEAFNOSUPPORT = (WSABASEERR+47), + WSAEADDRINUSE = (WSABASEERR+48), + WSAEADDRNOTAVAIL = (WSABASEERR+49), + WSAENETDOWN = (WSABASEERR+50), + WSAENETUNREACH = (WSABASEERR+51), + WSAENETRESET = (WSABASEERR+52), + WSAECONNABORTED = (WSABASEERR+53), + WSAECONNRESET = (WSABASEERR+54), + WSAENOBUFS = (WSABASEERR+55), + WSAEISCONN = (WSABASEERR+56), + WSAENOTCONN = (WSABASEERR+57), + WSAESHUTDOWN = (WSABASEERR+58), + WSAETOOMANYREFS = (WSABASEERR+59), + WSAETIMEDOUT = (WSABASEERR+60), + WSAECONNREFUSED = (WSABASEERR+61), + WSAELOOP = (WSABASEERR+62), + WSAENAMETOOLONG = (WSABASEERR+63), + WSAEHOSTDOWN = (WSABASEERR+64), + WSAEHOSTUNREACH = (WSABASEERR+65), + WSAENOTEMPTY = (WSABASEERR+66), + WSAEPROCLIM = (WSABASEERR+67), + WSAEUSERS = (WSABASEERR+68), + WSAEDQUOT = (WSABASEERR+69), + WSAESTALE = (WSABASEERR+70), + WSAEREMOTE = (WSABASEERR+71), + + /* + * Extended Windows Sockets error constant definitions + */ + WSASYSNOTREADY = (WSABASEERR+91), + WSAVERNOTSUPPORTED = (WSABASEERR+92), + WSANOTINITIALISED = (WSABASEERR+93), + + /* Authoritative Answer: Host not found */ + WSAHOST_NOT_FOUND = (WSABASEERR+1001), + HOST_NOT_FOUND = WSAHOST_NOT_FOUND, + + /* Non-Authoritative: Host not found, or SERVERFAIL */ + WSATRY_AGAIN = (WSABASEERR+1002), + TRY_AGAIN = WSATRY_AGAIN, + + /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */ + WSANO_RECOVERY = (WSABASEERR+1003), + NO_RECOVERY = WSANO_RECOVERY, + + /* Valid name, no data record of requested type */ + WSANO_DATA = (WSABASEERR+1004), + NO_DATA = WSANO_DATA, + + /* no address, look for MX record */ + WSANO_ADDRESS = WSANO_DATA, + NO_ADDRESS = WSANO_ADDRESS +} + +/* + * Windows Sockets errors redefined as regular Berkeley error constants + */ +enum: int +{ + EWOULDBLOCK = WSAEWOULDBLOCK, + EINPROGRESS = WSAEINPROGRESS, + EALREADY = WSAEALREADY, + ENOTSOCK = WSAENOTSOCK, + EDESTADDRREQ = WSAEDESTADDRREQ, + EMSGSIZE = WSAEMSGSIZE, + EPROTOTYPE = WSAEPROTOTYPE, + ENOPROTOOPT = WSAENOPROTOOPT, + EPROTONOSUPPORT = WSAEPROTONOSUPPORT, + ESOCKTNOSUPPORT = WSAESOCKTNOSUPPORT, + EOPNOTSUPP = WSAEOPNOTSUPP, + EPFNOSUPPORT = WSAEPFNOSUPPORT, + EAFNOSUPPORT = WSAEAFNOSUPPORT, + EADDRINUSE = WSAEADDRINUSE, + EADDRNOTAVAIL = WSAEADDRNOTAVAIL, + ENETDOWN = WSAENETDOWN, + ENETUNREACH = WSAENETUNREACH, + ENETRESET = WSAENETRESET, + ECONNABORTED = WSAECONNABORTED, + ECONNRESET = WSAECONNRESET, + ENOBUFS = WSAENOBUFS, + EISCONN = WSAEISCONN, + ENOTCONN = WSAENOTCONN, + ESHUTDOWN = WSAESHUTDOWN, + ETOOMANYREFS = WSAETOOMANYREFS, + ETIMEDOUT = WSAETIMEDOUT, + ECONNREFUSED = WSAECONNREFUSED, + ELOOP = WSAELOOP, + ENAMETOOLONG = WSAENAMETOOLONG, + EHOSTDOWN = WSAEHOSTDOWN, + EHOSTUNREACH = WSAEHOSTUNREACH, + ENOTEMPTY = WSAENOTEMPTY, + EPROCLIM = WSAEPROCLIM, + EUSERS = WSAEUSERS, + EDQUOT = WSAEDQUOT, + ESTALE = WSAESTALE, + EREMOTE = WSAEREMOTE +} + +enum: int +{ + EAI_NONAME = WSAHOST_NOT_FOUND, +} + +int WSAGetLastError() @nogc @trusted; + + +enum: int +{ + AF_UNSPEC = 0, + + AF_UNIX = 1, + AF_INET = 2, + AF_IMPLINK = 3, + AF_PUP = 4, + AF_CHAOS = 5, + AF_NS = 6, + AF_IPX = AF_NS, + AF_ISO = 7, + AF_OSI = AF_ISO, + AF_ECMA = 8, + AF_DATAKIT = 9, + AF_CCITT = 10, + AF_SNA = 11, + AF_DECnet = 12, + AF_DLI = 13, + AF_LAT = 14, + AF_HYLINK = 15, + AF_APPLETALK = 16, + AF_NETBIOS = 17, + AF_VOICEVIEW = 18, + AF_FIREFOX = 19, + AF_UNKNOWN1 = 20, + AF_BAN = 21, + AF_ATM = 22, + AF_INET6 = 23, + AF_CLUSTER = 24, + AF_12844 = 25, + AF_IRDA = 26, + AF_NETDES = 28, + + AF_MAX = 29, + + + PF_UNSPEC = AF_UNSPEC, + + PF_UNIX = AF_UNIX, + PF_INET = AF_INET, + PF_IMPLINK = AF_IMPLINK, + PF_PUP = AF_PUP, + PF_CHAOS = AF_CHAOS, + PF_NS = AF_NS, + PF_IPX = AF_IPX, + PF_ISO = AF_ISO, + PF_OSI = AF_OSI, + PF_ECMA = AF_ECMA, + PF_DATAKIT = AF_DATAKIT, + PF_CCITT = AF_CCITT, + PF_SNA = AF_SNA, + PF_DECnet = AF_DECnet, + PF_DLI = AF_DLI, + PF_LAT = AF_LAT, + PF_HYLINK = AF_HYLINK, + PF_APPLETALK = AF_APPLETALK, + PF_VOICEVIEW = AF_VOICEVIEW, + PF_FIREFOX = AF_FIREFOX, + PF_UNKNOWN1 = AF_UNKNOWN1, + PF_BAN = AF_BAN, + PF_INET6 = AF_INET6, + + PF_MAX = AF_MAX, +} + + +enum: int +{ + SOL_SOCKET = 0xFFFF, +} + + +enum: int +{ + SO_DEBUG = 0x0001, + SO_ACCEPTCONN = 0x0002, + SO_REUSEADDR = 0x0004, + SO_KEEPALIVE = 0x0008, + SO_DONTROUTE = 0x0010, + SO_BROADCAST = 0x0020, + SO_USELOOPBACK = 0x0040, + SO_LINGER = 0x0080, + SO_DONTLINGER = ~SO_LINGER, + SO_OOBINLINE = 0x0100, + SO_SNDBUF = 0x1001, + SO_RCVBUF = 0x1002, + SO_SNDLOWAT = 0x1003, + SO_RCVLOWAT = 0x1004, + SO_SNDTIMEO = 0x1005, + SO_RCVTIMEO = 0x1006, + SO_ERROR = 0x1007, + SO_TYPE = 0x1008, + SO_EXCLUSIVEADDRUSE = ~SO_REUSEADDR, + + TCP_NODELAY = 1, + + IP_OPTIONS = 1, + + IP_HDRINCL = 2, + IP_TOS = 3, + IP_TTL = 4, + IP_MULTICAST_IF = 9, + IP_MULTICAST_TTL = 10, + IP_MULTICAST_LOOP = 11, + IP_ADD_MEMBERSHIP = 12, + IP_DROP_MEMBERSHIP = 13, + IP_DONTFRAGMENT = 14, + IP_ADD_SOURCE_MEMBERSHIP = 15, + IP_DROP_SOURCE_MEMBERSHIP = 16, + IP_BLOCK_SOURCE = 17, + IP_UNBLOCK_SOURCE = 18, + IP_PKTINFO = 19, + + IPV6_UNICAST_HOPS = 4, + IPV6_MULTICAST_IF = 9, + IPV6_MULTICAST_HOPS = 10, + IPV6_MULTICAST_LOOP = 11, + IPV6_ADD_MEMBERSHIP = 12, + IPV6_DROP_MEMBERSHIP = 13, + IPV6_JOIN_GROUP = IPV6_ADD_MEMBERSHIP, + IPV6_LEAVE_GROUP = IPV6_DROP_MEMBERSHIP, + IPV6_V6ONLY = 27, +} + + +/// Default FD_SETSIZE value. +/// In C/C++, it is redefinable by #define-ing the macro before #include-ing +/// winsock.h. In D, use the $(D FD_CREATE) function to allocate a $(D fd_set) +/// of an arbitrary size. +enum int FD_SETSIZE = 64; + + +struct fd_set_custom(uint SETSIZE) +{ + uint fd_count; + SOCKET[SETSIZE] fd_array; +} + +alias fd_set = fd_set_custom!FD_SETSIZE; + +// Removes. +void FD_CLR(SOCKET fd, fd_set* set) pure @nogc +{ + uint c = set.fd_count; + SOCKET* start = set.fd_array.ptr; + SOCKET* stop = start + c; + + for (; start != stop; start++) + { + if (*start == fd) + goto found; + } + return; //not found + + found: + for (++start; start != stop; start++) + { + *(start - 1) = *start; + } + + set.fd_count = c - 1; +} + + +// Tests. +int FD_ISSET(SOCKET fd, const(fd_set)* set) pure @nogc +{ +const(SOCKET)* start = set.fd_array.ptr; +const(SOCKET)* stop = start + set.fd_count; + + for (; start != stop; start++) + { + if (*start == fd) + return true; + } + return false; +} + + +// Adds. +void FD_SET(SOCKET fd, fd_set* set) pure @nogc +{ + uint c = set.fd_count; + set.fd_array.ptr[c] = fd; + set.fd_count = c + 1; +} + + +// Resets to zero. +void FD_ZERO(fd_set* set) pure @nogc +{ + set.fd_count = 0; +} + + +/// Creates a new $(D fd_set) with the specified capacity. +fd_set* FD_CREATE(uint capacity) pure +{ + // Take into account alignment (SOCKET may be 64-bit and require 64-bit alignment on 64-bit systems) + size_t size = (fd_set_custom!1).sizeof - SOCKET.sizeof + (SOCKET.sizeof * capacity); + auto data = new ubyte[size]; + auto set = cast(fd_set*)data.ptr; + FD_ZERO(set); + return set; +} + +struct linger +{ + ushort l_onoff; + ushort l_linger; +} + + +struct protoent +{ + char* p_name; + char** p_aliases; + short p_proto; +} + + +struct servent +{ + char* s_name; + char** s_aliases; + + version (Win64) + { + char* s_proto; + short s_port; + } + else version (Win32) + { + short s_port; + char* s_proto; + } +} + + +/+ +union in6_addr +{ + private union _u_t + { + ubyte[16] Byte; + ushort[8] Word; + } + _u_t u; +} + + +struct in_addr6 +{ + ubyte[16] s6_addr; +} ++/ + +@safe pure @nogc +{ + ushort htons(ushort x); + uint htonl(uint x); + ushort ntohs(ushort x); + uint ntohl(uint x); +} + + +enum: int +{ + SOCK_STREAM = 1, + SOCK_DGRAM = 2, + SOCK_RAW = 3, + SOCK_RDM = 4, + SOCK_SEQPACKET = 5, +} + + +enum: int +{ + IPPROTO_IP = 0, + IPPROTO_ICMP = 1, + IPPROTO_IGMP = 2, + IPPROTO_GGP = 3, + IPPROTO_TCP = 6, + IPPROTO_PUP = 12, + IPPROTO_UDP = 17, + IPPROTO_IDP = 22, + IPPROTO_IPV6 = 41, + IPPROTO_ND = 77, + IPPROTO_RAW = 255, + + IPPROTO_MAX = 256, +} + + +enum: int +{ + MSG_OOB = 0x1, + MSG_PEEK = 0x2, + MSG_DONTROUTE = 0x4 +} + + +enum: int +{ + SD_RECEIVE = 0, + SD_SEND = 1, + SD_BOTH = 2, +} + + +enum: uint +{ + INADDR_ANY = 0, + INADDR_LOOPBACK = 0x7F000001, + INADDR_BROADCAST = 0xFFFFFFFF, + INADDR_NONE = 0xFFFFFFFF, + ADDR_ANY = INADDR_ANY, +} + + +enum: int +{ + AI_PASSIVE = 0x1, // Socket address will be used in bind() call + AI_CANONNAME = 0x2, // Return canonical name in first ai_canonname + AI_NUMERICHOST = 0x4, // Nodename must be a numeric address string + AI_NUMERICSERV = 0x8, // Servicename must be a numeric port number + AI_DNS_ONLY = 0x10, // Restrict queries to unicast DNS only (no LLMNR, netbios, etc.) + AI_FORCE_CLEAR_TEXT = 0x20, // Force clear text DNS query + AI_BYPASS_DNS_CACHE = 0x40, // Bypass DNS cache + AI_RETURN_TTL = 0x80, // Return record TTL + AI_ALL = 0x0100, // Query both IP6 and IP4 with AI_V4MAPPED + AI_ADDRCONFIG = 0x0400, // Resolution only if global address configured + AI_V4MAPPED = 0x0800, // On v6 failure, query v4 and convert to V4MAPPED format + AI_NON_AUTHORITATIVE = 0x04000, // LUP_NON_AUTHORITATIVE + AI_SECURE = 0x08000, // LUP_SECURE + AI_RETURN_PREFERRED_NAMES = 0x010000, // LUP_RETURN_PREFERRED_NAMES + AI_FQDN = 0x00020000, // Return the FQDN in ai_canonname + AI_FILESERVER = 0x00040000, // Resolving fileserver name resolution + AI_DISABLE_IDN_ENCODING = 0x00080000, // Disable Internationalized Domain Names handling + AI_SECURE_WITH_FALLBACK = 0x00100000, // Forces clear text fallback if the secure DNS query fails + AI_EXCLUSIVE_CUSTOM_SERVERS = 0x00200000, // Use exclusively the custom DNS servers + AI_RETURN_RESPONSE_FLAGS = 0x10000000, // Requests extra information about the DNS results + AI_REQUIRE_SECURE = 0x20000000, // Forces the DNS query to be done over seucre protocols + AI_RESOLUTION_HANDLE = 0x40000000, // Request resolution handle + AI_EXTENDED = 0x80000000, // Indicates this is extended ADDRINFOEX(2/..) struct +} + + +struct timeval +{ + int tv_sec; + int tv_usec; +} + + +union in_addr +{ + private union _S_un_t + { + private struct _S_un_b_t + { + ubyte s_b1, s_b2, s_b3, s_b4; + } + _S_un_b_t S_un_b; + + private struct _S_un_w_t + { + ushort s_w1, s_w2; + } + _S_un_w_t S_un_w; + + uint S_addr; + } + _S_un_t S_un; + + uint s_addr; + + struct + { + ubyte s_net, s_host; + + union + { + ushort s_imp; + + struct + { + ubyte s_lh, s_impno; + } + } + } +} + + +union in6_addr +{ + private union _in6_u_t + { + ubyte[16] u6_addr8; + ushort[8] u6_addr16; + uint[4] u6_addr32; + } + _in6_u_t in6_u; + + ubyte[16] s6_addr8; + ushort[8] s6_addr16; + uint[4] s6_addr32; + + alias s6_addr = s6_addr8; +} + + +enum in6_addr IN6ADDR_ANY = { s6_addr8: [0] }; +enum in6_addr IN6ADDR_LOOPBACK = { s6_addr8: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] }; +//alias IN6ADDR_ANY_INIT = IN6ADDR_ANY; +//alias IN6ADDR_LOOPBACK_INIT = IN6ADDR_LOOPBACK; + +enum int INET_ADDRSTRLEN = 16; +enum int INET6_ADDRSTRLEN = 46; + + + + +struct sockaddr +{ + short sa_family; + ubyte[14] sa_data; +} +alias sockaddr SOCKADDR; +alias SOCKADDR* PSOCKADDR, LPSOCKADDR; + +struct sockaddr_storage +{ + short ss_family; + char[6] __ss_pad1 = void; + long __ss_align; + char[112] __ss_pad2 = void; +} +alias sockaddr_storage SOCKADDR_STORAGE; +alias SOCKADDR_STORAGE* PSOCKADDR_STORAGE; + +struct sockaddr_in +{ + short sin_family = AF_INET; + ushort sin_port; + in_addr sin_addr; + ubyte[8] sin_zero; +} +alias sockaddr_in SOCKADDR_IN; +alias SOCKADDR_IN* PSOCKADDR_IN, LPSOCKADDR_IN; + + +struct sockaddr_in6 +{ + short sin6_family = AF_INET6; + ushort sin6_port; + uint sin6_flowinfo; + in6_addr sin6_addr; + uint sin6_scope_id; +} + + +struct addrinfo +{ + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + size_t ai_addrlen; + char* ai_canonname; + sockaddr* ai_addr; + addrinfo* ai_next; +} + + +struct hostent +{ + char* h_name; + char** h_aliases; + short h_addrtype; + short h_length; + char** h_addr_list; + + + char* h_addr() @safe pure nothrow @nogc + { + return h_addr_list[0]; + } +} + +// Note: These are Winsock2!! +struct WSAOVERLAPPED; +alias LPWSAOVERLAPPED = WSAOVERLAPPED*; +alias LPWSAOVERLAPPED_COMPLETION_ROUTINE = void function(uint, uint, LPWSAOVERLAPPED, uint) nothrow @nogc; +int WSAIoctl(SOCKET s, uint dwIoControlCode, + void* lpvInBuffer, uint cbInBuffer, + void* lpvOutBuffer, uint cbOutBuffer, + uint* lpcbBytesReturned, + LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) @nogc; + + +enum IOC_VENDOR = 0x18000000; +enum SIO_KEEPALIVE_VALS = IOC_IN | IOC_VENDOR | 4; + +/* Argument structure for SIO_KEEPALIVE_VALS */ +struct tcp_keepalive +{ + uint onoff; + uint keepalivetime; + uint keepaliveinterval; +} + + +struct pollfd +{ + SOCKET fd; // Socket handle + short events; // Requested events to monitor + short revents; // Returned events indicating status +} +alias WSAPOLLFD = pollfd; +alias PWSAPOLLFD = pollfd*; +alias LPWSAPOLLFD = pollfd*; + +enum: short { + POLLRDNORM = 0x0100, + POLLRDBAND = 0x0200, + POLLIN = (POLLRDNORM | POLLRDBAND), + POLLPRI = 0x0400, + + POLLWRNORM = 0x0010, + POLLOUT = (POLLWRNORM), + POLLWRBAND = 0x0020, + + POLLERR = 0x0001, + POLLHUP = 0x0002, + POLLNVAL = 0x0004 +} + +int WSAPoll(LPWSAPOLLFD fdArray, uint fds, int timeout) @nogc; diff --git a/src/urt/internal/sys/windows/winuser.d b/src/urt/internal/sys/windows/winuser.d new file mode 100644 index 0000000..0f524d5 --- /dev/null +++ b/src/urt/internal/sys/windows/winuser.d @@ -0,0 +1,144 @@ +/// Minimal winuser bindings — only virtual key codes. +/// Full winuser.d was trimmed; GUI/display/font APIs removed. +module urt.internal.sys.windows.winuser; +version (Windows): + +enum { + VK_LBUTTON = 0x01, + VK_RBUTTON = 0x02, + VK_CANCEL = 0x03, + VK_MBUTTON = 0x04, + VK_XBUTTON1 = 0x05, + VK_XBUTTON2 = 0x06, + VK_BACK = 0x08, + VK_TAB = 0x09, + VK_CLEAR = 0x0C, + VK_RETURN = 0x0D, + VK_SHIFT = 0x10, + VK_CONTROL = 0x11, + VK_MENU = 0x12, + VK_PAUSE = 0x13, + VK_CAPITAL = 0x14, + VK_KANA = 0x15, + VK_HANGEUL = 0x15, + VK_HANGUL = 0x15, + VK_JUNJA = 0x17, + VK_FINAL = 0x18, + VK_HANJA = 0x19, + VK_KANJI = 0x19, + VK_ESCAPE = 0x1B, + VK_CONVERT = 0x1C, + VK_NONCONVERT = 0x1D, + VK_ACCEPT = 0x1E, + VK_MODECHANGE = 0x1F, + VK_SPACE = 0x20, + VK_PRIOR = 0x21, + VK_NEXT = 0x22, + VK_END = 0x23, + VK_HOME = 0x24, + VK_LEFT = 0x25, + VK_UP = 0x26, + VK_RIGHT = 0x27, + VK_DOWN = 0x28, + VK_SELECT = 0x29, + VK_PRINT = 0x2A, + VK_EXECUTE = 0x2B, + VK_SNAPSHOT = 0x2C, + VK_INSERT = 0x2D, + VK_DELETE = 0x2E, + VK_HELP = 0x2F, + VK_LWIN = 0x5B, + VK_RWIN = 0x5C, + VK_APPS = 0x5D, + VK_SLEEP = 0x5F, + VK_NUMPAD0 = 0x60, + VK_NUMPAD1 = 0x61, + VK_NUMPAD2 = 0x62, + VK_NUMPAD3 = 0x63, + VK_NUMPAD4 = 0x64, + VK_NUMPAD5 = 0x65, + VK_NUMPAD6 = 0x66, + VK_NUMPAD7 = 0x67, + VK_NUMPAD8 = 0x68, + VK_NUMPAD9 = 0x69, + VK_MULTIPLY = 0x6A, + VK_ADD = 0x6B, + VK_SEPARATOR = 0x6C, + VK_SUBTRACT = 0x6D, + VK_DECIMAL = 0x6E, + VK_DIVIDE = 0x6F, + VK_F1 = 0x70, + VK_F2 = 0x71, + VK_F3 = 0x72, + VK_F4 = 0x73, + VK_F5 = 0x74, + VK_F6 = 0x75, + VK_F7 = 0x76, + VK_F8 = 0x77, + VK_F9 = 0x78, + VK_F10 = 0x79, + VK_F11 = 0x7A, + VK_F12 = 0x7B, + VK_F13 = 0x7C, + VK_F14 = 0x7D, + VK_F15 = 0x7E, + VK_F16 = 0x7F, + VK_F17 = 0x80, + VK_F18 = 0x81, + VK_F19 = 0x82, + VK_F20 = 0x83, + VK_F21 = 0x84, + VK_F22 = 0x85, + VK_F23 = 0x86, + VK_F24 = 0x87, + VK_NUMLOCK = 0x90, + VK_SCROLL = 0x91, + VK_LSHIFT = 0xA0, + VK_RSHIFT = 0xA1, + VK_LCONTROL = 0xA2, + VK_RCONTROL = 0xA3, + VK_LMENU = 0xA4, + VK_RMENU = 0xA5, + VK_BROWSER_BACK = 0xA6, + VK_BROWSER_FORWARD = 0xA7, + VK_BROWSER_REFRESH = 0xA8, + VK_BROWSER_STOP = 0xA9, + VK_BROWSER_SEARCH = 0xAA, + VK_BROWSER_FAVORITES = 0xAB, + VK_BROWSER_HOME = 0xAC, + VK_VOLUME_MUTE = 0xAD, + VK_VOLUME_DOWN = 0xAE, + VK_VOLUME_UP = 0xAF, + VK_MEDIA_NEXT_TRACK = 0xB0, + VK_MEDIA_PREV_TRACK = 0xB1, + VK_MEDIA_STOP = 0xB2, + VK_MEDIA_PLAY_PAUSE = 0xB3, + VK_LAUNCH_MAIL = 0xB4, + VK_LAUNCH_MEDIA_SELECT = 0xB5, + VK_LAUNCH_APP1 = 0xB6, + VK_LAUNCH_APP2 = 0xB7, + VK_OEM_1 = 0xBA, + VK_OEM_PLUS = 0xBB, + VK_OEM_COMMA = 0xBC, + VK_OEM_MINUS = 0xBD, + VK_OEM_PERIOD = 0xBE, + VK_OEM_2 = 0xBF, + VK_OEM_3 = 0xC0, + VK_OEM_4 = 0xDB, + VK_OEM_5 = 0xDC, + VK_OEM_6 = 0xDD, + VK_OEM_7 = 0xDE, + VK_OEM_8 = 0xDF, + VK_OEM_102 = 0xE2, + VK_PROCESSKEY = 0xE5, + VK_PACKET = 0xE7, + VK_ATTN = 0xF6, + VK_CRSEL = 0xF7, + VK_EXSEL = 0xF8, + VK_EREOF = 0xF9, + VK_PLAY = 0xFA, + VK_ZOOM = 0xFB, + VK_NONAME = 0xFC, + VK_PA1 = 0xFD, + VK_OEM_CLEAR = 0xFE, +} diff --git a/src/urt/internal/traits.d b/src/urt/internal/traits.d new file mode 100644 index 0000000..cf52442 --- /dev/null +++ b/src/urt/internal/traits.d @@ -0,0 +1,1227 @@ +// TODO: THIS NEEDS TO BE DISSOLVED... +module urt.internal.traits; + +import urt.traits; + +template Fields(T) +{ + static if (is(T == struct) || is(T == union)) + alias Fields = typeof(T.tupleof[0 .. $ - __traits(isNested, T)]); + else static if (is(T == class) || is(T == interface)) + alias Fields = typeof(T.tupleof); + else + alias Fields = AliasSeq!T; +} + +T trustedCast(T, U)(auto ref U u) @trusted pure nothrow +{ + return cast(T)u; +} + +template BaseElemOf(T) +{ + static if (is(OriginalType!T == E[N], E, size_t N)) + alias BaseElemOf = BaseElemOf!E; + else + alias BaseElemOf = T; +} + +unittest +{ + static assert(is(BaseElemOf!(int) == int)); + static assert(is(BaseElemOf!(int[1]) == int)); + static assert(is(BaseElemOf!(int[1][2]) == int)); + static assert(is(BaseElemOf!(int[1][]) == int[1][])); + static assert(is(BaseElemOf!(int[][1]) == int[])); + enum E : int[2]{ test = [0, 1] } + static assert(is(BaseElemOf!(E) == int)); +} + +// [For internal use] +template ModifyTypePreservingTQ(alias Modifier, T) +{ + static if (is(T U == immutable U)) alias ModifyTypePreservingTQ = immutable Modifier!U; + else static if (is(T U == shared inout const U)) alias ModifyTypePreservingTQ = shared inout const Modifier!U; + else static if (is(T U == shared inout U)) alias ModifyTypePreservingTQ = shared inout Modifier!U; + else static if (is(T U == shared const U)) alias ModifyTypePreservingTQ = shared const Modifier!U; + else static if (is(T U == shared U)) alias ModifyTypePreservingTQ = shared Modifier!U; + else static if (is(T U == inout const U)) alias ModifyTypePreservingTQ = inout const Modifier!U; + else static if (is(T U == inout U)) alias ModifyTypePreservingTQ = inout Modifier!U; + else static if (is(T U == const U)) alias ModifyTypePreservingTQ = const Modifier!U; + else alias ModifyTypePreservingTQ = Modifier!T; +} +@safe unittest +{ + alias Intify(T) = int; + static assert(is(ModifyTypePreservingTQ!(Intify, real) == int)); + static assert(is(ModifyTypePreservingTQ!(Intify, const real) == const int)); + static assert(is(ModifyTypePreservingTQ!(Intify, inout real) == inout int)); + static assert(is(ModifyTypePreservingTQ!(Intify, inout const real) == inout const int)); + static assert(is(ModifyTypePreservingTQ!(Intify, shared real) == shared int)); + static assert(is(ModifyTypePreservingTQ!(Intify, shared const real) == shared const int)); + static assert(is(ModifyTypePreservingTQ!(Intify, shared inout real) == shared inout int)); + static assert(is(ModifyTypePreservingTQ!(Intify, shared inout const real) == shared inout const int)); + static assert(is(ModifyTypePreservingTQ!(Intify, immutable real) == immutable int)); +} + +// Substitute all `inout` qualifiers that appears in T to `const` +template substInout(T) +{ + static if (is(T == immutable)) + { + alias substInout = T; + } + else static if (is(T : shared const U, U) || is(T : const U, U)) + { + // U is top-unqualified + mixin("alias substInout = " + ~ (is(T == shared) ? "shared " : "") + ~ (is(T == const) || is(T == inout) ? "const " : "") // substitute inout to const + ~ "substInoutForm!U;"); + } + else + static assert(0); +} + +private template substInoutForm(T) +{ + static if (is(T == struct) || is(T == class) || is(T == union) || is(T == interface)) + { + alias substInoutForm = T; // prevent matching to the form of alias-this-ed type + } + else static if (is(T == V[K], K, V)) alias substInoutForm = substInout!V[substInout!K]; + else static if (is(T == U[n], U, size_t n)) alias substInoutForm = substInout!U[n]; + else static if (is(T == U[], U)) alias substInoutForm = substInout!U[]; + else static if (is(T == U*, U)) alias substInoutForm = substInout!U*; + else alias substInoutForm = T; +} + +unittest +{ + // https://github.com/dlang/dmd/issues/21452 + struct S { int x; } + struct T { int x; alias x this; } + + enum EnumInt { a = 123 } + enum EnumUInt : uint { a = 123 } + enum EnumFloat : float { a = 123 } + enum EnumString : string { a = "123" } + enum EnumStringW : wstring { a = "123" } + enum EnumStruct : S { a = S(7) } + enum EnumAliasThis : T { a = T(7) } + enum EnumDArray : int[] { a = [1] } + enum EnumAArray : int[int] { a = [0 : 1] } + + static assert(substInout!(EnumInt).stringof == "EnumInt"); + static assert(substInout!(inout(EnumUInt)).stringof == "const(EnumUInt)"); + static assert(substInout!(EnumFloat).stringof == "EnumFloat"); + static assert(substInout!(EnumString).stringof == "EnumString"); + static assert(substInout!(inout(EnumStringW)).stringof == "const(EnumStringW)"); + static assert(substInout!(EnumStruct).stringof == "EnumStruct"); + static assert(substInout!(EnumAliasThis).stringof == "EnumAliasThis"); + static assert(substInout!(EnumDArray).stringof == "EnumDArray"); + static assert(substInout!(inout(EnumAArray)[int]).stringof == "const(EnumAArray)[int]"); +} + +/// used to declare an extern(D) function that is defined in a different module +template externDFunc(string fqn, T:FT*, FT) if (is(FT == function)) +{ + static if (is(FT RT == return) && is(FT Args == function)) + { + import core.demangle : mangleFunc; + enum decl = { + string s = "extern(D) RT externDFunc(Args)"; + foreach (attr; __traits(getFunctionAttributes, FT)) + s ~= " " ~ attr; + return s ~ ";"; + }(); + pragma(mangle, mangleFunc!T(fqn)) mixin(decl); + } + else + static assert(0); +} + +template staticIota(int beg, int end) +{ + static if (beg + 1 >= end) + { + static if (beg >= end) + { + alias staticIota = AliasSeq!(); + } + else + { + alias staticIota = AliasSeq!(+beg); + } + } + else + { + enum mid = beg + (end - beg) / 2; + alias staticIota = AliasSeq!(staticIota!(beg, mid), staticIota!(mid, end)); + } +} + +private struct __InoutWorkaroundStruct {} +@property T rvalueOf(T)(T val) { return val; } +@property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); +@property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); + +// taken from std.traits.isAssignable +template isAssignable(Lhs, Rhs = Lhs) +{ + enum isAssignable = __traits(compiles, lvalueOf!Lhs = rvalueOf!Rhs) && __traits(compiles, lvalueOf!Lhs = lvalueOf!Rhs); +} + +// taken from std.traits.isInnerClass +template isInnerClass(T) if (is(T == class)) +{ + static if (is(typeof(T.outer))) + { + template hasOuterMember(T...) + { + static if (T.length == 0) + enum hasOuterMember = false; + else + enum hasOuterMember = T[0] == "outer" || hasOuterMember!(T[1 .. $]); + } + enum isInnerClass = __traits(isSame, typeof(T.outer), __traits(parent, T)) && !hasOuterMember!(__traits(allMembers, T)); + } + else + enum isInnerClass = false; +} + +template dtorIsNothrow(T) +{ + enum dtorIsNothrow = is(typeof(function{T t=void;}) : void function() nothrow); +} + +// taken from std.meta.allSatisfy +template allSatisfy(alias F, T...) +{ + static foreach (Ti; T) + { + static if (!is(typeof(allSatisfy) == bool) && // not yet defined + !F!(Ti)) + { + enum allSatisfy = false; + } + } + static if (!is(typeof(allSatisfy) == bool)) // if not yet defined + { + enum allSatisfy = true; + } +} + +// taken from std.meta.anySatisfy +template anySatisfy(alias F, Ts...) +{ + static foreach (T; Ts) + { + static if (!is(typeof(anySatisfy) == bool) && // not yet defined + F!T) + { + enum anySatisfy = true; + } + } + static if (!is(typeof(anySatisfy) == bool)) // if not yet defined + { + enum anySatisfy = false; + } +} + +// simplified from std.traits.maxAlignment +template maxAlignment(Ts...) +if (Ts.length > 0) +{ + enum maxAlignment = + { + size_t result = 0; + static foreach (T; Ts) + if (T.alignof > result) result = T.alignof; + return result; + }(); +} + +template classInstanceAlignment(T) +if (is(T == class)) +{ + enum classInstanceAlignment = __traits(classInstanceAlignment, T); +} + +/// See $(REF hasElaborateMove, std,traits) +template hasElaborateMove(S) +{ + static if (__traits(isStaticArray, S)) + { + enum bool hasElaborateMove = S.sizeof && hasElaborateMove!(BaseElemOf!S); + } + else static if (is(S == struct)) + { + enum hasElaborateMove = (is(typeof(S.init.opPostMove(lvalueOf!S))) && + !is(typeof(S.init.opPostMove(rvalueOf!S)))) || + anySatisfy!(.hasElaborateMove, Fields!S); + } + else + { + enum bool hasElaborateMove = false; + } +} + +// std.traits.hasElaborateDestructor +template hasElaborateDestructor(S) +{ + static if (__traits(isStaticArray, S)) + { + enum bool hasElaborateDestructor = S.sizeof && hasElaborateDestructor!(BaseElemOf!S); + } + else static if (is(S == struct)) + { + // Once https://issues.dlang.org/show_bug.cgi?id=24865 is fixed, then + // this should be the implementation, but until that's fixed, we need the + // uncommented code. + // enum hasElaborateDestructor = __traits(hasMember, S, "__xdtor"); + + enum hasElaborateDestructor = hasDtor([__traits(allMembers, S)]); + } + else + { + enum bool hasElaborateDestructor = false; + } +} + +private bool hasDtor(string[] members) +{ + foreach (name; members) + { + if (name == "__xdtor") + return true; + } + + return false; +} + +@safe unittest +{ + static struct NoDestructor {} + static assert(!hasElaborateDestructor!NoDestructor); + static assert(!hasElaborateDestructor!(NoDestructor[42])); + static assert(!hasElaborateDestructor!(NoDestructor[0])); + static assert(!hasElaborateDestructor!(NoDestructor[])); + + static struct HasDestructor { ~this() {} } + static assert( hasElaborateDestructor!HasDestructor); + static assert( hasElaborateDestructor!(HasDestructor[42])); + static assert(!hasElaborateDestructor!(HasDestructor[0])); + static assert(!hasElaborateDestructor!(HasDestructor[])); + + static struct HasDestructor2 { HasDestructor s; } + static assert( hasElaborateDestructor!HasDestructor2); + static assert( hasElaborateDestructor!(HasDestructor2[42])); + static assert(!hasElaborateDestructor!(HasDestructor2[0])); + static assert(!hasElaborateDestructor!(HasDestructor2[])); + + static class HasFinalizer { ~this() {} } + static assert(!hasElaborateDestructor!HasFinalizer); + + static struct HasUnion { union { HasDestructor s; } } + static assert(!hasElaborateDestructor!HasUnion); + static assert(!hasElaborateDestructor!(HasUnion[42])); + static assert(!hasElaborateDestructor!(HasUnion[0])); + static assert(!hasElaborateDestructor!(HasUnion[])); + + static assert(!hasElaborateDestructor!int); + static assert(!hasElaborateDestructor!(int[0])); + static assert(!hasElaborateDestructor!(int[42])); + static assert(!hasElaborateDestructor!(int[])); +} + +// https://issues.dlang.org/show_bug.cgi?id=24865 +@safe unittest +{ + static struct S2 { ~this() {} } + static struct S3 { S2 field; } + static struct S6 { S3[0] field; } + + static assert( hasElaborateDestructor!S2); + static assert( hasElaborateDestructor!S3); + static assert(!hasElaborateDestructor!S6); +} + +// std.traits.hasElaborateCopyDestructor +template hasElaborateCopyConstructor(S) +{ + static if (__traits(isStaticArray, S)) + { + enum bool hasElaborateCopyConstructor = S.sizeof && hasElaborateCopyConstructor!(BaseElemOf!S); + } + else static if (is(S == struct)) + { + enum hasElaborateCopyConstructor = __traits(hasCopyConstructor, S) || __traits(hasPostblit, S); + } + else + { + enum bool hasElaborateCopyConstructor = false; + } +} + +@safe unittest +{ + static struct S + { + int x; + this(return scope ref typeof(this) rhs) { } + this(int x, int y) {} + } + + static assert( hasElaborateCopyConstructor!S); + static assert(!hasElaborateCopyConstructor!(S[0][1])); + + static struct S2 + { + int x; + this(int x, int y) {} + } + + static assert(!hasElaborateCopyConstructor!S2); + + static struct S3 + { + int x; + this(return scope ref typeof(this) rhs, int x = 42) { } + this(int x, int y) {} + } + + static assert( hasElaborateCopyConstructor!S3); + + static struct S4 { union { S s; } } + + static assert(!hasElaborateCopyConstructor!S4); +} + +template hasElaborateAssign(S) +{ + static if (__traits(isStaticArray, S)) + { + enum bool hasElaborateAssign = S.sizeof && hasElaborateAssign!(BaseElemOf!S); + } + else static if (is(S == struct)) + { + enum hasElaborateAssign = is(typeof(S.init.opAssign(rvalueOf!S))) || + is(typeof(S.init.opAssign(lvalueOf!S))); + } + else + { + enum bool hasElaborateAssign = false; + } +} + +unittest +{ + { + static struct S {} + static assert(!hasElaborateAssign!S); + static assert(!hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct S { int i; } + static assert(!hasElaborateAssign!S); + static assert(!hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct S { void opAssign(S) {} } + static assert( hasElaborateAssign!S); + static assert( hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct S { void opAssign(ref S) {} } + static assert( hasElaborateAssign!S); + static assert( hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct S { void opAssign(int) {} } + static assert(!hasElaborateAssign!S); + static assert(!hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct S { this(this) {} } + static assert( hasElaborateAssign!S); + static assert( hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + // https://issues.dlang.org/show_bug.cgi?id=24834 + /+ + { + static struct S { this(ref S) {} } + static assert( hasElaborateAssign!S); + static assert( hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + +/ + { + static struct S { ~this() {} } + static assert( hasElaborateAssign!S); + static assert( hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct S { @disable void opAssign(S); } + static assert(!hasElaborateAssign!S); + static assert(!hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct Member {} + static struct S { Member member; } + static assert(!hasElaborateAssign!S); + static assert(!hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct Member { void opAssign(Member) {} } + static struct S { Member member; } + static assert( hasElaborateAssign!S); + static assert( hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct Member {} + static struct S { Member member; void opAssign(S) {} } + static assert( hasElaborateAssign!S); + static assert( hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct Member { @disable void opAssign(Member); } + static struct S { Member member; } + static assert(!hasElaborateAssign!S); + static assert(!hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct Member { @disable void opAssign(Member); } + static struct S { Member member; void opAssign(S) {} } + static assert( hasElaborateAssign!S); + static assert( hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct Member { void opAssign(Member) {} } + static struct S { Member member; @disable void opAssign(S); } + static assert(!hasElaborateAssign!S); + static assert(!hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct Member { void opAssign(Member) {} } + static struct S { union { Member member; } } + static assert(!hasElaborateAssign!S); + static assert(!hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + + static assert(!hasElaborateAssign!int); + static assert(!hasElaborateAssign!(string[])); + static assert(!hasElaborateAssign!Object); +} + +template hasIndirections(T) +{ + static if (is(T == enum)) + enum hasIndirections = hasIndirections!(OriginalType!T); + else static if (is(T == struct) || is(T == union)) + enum hasIndirections = anySatisfy!(.hasIndirections, typeof(T.tupleof)); + else static if (__traits(isAssociativeArray, T) || is(T == class) || is(T == interface)) + enum hasIndirections = true; + else static if (is(T == E[N], E, size_t N)) + enum hasIndirections = T.sizeof && (is(immutable E == immutable void) || hasIndirections!(BaseElemOf!E)); + else static if (isFunctionPointer!T) + enum hasIndirections = false; + else + enum hasIndirections = isPointer!T || isDelegate!T || isDynamicArray!T; +} + +@safe unittest +{ + static assert(!hasIndirections!int); + static assert(!hasIndirections!(const int)); + + static assert( hasIndirections!(int*)); + static assert( hasIndirections!(const int*)); + + static assert( hasIndirections!(int[])); + static assert(!hasIndirections!(int[42])); + static assert(!hasIndirections!(int[0])); + + static assert( hasIndirections!(int*)); + static assert( hasIndirections!(int*[])); + static assert( hasIndirections!(int*[42])); + static assert(!hasIndirections!(int*[0])); + + static assert( hasIndirections!string); + static assert( hasIndirections!(string[])); + static assert( hasIndirections!(string[42])); + static assert(!hasIndirections!(string[0])); + + static assert( hasIndirections!(void[])); + static assert( hasIndirections!(void[17])); + static assert(!hasIndirections!(void[0])); + + static assert( hasIndirections!(string[int])); + static assert( hasIndirections!(string[int]*)); + static assert( hasIndirections!(string[int][])); + static assert( hasIndirections!(string[int][12])); + static assert(!hasIndirections!(string[int][0])); + + static assert(!hasIndirections!(int function(string))); + static assert( hasIndirections!(int delegate(string))); + static assert(!hasIndirections!(const(int function(string)))); + static assert( hasIndirections!(const(int delegate(string)))); + static assert(!hasIndirections!(immutable(int function(string)))); + static assert( hasIndirections!(immutable(int delegate(string)))); + + static class C {} + static assert( hasIndirections!C); + + static interface I {} + static assert( hasIndirections!I); + + { + enum E : int { a } + static assert(!hasIndirections!E); + } + { + enum E : int* { a } + static assert( hasIndirections!E); + } + { + enum E : string { a = "" } + static assert( hasIndirections!E); + } + { + enum E : int[] { a = null } + static assert( hasIndirections!E); + } + { + enum E : int[3] { a = [1, 2, 3] } + static assert(!hasIndirections!E); + } + { + enum E : int*[3] { a = [null, null, null] } + static assert( hasIndirections!E); + } + { + enum E : int*[0] { a = int*[0].init } + static assert(!hasIndirections!E); + } + { + enum E : C { a = null } + static assert( hasIndirections!E); + } + { + enum E : I { a = null } + static assert( hasIndirections!E); + } + + { + static struct S {} + static assert(!hasIndirections!S); + + enum E : S { a = S.init } + static assert(!hasIndirections!S); + } + { + static struct S { int i; } + static assert(!hasIndirections!S); + + enum E : S { a = S.init } + static assert(!hasIndirections!S); + } + { + static struct S { C c; } + static assert( hasIndirections!S); + + enum E : S { a = S.init } + static assert( hasIndirections!S); + } + { + static struct S { int[] arr; } + static assert( hasIndirections!S); + + enum E : S { a = S.init } + static assert( hasIndirections!S); + } + { + int local; + struct S { void foo() { ++local; } } + static assert( hasIndirections!S); + + enum E : S { a = S.init } + static assert( hasIndirections!S); + } + + { + static union U {} + static assert(!hasIndirections!U); + } + { + static union U { int i; } + static assert(!hasIndirections!U); + } + { + static union U { C c; } + static assert( hasIndirections!U); + } + { + static union U { int[] arr; } + static assert( hasIndirections!U); + } +} + +// https://issues.dlang.org/show_bug.cgi?id=12000 +@safe unittest +{ + static struct S(T) + { + static assert(hasIndirections!T); + } + + static class A(T) + { + S!A a; + } + + A!int dummy; +} + +// https://github.com/dlang/dmd/issues/20812 +@safe unittest +{ + static assert(!hasIndirections!void); + static assert(!hasIndirections!(const void)); + static assert(!hasIndirections!(inout void)); + static assert(!hasIndirections!(immutable void)); + static assert(!hasIndirections!(shared void)); + + static assert( hasIndirections!(void*)); + static assert( hasIndirections!(const void*)); + static assert( hasIndirections!(inout void*)); + static assert( hasIndirections!(immutable void*)); + static assert( hasIndirections!(shared void*)); + + static assert( hasIndirections!(void[])); + static assert( hasIndirections!(const void[])); + static assert( hasIndirections!(inout void[])); + static assert( hasIndirections!(immutable void[])); + static assert( hasIndirections!(shared void[])); + + static assert( hasIndirections!(void[42])); + static assert( hasIndirections!(const void[42])); + static assert( hasIndirections!(inout void[42])); + static assert( hasIndirections!(immutable void[42])); + static assert( hasIndirections!(shared void[42])); + + static assert(!hasIndirections!(void[0])); + static assert(!hasIndirections!(const void[0])); + static assert(!hasIndirections!(inout void[0])); + static assert(!hasIndirections!(immutable void[0])); + static assert(!hasIndirections!(shared void[0])); +} + +template hasUnsharedIndirections(T) +{ + static if (is(T == immutable)) + enum hasUnsharedIndirections = false; + else static if (is(T == struct) || is(T == union)) + enum hasUnsharedIndirections = anySatisfy!(.hasUnsharedIndirections, Fields!T); + else static if (is(T : E[N], E, size_t N)) + enum hasUnsharedIndirections = is(E == void) ? false : hasUnsharedIndirections!E; + else static if (isFunctionPointer!T) + enum hasUnsharedIndirections = false; + else static if (isPointer!T) + enum hasUnsharedIndirections = !is(T : shared(U)*, U) && !is(T : immutable(U)*, U); + else static if (isDynamicArray!T) + enum hasUnsharedIndirections = !is(T : shared(V)[], V) && !is(T : immutable(V)[], V); + else static if (is(T == class) || is(T == interface)) + enum hasUnsharedIndirections = !is(T : shared(W), W); + else + enum hasUnsharedIndirections = isDelegate!T || __traits(isAssociativeArray, T); // TODO: how to handle these? +} + +unittest +{ + static struct Foo { shared(int)* val; } + + static assert(!hasUnsharedIndirections!(immutable(char)*)); + static assert(!hasUnsharedIndirections!(string)); + + static assert(!hasUnsharedIndirections!(Foo)); + static assert( hasUnsharedIndirections!(Foo*)); + static assert(!hasUnsharedIndirections!(shared(Foo)*)); + static assert(!hasUnsharedIndirections!(immutable(Foo)*)); + + int local; + struct HasContextPointer { int opCall() { return ++local; } } + static assert(hasIndirections!HasContextPointer); +} + +enum bool isAggregateType(T) = is(T == struct) || is(T == union) || + is(T == class) || is(T == interface); + +enum bool isPointer(T) = is(T == U*, U) && !isAggregateType!T; + +enum bool isDynamicArray(T) = is(DynamicArrayTypeOf!T) && !isAggregateType!T; + +template OriginalType(T) +{ + template Impl(T) + { + static if (is(T U == enum)) alias Impl = OriginalType!U; + else alias Impl = T; + } + + alias OriginalType = ModifyTypePreservingTQ!(Impl, T); +} + +template DynamicArrayTypeOf(T) +{ + static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT)) + alias X = DynamicArrayTypeOf!AT; + else + alias X = OriginalType!T; + + static if (is(Unqual!X : E[], E) && !is(typeof({ enum n = X.length; }))) + alias DynamicArrayTypeOf = X; + else + static assert(0, T.stringof ~ " is not a dynamic array"); +} + +private template AliasThisTypeOf(T) + if (isAggregateType!T) +{ + alias members = __traits(getAliasThis, T); + + static if (members.length == 1) + alias AliasThisTypeOf = typeof(__traits(getMember, T.init, members[0])); + else + static assert(0, T.stringof~" does not have alias this type"); +} + +template isFunctionPointer(T...) + if (T.length == 1) +{ + static if (is(T[0] U) || is(typeof(T[0]) U)) + { + static if (is(U F : F*) && is(F == function)) + enum bool isFunctionPointer = true; + else + enum bool isFunctionPointer = false; + } + else + enum bool isFunctionPointer = false; +} + +template isDelegate(T...) + if (T.length == 1) +{ + static if (is(typeof(& T[0]) U : U*) && is(typeof(& T[0]) U == delegate)) + { + // T is a (nested) function symbol. + enum bool isDelegate = true; + } + else static if (is(T[0] W) || is(typeof(T[0]) W)) + { + // T is an expression or a type. Take the type of it and examine. + enum bool isDelegate = is(W == delegate); + } + else + enum bool isDelegate = false; +} + +// std.meta.Filter +template Filter(alias pred, TList...) +{ + static if (TList.length == 0) + { + alias Filter = AliasSeq!(); + } + else static if (TList.length == 1) + { + static if (pred!(TList[0])) + alias Filter = AliasSeq!(TList[0]); + else + alias Filter = AliasSeq!(); + } + /* The next case speeds up compilation by reducing + * the number of Filter instantiations + */ + else static if (TList.length == 2) + { + static if (pred!(TList[0])) + { + static if (pred!(TList[1])) + alias Filter = AliasSeq!(TList[0], TList[1]); + else + alias Filter = AliasSeq!(TList[0]); + } + else + { + static if (pred!(TList[1])) + alias Filter = AliasSeq!(TList[1]); + else + alias Filter = AliasSeq!(); + } + } + else + { + alias Filter = + AliasSeq!( + Filter!(pred, TList[ 0 .. $/2]), + Filter!(pred, TList[$/2 .. $ ])); + } +} + +// std.meta.staticMap +template staticMap(alias F, T...) +{ + static if (T.length == 0) + { + alias staticMap = AliasSeq!(); + } + else static if (T.length == 1) + { + alias staticMap = AliasSeq!(F!(T[0])); + } + /* Cases 2 to 8 improve compile performance by reducing + * the number of recursive instantiations of staticMap + */ + else static if (T.length == 2) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1])); + } + else static if (T.length == 3) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2])); + } + else static if (T.length == 4) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3])); + } + else static if (T.length == 5) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4])); + } + else static if (T.length == 6) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5])); + } + else static if (T.length == 7) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5]), F!(T[6])); + } + else static if (T.length == 8) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5]), F!(T[6]), F!(T[7])); + } + else + { + alias staticMap = + AliasSeq!( + staticMap!(F, T[ 0 .. $/2]), + staticMap!(F, T[$/2 .. $ ])); + } +} + +// std.exception.assertCTFEable +version (CoreUnittest) package(core) +void assertCTFEable(alias dg)() +{ + static assert({ cast(void) dg(); return true; }()); + cast(void) dg(); +} + +// std.traits.FunctionTypeOf +/* +Get the function type from a callable object `func`. + +Using builtin `typeof` on a property function yields the types of the +property value, not of the property function itself. Still, +`FunctionTypeOf` is able to obtain function types of properties. + +Note: +Do not confuse function types with function pointer types; function types are +usually used for compile-time reflection purposes. + */ +template FunctionTypeOf(func...) +if (func.length == 1 /*&& isCallable!func*/) +{ + static if (is(typeof(& func[0]) Fsym : Fsym*) && is(Fsym == function) || is(typeof(& func[0]) Fsym == delegate)) + { + alias FunctionTypeOf = Fsym; // HIT: (nested) function symbol + } + else static if (is(typeof(& func[0].opCall) Fobj == delegate)) + { + alias FunctionTypeOf = Fobj; // HIT: callable object + } + else static if (is(typeof(& func[0].opCall) Ftyp : Ftyp*) && is(Ftyp == function)) + { + alias FunctionTypeOf = Ftyp; // HIT: callable type + } + else static if (is(func[0] T) || is(typeof(func[0]) T)) + { + static if (is(T == function)) + alias FunctionTypeOf = T; // HIT: function + else static if (is(T Fptr : Fptr*) && is(Fptr == function)) + alias FunctionTypeOf = Fptr; // HIT: function pointer + else static if (is(T Fdlg == delegate)) + alias FunctionTypeOf = Fdlg; // HIT: delegate + else + static assert(0); + } + else + static assert(0); +} + +@safe unittest +{ + class C + { + int value() @property { return 0; } + } + static assert(is( typeof(C.value) == int )); + static assert(is( FunctionTypeOf!(C.value) == function )); +} + +// Disabled: uses `new Callable` (GC allocation) which is unsupported in @nogc uRT build +version (none) +@system unittest +{ + int test(int a); + int propGet() @property; + int propSet(int a) @property; + int function(int) test_fp; + int delegate(int) test_dg; + static assert(is( typeof(test) == FunctionTypeOf!(typeof(test)) )); + static assert(is( typeof(test) == FunctionTypeOf!test )); + static assert(is( typeof(test) == FunctionTypeOf!test_fp )); + static assert(is( typeof(test) == FunctionTypeOf!test_dg )); + alias int GetterType() @property; + alias int SetterType(int) @property; + static assert(is( FunctionTypeOf!propGet == GetterType )); + static assert(is( FunctionTypeOf!propSet == SetterType )); + + interface Prop { int prop() @property; } + Prop prop; + static assert(is( FunctionTypeOf!(Prop.prop) == GetterType )); + static assert(is( FunctionTypeOf!(prop.prop) == GetterType )); + + class Callable { int opCall(int) { return 0; } } + auto call = new Callable; + static assert(is( FunctionTypeOf!call == typeof(test) )); + + struct StaticCallable { static int opCall(int) { return 0; } } + StaticCallable stcall_val; + StaticCallable* stcall_ptr; + static assert(is( FunctionTypeOf!stcall_val == typeof(test) )); + static assert(is( FunctionTypeOf!stcall_ptr == typeof(test) )); + + interface Overloads + { + void test(string); + real test(real); + int test(int); + int test() @property; + } + alias ov = __traits(getVirtualMethods, Overloads, "test"); + alias F_ov0 = FunctionTypeOf!(ov[0]); + alias F_ov1 = FunctionTypeOf!(ov[1]); + alias F_ov2 = FunctionTypeOf!(ov[2]); + alias F_ov3 = FunctionTypeOf!(ov[3]); + static assert(is(F_ov0* == void function(string))); + static assert(is(F_ov1* == real function(real))); + static assert(is(F_ov2* == int function(int))); + static assert(is(F_ov3* == int function() @property)); + + alias F_dglit = FunctionTypeOf!((int a){ return a; }); + static assert(is(F_dglit* : int function(int))); +} + +// std.traits.ReturnType +/* +Get the type of the return value from a function, +a pointer to function, a delegate, a struct +with an opCall, a pointer to a struct with an opCall, +or a class with an `opCall`. Please note that $(D_KEYWORD ref) +is not part of a type, but the attribute of the function +(see template $(LREF functionAttributes)). +*/ +template ReturnType(func...) +if (func.length == 1 /*&& isCallable!func*/) +{ + static if (is(FunctionTypeOf!func R == return)) + alias ReturnType = R; + else + static assert(0, "argument has no return type"); +} + +// +@safe unittest +{ + int foo(); + ReturnType!foo x; // x is declared as int +} + +@safe unittest +{ + struct G + { + int opCall (int i) { return 1;} + } + + alias ShouldBeInt = ReturnType!G; + static assert(is(ShouldBeInt == int)); + + G g; + static assert(is(ReturnType!g == int)); + + G* p; + alias pg = ReturnType!p; + static assert(is(pg == int)); + + class C + { + int opCall (int i) { return 1;} + } + + static assert(is(ReturnType!C == int)); + + C c; + static assert(is(ReturnType!c == int)); + + class Test + { + int prop() @property { return 0; } + } + alias R_Test_prop = ReturnType!(Test.prop); + static assert(is(R_Test_prop == int)); + + alias R_dglit = ReturnType!((int a) { return a; }); + static assert(is(R_dglit == int)); +} + +// std.traits.Parameters +/* +Get, as a tuple, the types of the parameters to a function, a pointer +to function, a delegate, a struct with an `opCall`, a pointer to a +struct with an `opCall`, or a class with an `opCall`. +*/ +template Parameters(func...) +if (func.length == 1 /*&& isCallable!func*/) +{ + static if (is(FunctionTypeOf!func P == function)) + alias Parameters = P; + else + static assert(0, "argument has no parameters"); +} + +// +@safe unittest +{ + int foo(int, long); + void bar(Parameters!foo); // declares void bar(int, long); + void abc(Parameters!foo[1]); // declares void abc(long); +} + +@safe unittest +{ + int foo(int i, bool b) { return 0; } + static assert(is(Parameters!foo == AliasSeq!(int, bool))); + static assert(is(Parameters!(typeof(&foo)) == AliasSeq!(int, bool))); + + struct S { real opCall(real r, int i) { return 0.0; } } + S s; + static assert(is(Parameters!S == AliasSeq!(real, int))); + static assert(is(Parameters!(S*) == AliasSeq!(real, int))); + static assert(is(Parameters!s == AliasSeq!(real, int))); + + class Test + { + int prop() @property { return 0; } + } + alias P_Test_prop = Parameters!(Test.prop); + static assert(P_Test_prop.length == 0); + + alias P_dglit = Parameters!((int a){}); + static assert(P_dglit.length == 1); + static assert(is(P_dglit[0] == int)); +} + +// Return `true` if `Type` has `member` that evaluates to `true` in a static if condition +enum isTrue(Type, string member) = __traits(compiles, { static if (__traits(getMember, Type, member)) {} else static assert(0); }); + +unittest +{ + static struct T + { + enum a = true; + enum b = false; + enum c = 1; + enum d = 45; + enum e = "true"; + enum f = ""; + enum g = null; + alias h = bool; + } + + static assert( isTrue!(T, "a")); + static assert(!isTrue!(T, "b")); + static assert( isTrue!(T, "c")); + static assert( isTrue!(T, "d")); + static assert( isTrue!(T, "e")); + static assert( isTrue!(T, "f")); + static assert(!isTrue!(T, "g")); + static assert(!isTrue!(T, "h")); +} + +template hasUDA(alias symbol, alias attribute) +{ + enum isAttr(T) = is(T == attribute); + + enum hasUDA = anySatisfy!(isAttr, __traits(getAttributes, symbol)); +} + +unittest +{ + enum SomeUDA; + + struct Test + { + int woUDA; + @SomeUDA int oneUDA; + @SomeUDA @SomeUDA int twoUDAs; + } + + static assert(hasUDA!(Test.oneUDA, SomeUDA)); + static assert(hasUDA!(Test.twoUDAs, SomeUDA)); + static assert(!hasUDA!(Test.woUDA, SomeUDA)); +} diff --git a/src/urt/io.d b/src/urt/io.d index 73fba66..58b19df 100644 --- a/src/urt/io.d +++ b/src/urt/io.d @@ -1,20 +1,47 @@ module urt.io; -import core.stdc.stdio; - - nothrow @nogc: -int write(const(char)[] str) +enum WriteTarget : ubyte { - return printf("%.*s", cast(int)str.length, str.ptr); + stdout = 0, + stderr = 1, + debugstring = 2, // Windows OutputDebugStringA } -int writeln(const(char)[] str) + +int write_to(WriteTarget target, bool newline = false)(const(char)[] str) { - return printf("%.*s\n", cast(int)str.length, str.ptr); + static if (target == WriteTarget.stdout) + { + import urt.internal.stdc; + return printf("%.*s" ~ (newline ? "\n" : ""), cast(int)str.length, str.ptr); + } + else static if (target == WriteTarget.stderr) + { + import urt.internal.stdc; + return fprintf(stderr, "%.*s" ~ (newline ? "\n" : ""), cast(int)str.length, str.ptr); + } + else static if (target == WriteTarget.debugstring) + { + version (Windows) + { + import core.sys.windows.windows; + OutputDebugStringA(str.ptr); + static if (newline) + OutputDebugStringA("\n"); + return cast(int)str.length + newline; + } + else + { + // is stderr the best analogy on other platforms? + return write_to!(WriteTarget.stderr, newline)(str); + } + } + else + static assert(0, "Invalid WriteTarget"); } -int write(Args...)(ref Args args) +int write_to(WriteTarget target, bool newline = false, Args...)(ref Args args) if (Args.length != 1 || !is(Args[0] : const(char)[])) { import urt.string.format; @@ -22,19 +49,10 @@ int write(Args...)(ref Args args) size_t len = concat(null, args).length; const(char)[] t = concat(cast(char[])talloc(len), args); - return write(t); -} - -int writeln(Args...)(ref Args args) - if (Args.length != 1 || !is(Args[0] : const(char)[])) -{ - import urt.string.format; - import urt.mem.temp; - - return tconcat(args).writeln; + return write_to!(target, newline)(t); } -int writef(Args...)(const(char)[] fmt, ref Args args) +int writef_to(WriteTarget target, bool newline = false, Args...)(const(char)[] fmt, ref Args args) if (Args.length > 0) { import urt.string.format; @@ -42,19 +60,27 @@ int writef(Args...)(const(char)[] fmt, ref Args args) size_t len = format(null, fmt, args).length; const(char)[] t = format(cast(char[])talloc(len), fmt, args); - return write(t); + return write_to!(target, newline)(t); } -int writelnf(Args...)(const(char)[] fmt, ref Args args) - if (Args.length > 0) -{ - import urt.string.format; - import urt.mem.temp; +alias write = write_to!(WriteTarget.stdout, false); +alias writeln = write_to!(WriteTarget.stdout, true); +alias write_err = write_to!(WriteTarget.stderr, false); +alias writeln_err = write_to!(WriteTarget.stderr, true); +alias write_debug = write_to!(WriteTarget.debugstring, false); +alias writeln_debug = write_to!(WriteTarget.debugstring, true); - size_t len = format(null, fmt, args).length; - const(char)[] t = format(cast(char[])talloc(len), fmt, args); - return writeln(t); -} +int write(Args...)(ref Args args) + if (Args.length != 1 || !is(Args[0] : const(char)[])) + => write_to!(WriteTarget.stdout, false)(args); +int writeln(Args...)(ref Args args) + if (Args.length != 1 || !is(Args[0] : const(char)[])) + => write_to!(WriteTarget.stdout, true)(args); + +int writef(Args...)(ref Args args) + => writef_to!(WriteTarget.stdout, false)(args); +int writelnf(Args...)(ref Args args) + => writef_to!(WriteTarget.stdout, true)(args); unittest { diff --git a/src/urt/lifetime.d b/src/urt/lifetime.d index 586d9db..d08eb32 100644 --- a/src/urt/lifetime.d +++ b/src/urt/lifetime.d @@ -18,7 +18,7 @@ T* emplace(T, Args...)(T* chunk, auto ref Args args) T emplace(T, Args...)(T chunk, auto ref Args args) if (is(T == class)) { - import core.internal.traits : isInnerClass; + import urt.internal.traits : isInnerClass; static assert(!__traits(isAbstractClass, T), T.stringof ~ " is abstract and it can't be emplaced"); @@ -83,7 +83,7 @@ T* emplace(T, Args...)(void[] chunk, auto ref Args args) void copyEmplace(S, T)(ref S source, ref T target) @system if (is(immutable S == immutable T)) { - import core.internal.traits : BaseElemOf, hasElaborateCopyConstructor, Unconst, Unqual; + import urt.internal.traits : BaseElemOf, hasElaborateCopyConstructor, Unconst, Unqual; // cannot have the following as simple template constraint due to nested-struct special case... static if (!__traits(compiles, (ref S src) { T tgt = src; })) @@ -95,7 +95,7 @@ void copyEmplace(S, T)(ref S source, ref T target) @system void blit() { - import core.stdc.string : memcpy; + import urt.mem : memcpy; memcpy(cast(Unqual!(T)*) &target, cast(Unqual!(T)*) &source, T.sizeof); } @@ -198,7 +198,7 @@ T move(T)(return scope ref T source) nothrow @nogc private void moveImpl(T)(scope ref T target, return scope ref T source) nothrow @nogc { - import core.internal.traits : hasElaborateDestructor; + import urt.internal.traits : hasElaborateDestructor; static if (is(T == struct)) { @@ -220,7 +220,7 @@ private T moveImpl(T)(return scope ref T source) nothrow @nogc return trustedMoveImpl(source); } -private T trustedMoveImpl(T)(return scope ref T source) @trusted nothrow @nogc +private T trustedMoveImpl(T)(return scope ref T source) nothrow @nogc @trusted { T result = void; moveEmplaceImpl(result, source); @@ -237,7 +237,7 @@ private enum bool hasContextPointers(T) = { } else static if (is(T == struct)) { - import core.internal.traits : anySatisfy; + import urt.internal.traits : anySatisfy; return __traits(isNested, T) || anySatisfy!(hasContextPointers, typeof(T.tupleof)); } else return false; @@ -254,8 +254,8 @@ private void moveEmplaceImpl(T)(scope ref T target, return scope ref T source) @ // "Cannot move object with internal pointer unless `opPostMove` is defined."); // } - import core.internal.traits : hasElaborateAssign, isAssignable, hasElaborateMove, - hasElaborateDestructor, hasElaborateCopyConstructor; + import urt.internal.traits : hasElaborateAssign, isAssignable, hasElaborateMove, + hasElaborateDestructor, hasElaborateCopyConstructor; static if (is(T == struct)) { @@ -346,7 +346,7 @@ template _d_delstructImpl(T) * `@trusted` until the implementation can be brought up to modern D * expectations. */ - void _d_delstruct(ref T p) @trusted @nogc pure nothrow + void _d_delstruct(ref T p) nothrow @nogc pure @trusted { if (p) { @@ -378,7 +378,7 @@ template _d_delstructImpl(T) // wipes source after moving pragma(inline, true) -private void wipe(T, Init...)(return scope ref T source, ref const scope Init initializer) @trusted nothrow @nogc +private void wipe(T, Init...)(return scope ref T source, ref const scope Init initializer) nothrow @nogc @trusted if (!Init.length || ((Init.length == 1) && (is(immutable T == immutable Init[0])))) { @@ -392,7 +392,7 @@ if (!Init.length || } else static if (is(T == struct) && hasContextPointers!T) { - import core.internal.traits : anySatisfy; + import urt.internal.traits : anySatisfy; static if (anySatisfy!(hasContextPointers, typeof(T.tupleof))) { static foreach (i; 0 .. T.tupleof.length - __traits(isNested, T)) @@ -416,7 +416,7 @@ if (!Init.length || } else { - import core.internal.traits : hasElaborateAssign, isAssignable; + import urt.internal.traits : hasElaborateAssign, isAssignable; static if (Init.length) { static if (hasElaborateAssign!T || !isAssignable!T) @@ -435,7 +435,7 @@ if (!Init.length || T _d_newclassT(T)() @trusted if (is(T == class)) { - import core.internal.traits : hasIndirections; + import urt.internal.traits : hasIndirections; import core.exception : onOutOfMemoryError; import core.memory : pureMalloc; import core.memory : GC; @@ -511,7 +511,7 @@ T _d_newclassTTrace(T)(string file, int line, string funcname) @trusted T* _d_newitemT(T)() @trusted { import core.internal.lifetime : emplaceInitializer; - import core.internal.traits : hasIndirections; + import urt.internal.traits : hasIndirections; import core.memory : GC; auto flags = !hasIndirections!T ? GC.BlkAttr.NO_SCAN : GC.BlkAttr.NONE; @@ -557,7 +557,7 @@ version (D_ProfileGC) template TypeInfoSize(T) { - import core.internal.traits : hasElaborateDestructor; + import urt.internal.traits : hasElaborateDestructor; enum TypeInfoSize = hasElaborateDestructor!T ? size_t.sizeof : 0; } +/ diff --git a/src/urt/math.d b/src/urt/math.d index ff63a20..e92a83c 100644 --- a/src/urt/math.d +++ b/src/urt/math.d @@ -63,7 +63,7 @@ extern(C) double exp(double x); double log(double x); double acos(double x); - double pow(double x, double e); +// double pow(double x, double e); } int float_is_integer(double f, out ulong i) @@ -146,6 +146,68 @@ unittest assert(float_is_integer(-200, i) == -1 && cast(long)i == -200); } +auto pow(B, E)(B base, E exp) @trusted +{ + enum isFloatB = is(B == float) || is(B == double) || is(B == real); + enum isFloatE = is(E == float) || is(E == double) || is(E == real); + + static if (isFloatB) + { + // Floating-point base + B result = cast(B) 1.0; + B b = base; + + static if (isFloatE) + { + // F ^^ F — handle integer-valued exponents (covers 99% of + // real-world `^^` uses: value^^2, 10.0^^e, etc.) + if (exp == 0) return cast(B) 1.0; + long iexp = cast(long) exp; + if (cast(E) iexp == exp) + return _powfi!(B)(b, iexp); + // True non-integer exponent: not supported without libm. + assert(false, "Non-integer float exponent needs libm"); + } + else + { + // F ^^ I — binary exponentiation + return _powfi!(B)(b, cast(long) exp); + } + } + else + { + // I ^^ I — integer power + if (exp == 0) return cast(B) 1; + B result = cast(B) 1; + B b = base; + auto e = cast(ulong) exp; + while (e > 0) + { + if (e & 1) + result *= b; + b *= b; + e >>= 1; + } + return result; + } +} +// binary exponentiation: float base, integer exponent. +private F _powfi(F)(F base, long exp) @trusted +{ + if (exp == 0) return cast(F) 1.0; + bool neg = exp < 0; + ulong e = neg ? cast(ulong)(-exp) : cast(ulong) exp; + F result = cast(F) 1.0; + while (e > 0) + { + if (e & 1) + result *= base; + base *= base; + e >>= 1; + } + return neg ? cast(F) 1.0 / result : result; +} + pragma(inline, true) bool addc(T = uint)(T a, T b, out T r, bool c_in) { diff --git a/src/urt/mem/alloc.d b/src/urt/mem/alloc.d index a87c8dd..3f8f362 100644 --- a/src/urt/mem/alloc.d +++ b/src/urt/mem/alloc.d @@ -1,6 +1,6 @@ module urt.mem.alloc; -import core.stdc.stdlib; +import urt.internal.stdc; nothrow @nogc: @@ -57,7 +57,7 @@ void[] alloc_aligned(size_t size, size_t alignment) nothrow @nogc void[] realloc(void[] mem, size_t newSize) nothrow @nogc { // TODO: we might pin the length to a debug table somewhere... - return core.stdc.stdlib.realloc(mem.ptr, newSize)[0 .. newSize]; + return urt.mem.realloc(mem.ptr, newSize)[0 .. newSize]; } void[] realloc_aligned(void[] mem, size_t newSize, size_t alignment) nothrow @nogc @@ -104,7 +104,7 @@ void free(void[] mem) nothrow @nogc // maybe check the length passed to free matches the alloc? // ... or you know, just don't do that. - core.stdc.stdlib.free(mem.ptr); + urt.mem.free(mem.ptr); } void free_aligned(void[] mem) nothrow @nogc @@ -114,10 +114,10 @@ void free_aligned(void[] mem) nothrow @nogc if (mem.ptr is null) return; void* p = (cast(void**)mem.ptr)[-1]; - core.stdc.stdlib.free(p); + urt.mem.free(p); } else - core.stdc.stdlib.free(mem.ptr); + urt.mem.free(mem.ptr); } size_t memsize(void* ptr) nothrow @nogc diff --git a/src/urt/mem/allocator.d b/src/urt/mem/allocator.d index 2d3f295..f6a54ca 100644 --- a/src/urt/mem/allocator.d +++ b/src/urt/mem/allocator.d @@ -434,7 +434,10 @@ unittest } } - Allocator a = new Mallocator; + import urt.util; + auto mallocator = InPlace!Mallocator(Default); + + Allocator a = mallocator; S* s = a.allocT!S(10); a.freeT(s); C c = a.allocT!C(); diff --git a/src/urt/mem/package.d b/src/urt/mem/package.d index dfea791..ec4e82e 100644 --- a/src/urt/mem/package.d +++ b/src/urt/mem/package.d @@ -1,24 +1,31 @@ module urt.mem; -public import core.stdc.stddef : wchar_t; - +// TODO: remove these public imports, because this is pulled by object.d! public import urt.lifetime : emplace, moveEmplace, forward, move; public import urt.mem.allocator; +nothrow @nogc: + + +version (LDC) + pragma(LDC_alloca) void* alloca(size_t size) pure @safe; +else + extern(C) void* alloca(size_t size) pure @trusted; extern(C) { nothrow @nogc: - void* alloca(size_t size); - void* calloc(size_t num, size_t size); - void* malloc(size_t size); - void free(void *ptr); + void* malloc(size_t size) @trusted; + void* calloc(size_t num, size_t size) @trusted; + void* realloc(void* ptr, size_t new_size) @trusted; + void free(void* ptr) @trusted; void* memcpy(void* dest, const void* src, size_t n) pure; void* memmove(void* dest, const void* src, size_t n) pure; void* memset(void* s, int c, size_t n) pure; void* memzero(void* s, size_t n) pure => memset(s, 0, n); + int memcmp(const void *s1, const void *s2, size_t n) pure; size_t strlen(const char* s) pure; int strcmp(const char* s1, const char* s2) pure; @@ -31,3 +38,169 @@ nothrow @nogc: // wchar_t* wcsncpy(wchar_t* dest, const wchar_t* src, size_t n) pure; // wchar_t* wcsncat(wchar_t* dest, const wchar_t* src, size_t n); } + + +private: + +version(DigitalMars) +{ + // DMD lowers alloca(n) calls to __alloca(n) + extern (C) void* __alloca(int nbytes) + { + version (D_InlineAsm_X86) + { + asm nothrow @nogc + { + naked ; + mov EDX,ECX ; + mov EAX,4[ESP] ; // get nbytes + push EBX ; + push EDI ; + push ESI ; + + add EAX,15 ; + and EAX,0xFFFFFFF0 ; // round up to 16 byte boundary + jnz Abegin ; + mov EAX,16 ; // minimum allocation is 16 + Abegin: + mov ESI,EAX ; // ESI = nbytes + neg EAX ; + add EAX,ESP ; // EAX is now what the new ESP will be. + jae Aoverflow ; + } + version (Win32) + { + asm nothrow @nogc + { + // Touch guard pages to commit stack memory + mov ECX,EAX ; + mov EBX,ESI ; + L1: + test [ECX+EBX],EBX ; + sub EBX,0x1000 ; + jae L1 ; + test [ECX],EBX ; + } + } + asm nothrow @nogc + { + mov ECX,EBP ; + sub ECX,ESP ; + sub ECX,[EDX] ; + add [EDX],ESI ; + mov ESP,EAX ; + add EAX,ECX ; + mov EDI,ESP ; + add ESI,ESP ; + shr ECX,2 ; + rep ; + movsd ; + jmp done ; + + Aoverflow: + xor EAX,EAX ; + + done: + pop ESI ; + pop EDI ; + pop EBX ; + ret ; + } + } + else version (D_InlineAsm_X86_64) + { + version (Win64) + { + asm nothrow @nogc + { + naked ; + push RBX ; + push RDI ; + push RSI ; + mov RAX,RCX ; + add RAX,15 ; + and AL,0xF0 ; + test RAX,RAX ; + jnz Abegin ; + mov RAX,16 ; + Abegin: + mov RSI,RAX ; + neg RAX ; + add RAX,RSP ; + jae Aoverflow ; + + // Touch guard pages + mov RCX,RAX ; + mov RBX,RSI ; + L1: + test [RCX+RBX],RBX ; + sub RBX,0x1000 ; + jae L1 ; + test [RCX],RBX ; + + mov RCX,RBP ; + sub RCX,RSP ; + sub RCX,[RDX] ; + add [RDX],RSI ; + mov RSP,RAX ; + add RAX,RCX ; + mov RDI,RSP ; + add RSI,RSP ; + shr RCX,3 ; + rep ; + movsq ; + jmp done ; + + Aoverflow: + xor RAX,RAX ; + + done: + pop RSI ; + pop RDI ; + pop RBX ; + ret ; + } + } + else + { + asm nothrow @nogc + { + naked ; + mov RDX,RCX ; + mov RAX,RDI ; + add RAX,15 ; + and AL,0xF0 ; + test RAX,RAX ; + jnz Abegin ; + mov RAX,16 ; + Abegin: + mov RSI,RAX ; + neg RAX ; + add RAX,RSP ; + jae Aoverflow ; + + mov RCX,RBP ; + sub RCX,RSP ; + sub RCX,[RDX] ; + add [RDX],RSI ; + mov RSP,RAX ; + add RAX,RCX ; + mov RDI,RSP ; + add RSI,RSP ; + shr RCX,3 ; + rep ; + movsq ; + jmp done ; + + Aoverflow: + xor RAX,RAX ; + + done: + ret ; + } + } + } + else + static assert(0, "Unsupported architecture for __alloca"); + } +} diff --git a/src/urt/mem/string.d b/src/urt/mem/string.d index a3adc22..ff606ca 100644 --- a/src/urt/mem/string.d +++ b/src/urt/mem/string.d @@ -4,12 +4,6 @@ import urt.mem; import urt.string; -// TODO: THIS IS TEMP!! REMOVE ME!! -shared static this() -{ - initStringHeap(ushort.max); -} - struct CacheString { @@ -77,12 +71,12 @@ private: auto __debugStringView() => toString; } -void initStringHeap(uint stringHeapSize) nothrow +void init_string_heap(uint string_heap_size) nothrow @nogc { assert(stringHeapInitialised == false, "String heap already initialised!"); - assert(stringHeapSize <= ushort.max, "String heap too large!"); + assert(string_heap_size <= ushort.max, "String heap too large!"); - stringHeap = defaultAllocator.allocArray!char(stringHeapSize); + stringHeap = defaultAllocator.allocArray!char(string_heap_size); // write the null string to the start stringHeap[0..2] = 0; @@ -91,8 +85,9 @@ void initStringHeap(uint stringHeapSize) nothrow stringHeapInitialised = true; } -void deinitStringHeap() nothrow +void deinit_string_heap() nothrow @nogc { + defaultAllocator.freeArray(stringHeap); } uint getStringHeapAllocated() nothrow @nogc diff --git a/src/urt/package.d b/src/urt/package.d index 4c4959e..0d673ad 100644 --- a/src/urt/package.d +++ b/src/urt/package.d @@ -16,20 +16,300 @@ public import urt.processor; public import urt.meta : Alias, AliasSeq; public import urt.util : min, max, swap; +version (Windows) +{ + private enum crt = __traits(getTargetInfo, "cppRuntimeLibrary"); + static if (crt) + pragma(lib, crt); + pragma(lib, "kernel32"); +} + private: -pragma(crt_constructor) -void crt_bootup() +// ---------------------------------------------------------------------- +// C entry point - replaces druntime's rt/dmain2.d +// +// We initialize uRT subsystems, scan the PE .minfo section for +// ModuleInfo records, run module constructors, call the D main +// (_Dmain), then run destructors. +// ---------------------------------------------------------------------- + +extern (C) int main(int argc, char** argv) nothrow @nogc @trusted { + import urt.mem; + + import urt.mem.string : init_string_heap, deinit_string_heap; + init_string_heap(ushort.max); + import urt.time : init_clock; init_clock(); import urt.rand; init_rand(); - import urt.dbg : setup_assert_handler; - setup_assert_handler(); - import urt.string.string : initStringAllocators; initStringAllocators(); + + string* args = cast(string*)alloca(string.sizeof * argc); + string[] d_args = args[0 .. argc]; + foreach (i; 0 .. argc) + d_args[i] = cast(string)argv[i][0 .. argv[i].strlen]; + + auto modules = get_module_infos(); + run_module_ctors(modules); + + version (unittest) + { + import urt.internal.stdc : fprintf, stderr, fflush; + import urt.internal.stdc : exit; + + size_t executed, passed; + foreach (m; modules) + { + if (m is null) continue; + if (auto fp = cast(void function() nothrow @nogc) m.unitTest) + { + auto mname2 = m.name; + fprintf(stderr, " running: %.*s ... ", cast(int) mname2.length, mname2.ptr); + fflush(stderr); + ++executed; + if (run_test(fp)) + ++passed; + else + fprintf(stderr, "FAIL\n"); + } + } + + if (executed > 0) + fprintf(stderr, "%d/%d modules passed unittests\n", + cast(int) passed, cast(int) executed); + else + fprintf(stderr, "No unittest functions found!\n"); + + fflush(stderr); + run_module_dtors(modules); + int result = executed > 0 && passed == executed ? 0 : 1; + } + else + { + int result = call_dmain(d_args); + + import urt.internal.stdc : fflush, stdout; + fflush(stdout); + } + + run_module_dtors(modules); + + deinit_string_heap(); + + return result; +} + +version (unittest) +{ + // separated from main() because DMD cannot mix alloca() and exception handling + bool run_test(void function() nothrow @nogc test) nothrow @nogc @trusted + { + import urt.internal.stdc : fprintf, stderr; + try + { + test(); + fprintf(stderr, "ok\n"); + return true; + } + catch (Throwable t) + { + auto msg = t.msg; + fprintf(stderr, "%.*s\n", cast(int) msg.length, msg.ptr); + return false; + } + } +} +else +{ + extern (C) int _Dmain(scope string[] args) @nogc; + + int call_dmain(scope string[] args) nothrow @nogc @trusted + { + int result; + try + result = _Dmain(args); + catch (Throwable t) + { + import urt.internal.stdc : fprintf, stderr; + auto msg = t.msg; + fprintf(stderr, "Uncaught exception: %.*s\n", cast(int) msg.length, msg.ptr); + result = 1; + } + return result; + } +} + +// ---------------------------------------------------------------------- +// Module constructor/destructor execution +// ---------------------------------------------------------------------- + +alias Fn = void function() nothrow @nogc; + +void run_module_ctors(immutable(ModuleInfo*)[] modules) nothrow @nogc @trusted +{ + // order-independent constructors first + foreach (m; modules) + { + if (m is null) + continue; + if (auto fp = cast(Fn) m.ictor) + fp(); + } + + // then regular constructors + foreach (m; modules) + { + if (m is null) + continue; + if (auto fp = cast(Fn) m.ctor) + fp(); + } + + // TLS constructors + foreach (m; modules) + { + if (m is null) + continue; + if (auto fp = cast(Fn) m.tlsctor) + fp(); + } +} + +void run_module_dtors(immutable(ModuleInfo*)[] modules) nothrow @nogc @trusted +{ + // TLS destructors + foreach_reverse (m; modules) + { + if (m is null) + continue; + if (auto fp = cast(Fn) m.tlsdtor) + fp(); + } + + // regular destructors + foreach_reverse (m; modules) + { + if (m is null) + continue; + if (auto fp = cast(Fn) m.dtor) + fp(); + } +} + +// ---------------------------------------------------------------------- +// PE .minfo section scanning - finds compiler-generated ModuleInfo pointers. +// Inline PE parsing to avoid core.sys.windows struct __init dependencies. +// ---------------------------------------------------------------------- + +version (Windows) + extern (C) extern __gshared ubyte __ImageBase; + +version (linux) +{ + // Stashed by _d_dso_registry in object.d from ELF .init_array callback (DMD) + extern (C) extern __gshared immutable(ModuleInfo*)* _elf_minfo_beg; + extern (C) extern __gshared immutable(ModuleInfo*)* _elf_minfo_end; + + version (LDC) + { + // LDC emits ModuleInfo pointers into the __minfo ELF section but does + // not generate .init_array calls to _d_dso_registry. The linker + // generates __start___minfo / __stop___minfo boundary symbols for us. + // Declared as the element type (not pointer-to) so &symbol yields the + // section address with the correct type for slicing. + extern (C) extern __gshared immutable(ModuleInfo*) __start___minfo; + extern (C) extern __gshared immutable(ModuleInfo*) __stop___minfo; + } +} + +immutable(ModuleInfo*)[] get_module_infos() nothrow @nogc @trusted +{ + version (Windows) + { + auto section = find_pe_section(cast(void*)&__ImageBase, ".minfo"); + if (!section.length) + return null; + return (cast(immutable(ModuleInfo*)*)section.ptr)[0 .. section.length / (void*).sizeof]; + } + else version (linux) + { + // DMD path: _d_dso_registry stashed the .minfo section boundaries + if (_elf_minfo_beg !is null) + return _elf_minfo_beg[0 .. _elf_minfo_end - _elf_minfo_beg]; + + // LDC path: read __minfo section via linker-generated symbols + version (LDC) + { + if (&__start___minfo !is null && &__stop___minfo !is null) + return (&__start___minfo)[0 .. &__stop___minfo - &__start___minfo]; + } + + return null; + } + else + { + return null; + } +} + +version (Windows) +void[] find_pe_section(void* image_base, string name) nothrow @nogc @trusted +{ + if (name.length > 8) return null; + + auto base = cast(ubyte*) image_base; + + // DOS header: e_magic at offset 0 (2 bytes), e_lfanew at offset 0x3C (4 bytes) + if (base[0] != 0x4D || base[1] != 0x5A) // 'MZ' + return null; + + auto lfanew = *cast(int*)(base + 0x3C); + auto pe = base + lfanew; + + // PE signature check + if (pe[0] != 'P' || pe[1] != 'E' || pe[2] != 0 || pe[3] != 0) + return null; + + // COFF file header starts at pe+4 + // NumberOfSections at offset 2 (2 bytes) + // SizeOfOptionalHeader at offset 16 (2 bytes) + auto file_header = pe + 4; + ushort num_sections = *cast(ushort*)(file_header + 2); + ushort opt_header_size = *cast(ushort*)(file_header + 16); + + // Section headers start after optional header + auto sections = file_header + 20 + opt_header_size; + + // Each IMAGE_SECTION_HEADER is 40 bytes: + // Name[8] at offset 0 + // VirtualSize at offset 8 + // VirtualAddress at offset 12 + foreach (i; 0 .. num_sections) + { + auto sec = sections + i * 40; + auto sec_name = (cast(char*) sec)[0 .. 8]; + + bool match = true; + foreach (j; 0 .. name.length) + { + if (sec_name[j] != name[j]) + { + match = false; + break; + } + } + if (match && (name.length == 8 || sec_name[name.length] == 0)) + { + auto virtual_size = *cast(uint*)(sec + 8); + auto virtual_address = *cast(uint*)(sec + 12); + return (base + virtual_address)[0 .. virtual_size]; + } + } + return null; } diff --git a/src/urt/result.d b/src/urt/result.d index b5b67ea..ed3f676 100644 --- a/src/urt/result.d +++ b/src/urt/result.d @@ -69,7 +69,7 @@ nothrow @nogc: version (Windows) { - import core.sys.windows.windows; + import urt.internal.sys.windows; enum InternalResult : Result { @@ -93,7 +93,14 @@ version (Windows) } else version (Posix) { - import core.stdc.errno; + import urt.internal.stdc; + + extern (C) private int* __errno_location() nothrow @nogc; + + @property int errno() nothrow @nogc @trusted + { + return *__errno_location(); + } enum InternalResult : Result { diff --git a/src/urt/socket.d b/src/urt/socket.d index ddcafdc..16cc88e 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -11,8 +11,8 @@ version (Windows) // TODO: this is in core.sys.windows.winsock2; why do I need it here? pragma(lib, "ws2_32"); - import core.sys.windows.windows; - import core.sys.windows.winsock2 : + import urt.internal.sys.windows; + import urt.internal.sys.windows.winsock2 : _bind = bind, _listen = listen, _connect = connect, _accept = accept, _send = send, _sendto = sendto, _recv = recv, _recvfrom = recvfrom, _shutdown = shutdown; @@ -26,11 +26,10 @@ version (Windows) } else version (Posix) { - import core.stdc.errno; + import urt.internal.os; // use ImportC to import system C headers... import core.sys.posix.fcntl; import core.sys.posix.poll; import core.sys.posix.unistd : close, gethostname; - import urt.internal.os; // use ImportC to import system C headers... import core.sys.posix.netinet.in_ : in6_addr, sockaddr_in6; alias _bind = urt.internal.os.bind, _listen = urt.internal.os.listen, _connect = urt.internal.os.connect, @@ -1090,7 +1089,7 @@ Result socket_getlasterror() version (Windows) return Result(WSAGetLastError()); else - return Result(errno); + return errno_result(); } Result get_socket_error(Socket socket) @@ -1576,7 +1575,7 @@ version (Windows) void crt_bootup() { WSADATA wsaData; - int result = WSAStartup(MAKEWORD(2, 2), &wsaData); + int result = WSAStartup(0x0202, &wsaData); // what if this fails??? // this is truly the worst thing I ever wrote!! diff --git a/src/urt/string/string.d b/src/urt/string/string.d index 974e771..7313ca0 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -440,6 +440,7 @@ unittest assert(!emptyLit); // opCast!bool // Test makeString (default allocator) + // TODO: reinstate the GC for debug allocations... // String s1 = makeString("World"); String s1 = StringLit!"World"; assert(s1.length == 5); @@ -464,6 +465,7 @@ unittest assert(nullStr == ""); assert(!nullStr); + // TODO: reinstate once GC makeString is available // Test assignment and reference counting (basic check) String s3 = s1; // s3 references the same data as s1 assert(s3.ptr == s1.ptr); @@ -1103,7 +1105,7 @@ private: __gshared StringAllocator[4] stringAllocators; static assert(stringAllocators.length <= 4, "Only 2 bits reserved to store allocator index"); -package(urt) void initStringAllocators() +package(urt) void initStringAllocators() nothrow @nogc { stringAllocators[StringAlloc.Default].alloc = (ushort bytes, void* userData) { char* buffer = cast(char*)defaultAllocator().alloc(bytes + 4, ushort.alignof).ptr; diff --git a/src/urt/system.d b/src/urt/system.d index 903ce2a..585404f 100644 --- a/src/urt/system.d +++ b/src/urt/system.d @@ -26,7 +26,7 @@ void sleep(Duration duration) version (Windows) { - import core.sys.windows.winbase : Sleep; + import urt.internal.sys.windows.winbase : Sleep; Sleep(cast(uint)duration.as!"msecs"); } else @@ -93,7 +93,7 @@ void set_system_idle_params(IdleParams params) { version (Windows) { - import core.sys.windows.winbase; + import urt.internal.sys.windows.winbase; enum EXECUTION_STATE ES_SYSTEM_REQUIRED = 0x00000001; enum EXECUTION_STATE ES_DISPLAY_REQUIRED = 0x00000002; @@ -124,7 +124,7 @@ package: version (Windows) { - import core.sys.windows.winbase : GlobalMemoryStatusEx, MEMORYSTATUSEX; + import urt.internal.sys.windows.winbase : GlobalMemoryStatusEx, MEMORYSTATUSEX; extern(Windows) ulong GetTickCount64(); diff --git a/src/urt/time.d b/src/urt/time.d index 0abf92a..43592d1 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -4,7 +4,7 @@ import urt.traits : is_some_float; version (Windows) { - import core.sys.windows.windows; + import urt.internal.sys.windows; extern (Windows) void GetSystemTimePreciseAsFileTime(FILETIME* lpSystemTimeAsFileTime) nothrow @nogc; } @@ -843,7 +843,7 @@ package(urt) void init_clock() version (Windows) { - import core.sys.windows.windows; + import urt.internal.sys.windows; import urt.util : min; LARGE_INTEGER freq; diff --git a/src/urt/util.d b/src/urt/util.d index fb8fe2d..4971ddd 100644 --- a/src/urt/util.d +++ b/src/urt/util.d @@ -30,12 +30,12 @@ pure: auto min(T, U)(auto ref inout T a, auto ref inout U b) { - return a < b ? a : b; + return b < a ? b : a; } auto max(T, U)(auto ref inout T a, auto ref inout U b) { - return a > b ? a : b; + return b > a ? b : a; } template Align(size_t value, size_t alignment = size_t.sizeof) @@ -110,6 +110,42 @@ bool is_aligned(T)(T value, size_t alignment) return (cast(size_t)value & (alignment - 1)) == 0; } +T rol(T)(const T value, const uint count) pure + if (__traits(isIntegral, T) && __traits(isUnsigned, T)) +{ + assert(count < 8 * T.sizeof); + if (count == 0) + return cast(T)value; + return cast(T)((value << count) | (value >> (T.sizeof * 8 - count))); +} + +T ror(T)(const T value, const uint count) pure + if (__traits(isIntegral, T) && __traits(isUnsigned, T)) +{ + assert(count < 8 * T.sizeof); + if (count == 0) + return cast(T)value; + return cast(T)((value >> count) | (value << (T.sizeof * 8 - count))); +} + +T rol(uint count, T)(const T value) pure + if (__traits(isIntegral, T) && __traits(isUnsigned, T)) +{ + static assert(count < 8 * T.sizeof); + static if (count == 0) + return cast(T)value; + return cast(T)((value << count) | (value >> (T.sizeof * 8 - count))); +} + +T ror(uint count, T)(const T value) pure + if (__traits(isIntegral, T) && __traits(isUnsigned, T)) +{ + static assert(count < 8 * T.sizeof); + static if (count == 0) + return cast(T)value; + return cast(T)((value >> count) | (value << (T.sizeof * 8 - count))); +} + /+ ubyte log2(ubyte val) { @@ -460,7 +496,7 @@ T bit_reverse(T)(T x) // TODO: these may be inferior on platforms where mul is slow... static if (size_t.sizeof == 8) { - // return cast(ubyte)((b*0x0202020202ULL & 0x010884422010ULL) % 1023; // only 3 ops, but uses div! +// return cast(ubyte)((b*0x0202020202ULL & 0x010884422010ULL) % 1023; // only 3 ops, but uses div! return cast(ubyte)(cast(ulong)(x*0x80200802UL & 0x0884422110)*0x0101010101 >> 32); } else @@ -481,6 +517,83 @@ T bit_reverse(T)(T x) return byte_reverse(x); } } + else static if (false) // TODO: DMD, x86, 4 or 8 bytes... + { + static if (T.sizeof == 4) + { + asm pure nothrow @nogc { naked; } + + version (D_InlineAsm_X86_64) + { + version (Win64) + asm pure nothrow @nogc { mov EAX, ECX; } + else + asm pure nothrow @nogc { mov EAX, EDI; } + } + + asm pure nothrow @nogc + { + mov EDX, EAX; + shr EAX, 1; + and EDX, 0x5555_5555; + and EAX, 0x5555_5555; + shl EDX, 1; + or EAX, EDX; + mov EDX, EAX; + shr EAX, 2; + and EDX, 0x3333_3333; + and EAX, 0x3333_3333; + shl EDX, 2; + or EAX, EDX; + mov EDX, EAX; + shr EAX, 4; + and EDX, 0x0f0f_0f0f; + and EAX, 0x0f0f_0f0f; + shl EDX, 4; + or EAX, EDX; + bswap EAX; + ret; + } + } + else + { + asm pure nothrow @nogc { naked; } + + version (Win64) + asm pure nothrow @nogc { mov RAX, RCX; } + else + asm pure nothrow @nogc { mov RAX, RDI; } + + asm pure nothrow @nogc + { + mov RDX, RAX; + shr RAX, 1; + mov RCX, 0x5555_5555_5555_5555L; + and RDX, RCX; + and RAX, RCX; + shl RDX, 1; + or RAX, RDX; + + mov RDX, RAX; + shr RAX, 2; + mov RCX, 0x3333_3333_3333_3333L; + and RDX, RCX; + and RAX, RCX; + shl RDX, 2; + or RAX, RDX; + + mov RDX, RAX; + shr RAX, 4; + mov RCX, 0x0f0f_0f0f_0f0f_0f0fL; + and RDX, RCX; + and RAX, RCX; + shl RDX, 4; + or RAX, RDX; + bswap RAX; + ret; + } + } + } else { version (LDC) From 7d73a94d0bf51d1008912c1d239d407f32dd3bee Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 17 Mar 2026 00:47:21 +1000 Subject: [PATCH 102/138] Fix urt.io alias overload selection. --- src/urt/exception.d | 28 ++++--------- src/urt/io.d | 97 +++++++++++++++++++++++++++------------------ src/urt/package.d | 34 +++++++--------- 3 files changed, 81 insertions(+), 78 deletions(-) diff --git a/src/urt/exception.d b/src/urt/exception.d index 84decb4..fdb500d 100644 --- a/src/urt/exception.d +++ b/src/urt/exception.d @@ -24,35 +24,23 @@ __gshared AssertHandler _assert_handler = &urt_assert; void urt_assert(string file, size_t line, string msg) nothrow @nogc { - import urt.internal.stdc : exit; + if (msg.length == 0) + msg = "Assertion failed"; debug { - import urt.internal.stdc; + import urt.io : writef_to, WriteTarget; import urt.dbg; - if (msg.length == 0) - msg = "Assertion failed"; - version (Windows) - { - import urt.internal.sys.windows.winbase; - char[1024] buffer; - _snprintf(buffer.ptr, buffer.length, "%.*s(%d): %.*s\n", cast(int)file.length, file.ptr, cast(int)line, cast(int)msg.length, msg.ptr); - OutputDebugStringA(buffer.ptr); - - // Windows can have it at stdout aswell? - printf("%.*s(%d): %.*s\n", cast(int)file.length, file.ptr, cast(int)line, cast(int)msg.length, msg.ptr); - } - else - { - // TODO: write to stderr would be better... - printf("%.*s(%d): %.*s\n", cast(int)file.length, file.ptr, cast(int)line, cast(int)msg.length, msg.ptr); - } + writef_to!(WriteTarget.debugstring, true)("{0}({1}): {2}", file, line, msg); + writef_to!(WriteTarget.stdout, true)("{0}({1}): {2}", file, line, msg); breakpoint(); -// exit(-1); // TODO: what if some systems don't support a software breakpoint? } else + { + import urt.internal.stdc : exit; exit(-1); + } } diff --git a/src/urt/io.d b/src/urt/io.d index 58b19df..9cca1aa 100644 --- a/src/urt/io.d +++ b/src/urt/io.d @@ -9,47 +9,59 @@ enum WriteTarget : ubyte debugstring = 2, // Windows OutputDebugStringA } -int write_to(WriteTarget target, bool newline = false)(const(char)[] str) +template write_to(WriteTarget target, bool newline = false) { - static if (target == WriteTarget.stdout) + int write_to(const(char)[] str) { - import urt.internal.stdc; - return printf("%.*s" ~ (newline ? "\n" : ""), cast(int)str.length, str.ptr); - } - else static if (target == WriteTarget.stderr) - { - import urt.internal.stdc; - return fprintf(stderr, "%.*s" ~ (newline ? "\n" : ""), cast(int)str.length, str.ptr); - } - else static if (target == WriteTarget.debugstring) - { - version (Windows) + static if (target == WriteTarget.stdout || target == WriteTarget.stderr) { - import core.sys.windows.windows; - OutputDebugStringA(str.ptr); - static if (newline) - OutputDebugStringA("\n"); - return cast(int)str.length + newline; + version (FreeStanding) + { + import sys.bl808.uart : uart0_puts; + uart0_puts(str); + static if (newline) + uart0_puts("\n"); + return cast(int) str.length; + } + else + { + import urt.internal.stdc; + static if (target == WriteTarget.stderr) + return fprintf(stderr, "%.*s" ~ (newline ? "\n" : ""), cast(int)str.length, str.ptr); + else + return printf("%.*s" ~ (newline ? "\n" : ""), cast(int)str.length, str.ptr); + } } - else + else static if (target == WriteTarget.debugstring) { - // is stderr the best analogy on other platforms? - return write_to!(WriteTarget.stderr, newline)(str); + version (Windows) + { + import core.sys.windows.windows; + OutputDebugStringA(str.ptr); + static if (newline) + OutputDebugStringA("\n"); + return cast(int)str.length + newline; + } + else + { + // is stderr the best analogy on other platforms? + return write_to!(WriteTarget.stderr, newline)(str); + } } + else + static assert(0, "Invalid WriteTarget"); } - else - static assert(0, "Invalid WriteTarget"); -} -int write_to(WriteTarget target, bool newline = false, Args...)(ref Args args) - if (Args.length != 1 || !is(Args[0] : const(char)[])) -{ - import urt.string.format; - import urt.mem.temp; + int write_to(Args...)(ref Args args) + if (Args.length != 1 || !is(Args[0] : const(char)[])) + { + import urt.string.format; + import urt.mem.temp; - size_t len = concat(null, args).length; - const(char)[] t = concat(cast(char[])talloc(len), args); - return write_to!(target, newline)(t); + size_t len = concat(null, args).length; + const(char)[] t = concat(cast(char[])talloc(len), args); + return write_to(t); + } } int writef_to(WriteTarget target, bool newline = false, Args...)(const(char)[] fmt, ref Args args) @@ -70,12 +82,21 @@ alias writeln_err = write_to!(WriteTarget.stderr, true); alias write_debug = write_to!(WriteTarget.debugstring, false); alias writeln_debug = write_to!(WriteTarget.debugstring, true); -int write(Args...)(ref Args args) - if (Args.length != 1 || !is(Args[0] : const(char)[])) - => write_to!(WriteTarget.stdout, false)(args); -int writeln(Args...)(ref Args args) - if (Args.length != 1 || !is(Args[0] : const(char)[])) - => write_to!(WriteTarget.stdout, true)(args); +void flush(WriteTarget target = WriteTarget.stdout)() nothrow @nogc +{ + version (FreeStanding) + { + // UART writes are unbuffered — nothing to flush + } + else + { + import urt.internal.stdc : fflush, stdout, stderr; + static if (target == WriteTarget.stdout) + fflush(stdout); + else static if (target == WriteTarget.stderr) + fflush(stderr); + } +} int writef(Args...)(ref Args args) => writef_to!(WriteTarget.stdout, false)(args); diff --git a/src/urt/package.d b/src/urt/package.d index 0d673ad..2662c96 100644 --- a/src/urt/package.d +++ b/src/urt/package.d @@ -16,6 +16,8 @@ public import urt.processor; public import urt.meta : Alias, AliasSeq; public import urt.util : min, max, swap; +import urt.io; + version (Windows) { private enum crt = __traits(getTargetInfo, "cppRuntimeLibrary"); @@ -34,7 +36,7 @@ private: // (_Dmain), then run destructors. // ---------------------------------------------------------------------- -extern (C) int main(int argc, char** argv) nothrow @nogc @trusted +extern(C) int main(int argc, char** argv) nothrow @nogc @trusted { import urt.mem; @@ -60,7 +62,6 @@ extern (C) int main(int argc, char** argv) nothrow @nogc @trusted version (unittest) { - import urt.internal.stdc : fprintf, stderr, fflush; import urt.internal.stdc : exit; size_t executed, passed; @@ -69,24 +70,22 @@ extern (C) int main(int argc, char** argv) nothrow @nogc @trusted if (m is null) continue; if (auto fp = cast(void function() nothrow @nogc) m.unitTest) { - auto mname2 = m.name; - fprintf(stderr, " running: %.*s ... ", cast(int) mname2.length, mname2.ptr); - fflush(stderr); + write_err(" running: ", m.name, " ... "); + flush!(WriteTarget.stderr)(); ++executed; if (run_test(fp)) ++passed; else - fprintf(stderr, "FAIL\n"); + writeln_err("FAIL"); } } if (executed > 0) - fprintf(stderr, "%d/%d modules passed unittests\n", - cast(int) passed, cast(int) executed); + writeln_err(passed, '/', executed, " modules passed unittests", ); else - fprintf(stderr, "No unittest functions found!\n"); + writeln_err("No unittest functions found!"); - fflush(stderr); + flush!(WriteTarget.stderr)(); run_module_dtors(modules); int result = executed > 0 && passed == executed ? 0 : 1; } @@ -94,8 +93,7 @@ extern (C) int main(int argc, char** argv) nothrow @nogc @trusted { int result = call_dmain(d_args); - import urt.internal.stdc : fflush, stdout; - fflush(stdout); + flush!(WriteTarget.stdout)(); } run_module_dtors(modules); @@ -110,24 +108,22 @@ version (unittest) // separated from main() because DMD cannot mix alloca() and exception handling bool run_test(void function() nothrow @nogc test) nothrow @nogc @trusted { - import urt.internal.stdc : fprintf, stderr; try { test(); - fprintf(stderr, "ok\n"); + writeln_err("ok"); return true; } catch (Throwable t) { - auto msg = t.msg; - fprintf(stderr, "%.*s\n", cast(int) msg.length, msg.ptr); + writeln_err(t.msg); return false; } } } else { - extern (C) int _Dmain(scope string[] args) @nogc; + extern(C) int _Dmain(scope string[] args) @nogc; int call_dmain(scope string[] args) nothrow @nogc @trusted { @@ -136,9 +132,7 @@ else result = _Dmain(args); catch (Throwable t) { - import urt.internal.stdc : fprintf, stderr; - auto msg = t.msg; - fprintf(stderr, "Uncaught exception: %.*s\n", cast(int) msg.length, msg.ptr); + writeln_err("Uncaught exception: ", t.msg); result = 1; } return result; From 04b185a9b40b76196a36d05e218e211a078f477f Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 19 Mar 2026 03:49:33 +1000 Subject: [PATCH 103/138] Add functions to calculate visible width and slice visible characters. --- src/urt/string/ansi.d | 68 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/urt/string/ansi.d b/src/urt/string/ansi.d index 931d60d..6828cf1 100644 --- a/src/urt/string/ansi.d +++ b/src/urt/string/ansi.d @@ -84,6 +84,74 @@ size_t parse_ansi_code(const(char)[] text) return i + 1; } +size_t visible_width(const(char)[] text) +{ + import urt.string.uni : uni_seq_len; + + size_t width = 0; + size_t i = 0; + while (i < text.length) + { + if (text[i] == '\x1b' && i + 1 < text.length && text[i + 1] == '[') + { + i += 2; + while (i < text.length && text[i] != 'm') + ++i; + if (i < text.length) + ++i; + } + else + { + ++width; + i += uni_seq_len(text[i .. $]); + } + } + return width; +} + +const(char)[] visible_slice(const(char)[] text, char[] buf, size_t start = 0, size_t end = size_t.max) +{ + import urt.string.uni : uni_seq_len; + + size_t out_pos = 0; + size_t visible = 0; + + for (size_t i = 0; i < text.length; ) + { + if (text[i] == '\x1b' && i + 1 < text.length && text[i + 1] == '[') + { + size_t seq_start = i; + i += 2; + while (i < text.length && text[i] != 'm') + ++i; + if (i < text.length) + ++i; + size_t seq_len = i - seq_start; + if (out_pos + seq_len <= buf.length) + { + buf[out_pos .. out_pos + seq_len] = text[seq_start .. i]; + out_pos += seq_len; + } + } + else + { + size_t ch_len = uni_seq_len(text[i .. $]); + if (visible >= start && visible < end) + { + if (out_pos + ch_len <= buf.length) + { + buf[out_pos .. out_pos + ch_len] = text[i .. i + ch_len]; + out_pos += ch_len; + } + } + ++visible; + i += ch_len; + } + } + + return buf[0 .. out_pos]; +} + char[] strip_decoration(char[] text) pure { return strip_decoration(text, text); From 8f874cffa7b12f3077e6d1c4c3e4ae24543a160e Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 26 Mar 2026 03:43:13 +1000 Subject: [PATCH 104/138] Add __monitor to Object. --- src/object.d | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/object.d b/src/object.d index e739fa9..86640ce 100644 --- a/src/object.d +++ b/src/object.d @@ -103,6 +103,9 @@ public import urt.util : min, max, swap; class Object { @nogc: + static if (__VERSION__ >= 2113) + void* __monitor; + size_t toHash() @trusted nothrow { size_t addr = cast(size_t) cast(void*) this; From f8c746dc180a0dbecc17aa913170f6143004bd06 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 25 Mar 2026 22:43:45 +1000 Subject: [PATCH 105/138] Add urt.string.regex --- src/urt/string/regex.d | 952 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 952 insertions(+) create mode 100644 src/urt/string/regex.d diff --git a/src/urt/string/regex.d b/src/urt/string/regex.d new file mode 100644 index 0000000..80e81aa --- /dev/null +++ b/src/urt/string/regex.d @@ -0,0 +1,952 @@ +module urt.string.regex; + +import urt.mem.allocator; + +nothrow @nogc: + + +struct RegexMatch +{ + const(char)[] full; + const(char)[][Regex.MaxGroups] captures; + ubyte num_captures; +} + +bool regex_match(const(char)[] text, const(char)[] pattern, ref RegexMatch result) +{ + Regex re = regex_compile(pattern, tempAllocator()); + if (!re.valid) + return false; + return re.exec(text, result); +} + +// Compiled regex program. Single contiguous allocation for all instructions +// and character class data. Compile with regex_compile(), run with exec(). +// +// Supported syntax: +// . any character +// \d \D digit / non-digit +// \s \S whitespace / non-whitespace +// \w \W word char [a-zA-Z0-9_] / non-word +// \. \\ etc literal escapes +// [abc] character class +// [^abc] negated character class +// [a-z] character range in class +// * + ? greedy quantifiers +// *? +? ?? non-greedy (lazy) quantifiers +// (...) capture group (first group returned) +// (a|b) alternation +// ^ $ anchors (start/end of text) +struct Regex +{ +nothrow @nogc: + + enum MaxGroups = 8; + + bool valid() const + => data.ptr !is null; + + // Execute against text. Unanchored unless pattern used ^. + bool exec(const(char)[] text, ref RegexMatch result) const + { + if (!valid) + return false; + + ref const Header hdr = *cast(const(Header)*)data.ptr; + const(Inst)[] code = (cast(const(Inst)*)(data.ptr + Header.sizeof))[0 .. hdr.num_insts]; + const(ClassRange)[] ranges = (cast(const(ClassRange)*)(data.ptr + Header.sizeof + hdr.num_insts * Inst.sizeof))[0 .. hdr.num_ranges]; + const(ClassDef)[] classes = (cast(const(ClassDef)*)(data.ptr + Header.sizeof + hdr.num_insts * Inst.sizeof + hdr.num_ranges * ClassRange.sizeof))[0 .. hdr.num_classes]; + + struct Thread + { + ushort ipc, pos; + ushort[MaxGroups] gs, ge; + } + + Thread[512] stack = void; + ushort sp; + + foreach (start; 0 .. hdr.anchored ? 1 : text.length + 1) + { + sp = 0; + Thread t; + t.ipc = 0; + t.pos = cast(ushort)start; + t.gs[] = ushort.max; + t.ge[] = ushort.max; + + bool matched = false; + + while (true) + { + if (t.ipc >= code.length) + break; + + Inst inst = code[t.ipc]; + + final switch (inst.op) with (Op) + { + case literal: + if (t.pos < text.length && text[t.pos] == inst.operand) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case any: + if (t.pos < text.length) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case digit: + if (t.pos < text.length && is_digit(text[t.pos])) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case not_digit: + if (t.pos < text.length && !is_digit(text[t.pos])) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case space: + if (t.pos < text.length && is_space(text[t.pos])) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case not_space: + if (t.pos < text.length && !is_space(text[t.pos])) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case word: + if (t.pos < text.length && is_word(text[t.pos])) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case not_word: + if (t.pos < text.length && !is_word(text[t.pos])) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case char_class: + if (t.pos < text.length && match_class(classes, ranges, inst.operand, text[t.pos])) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case anchor_start: + if (t.pos == 0) + ++t.ipc; + else + goto backtrack; + break; + case anchor_end: + if (t.pos == text.length) + ++t.ipc; + else + goto backtrack; + break; + case group_open: + if (inst.operand < MaxGroups) + t.gs[inst.operand] = t.pos; + ++t.ipc; + break; + case group_close: + if (inst.operand < MaxGroups) + t.ge[inst.operand] = t.pos; + ++t.ipc; + break; + case split: + if (sp < stack.length) + { + stack[sp] = t; + if (inst.operand == 0) + { + stack[sp].ipc = inst.aux; + ++t.ipc; + } + else + { + stack[sp].ipc = cast(ushort)(t.ipc + 1); + t.ipc = inst.aux; + } + ++sp; + } + else + goto backtrack; + break; + case jump: + t.ipc = inst.aux; + break; + case Op.match: + matched = true; + break; + } + + if (matched) + break; + continue; + + backtrack: + if (sp == 0) + break; + t = stack[--sp]; + } + + if (matched) + { + result.full = text[start .. t.pos]; + result.num_captures = 0; + foreach (g; 0 .. hdr.num_groups) + { + if (g >= MaxGroups) + break; + if (t.gs[g] != ushort.max && t.ge[g] != ushort.max) + { + result.captures[g] = text[t.gs[g] .. t.ge[g]]; + result.num_captures = cast(ubyte)(g + 1); + } + else + result.captures[g] = null; + } + return true; + } + } + + return false; + } + +private: + const(ubyte)[] data; // Header ~ Inst[] ~ ClassRange[] ~ ClassDef[] + + struct Header + { + ushort num_insts; + ubyte num_classes; + ubyte num_ranges; + ubyte num_groups; + bool anchored; + } + + enum Op : ubyte + { + literal, any, digit, not_digit, space, not_space, word, not_word, + char_class, anchor_start, anchor_end, group_open, group_close, + split, jump, match, + } + + struct Inst + { + Op op; + ubyte operand; + ushort aux; + } + + struct ClassRange + { + char lo, hi; + } + + struct ClassDef + { + ubyte start, count; + bool negated; + } + + static bool is_digit(char ch) { return ch >= '0' && ch <= '9'; } + static bool is_space(char ch) { return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; } + static bool is_word(char ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || is_digit(ch) || ch == '_'; } + + static bool match_class(const(ClassDef)[] classes, const(ClassRange)[] ranges, ubyte idx, char ch) + { + if (idx >= classes.length) + return false; + ref const ClassDef cd = classes[idx]; + bool found = false; + foreach (i; cd.start .. cd.start + cd.count) + { + if (ch >= ranges[i].lo && ch <= ranges[i].hi) + { + found = true; + break; + } + } + return cd.negated ? !found : found; + } +} + + +Regex regex_compile(const(char)[] pattern, NoGCAllocator allocator = defaultAllocator()) +{ + import urt.mem : memcpy; + + alias Op = Regex.Op; + alias Inst = Regex.Inst; + alias ClassRange = Regex.ClassRange; + alias ClassDef = Regex.ClassDef; + + // TODO: replace these stack buffers with Array!N when it works... + Inst[256] code = void; + ClassRange[128] class_ranges = void; + ClassDef[32] class_defs = void; + ubyte num_classes, total_ranges, num_groups; + ushort pc; + bool anchored; + + bool emit(Op op, ubyte operand = 0, ushort aux = 0) + { + if (pc >= code.length - 1) + return false; + code[pc++] = Inst(op, operand, aux); + return true; + } + + bool shift_code(ushort start, ushort end, ushort n) + { + if (end + n >= code.length) + return false; + for (ushort i = cast(ushort)(end + n - 1); i >= start + n; --i) + code[i] = code[cast(ushort)(i - n)]; + for (ushort i = 0; i < end + n; ++i) + { + if (code[i].op == Op.split || code[i].op == Op.jump) + { + if (code[i].aux >= start && code[i].aux < end) + code[i].aux += n; + } + } + pc += n; + return true; + } + + struct GroupFrame + { + ushort group_pc; + ubyte group_id; + ubyte num_jumps; + ushort[8] jump_pcs; + ushort last_split; + bool has_alt; + } + GroupFrame[16] gframes = void; + ubyte gdepth; + + Regex fail; + + size_t pi = 0; + while (pi < pattern.length) + { + ushort atom_start = pc; + char c = pattern[pi++]; + + switch (c) + { + case '^': + if (!emit(Op.anchor_start)) + return fail; + continue; + case '$': + if (!emit(Op.anchor_end)) + return fail; + continue; + + case '|': + { + // Alternation: a|b|c compiles as cascading splits: + // split(alt1, L1) → alt1 → jump(end) → L1: split(alt2, L2) → alt2 → jump(end) → L2: alt3 → end + // First | inserts a split before alt1. Subsequent | patch the previous split's aux. + if (gdepth == 0) + return fail; + ref GroupFrame gf = gframes[gdepth - 1]; + + // Emit jump to skip over remaining alternatives (patched at ')') + if (!emit(Op.jump)) + return fail; + if (gf.num_jumps < gf.jump_pcs.length) + gf.jump_pcs[gf.num_jumps++] = cast(ushort)(pc - 1); + + // Insert a split before the current alternative. + // For a|b: split(a, b). For a|b|c: split(a, split(b, c)). + // Each | inserts a split before its preceding alternative that + // points forward to the next one. + { + // Find where the current alternative started — it's right after + // the previous split (or after group_open for the first alt). + ushort split_pos; + if (!gf.has_alt) + split_pos = cast(ushort)(gf.group_pc + 1); // after group_open + else + split_pos = cast(ushort)(gf.last_split + 1); // after previous split + + if (!shift_code(split_pos, pc, 1)) + return fail; + code[split_pos] = Inst(Op.split, 0, pc); // aux = start of next alt + gf.last_split = split_pos; + + // Fix jump_pcs that shifted + foreach (ref jp; gf.jump_pcs[0 .. gf.num_jumps]) + { + if (jp >= split_pos) + ++jp; + } + } + gf.has_alt = true; + continue; + } + + case '.': + if (!emit(Op.any)) + return fail; + break; + + case '\\': + if (pi >= pattern.length) + return fail; + switch (pattern[pi++]) + { + case 'd': + if (!emit(Op.digit)) + return fail; + break; + case 'D': + if (!emit(Op.not_digit)) + return fail; + break; + case 's': + if (!emit(Op.space)) + return fail; + break; + case 'S': + if (!emit(Op.not_space)) + return fail; + break; + case 'w': + if (!emit(Op.word)) + return fail; + break; + case 'W': + if (!emit(Op.not_word)) + return fail; + break; + default: + if (!emit(Op.literal, cast(ubyte)pattern[pi-1])) + return fail; + break; + } + break; + + case '[': + { + if (num_classes >= class_defs.length) + return fail; + bool negated = pi < pattern.length && pattern[pi] == '^'; + if (negated) + ++pi; + ubyte rs = total_ranges; + while (pi < pattern.length && pattern[pi] != ']') + { + if (total_ranges >= class_ranges.length) + return fail; + char lo = pattern[pi++]; + if (lo == '\\' && pi < pattern.length) + lo = pattern[pi++]; + if (pi + 1 < pattern.length && pattern[pi] == '-' && pattern[pi+1] != ']') + { + ++pi; + char hi = pattern[pi++]; + if (hi == '\\' && pi < pattern.length) + hi = pattern[pi++]; + class_ranges[total_ranges++] = ClassRange(lo, hi); + } + else + class_ranges[total_ranges++] = ClassRange(lo, lo); + } + if (pi < pattern.length) + ++pi; + class_defs[num_classes] = ClassDef(rs, cast(ubyte)(total_ranges - rs), negated); + if (!emit(Op.char_class, num_classes++)) + return fail; + break; + } + + case '(': + { + if (gdepth >= gframes.length) + return fail; + ubyte gid = num_groups++; + gframes[gdepth] = GroupFrame.init; + gframes[gdepth].group_id = gid; + gframes[gdepth].group_pc = pc; + ++gdepth; + if (!emit(Op.group_open, gid)) + return fail; + continue; + } + + case ')': + { + if (gdepth == 0) + return fail; + --gdepth; + ref GroupFrame gf = gframes[gdepth]; + + // Patch all | jumps to land on the group_close (not past it) + foreach (ref jp; gf.jump_pcs[0 .. gf.num_jumps]) + code[jp].aux = pc; + + if (!emit(Op.group_close, gf.group_id)) + return fail; + + atom_start = gf.group_pc; + break; + } + + default: + if (!emit(Op.literal, cast(ubyte)c)) + return fail; + break; + } + + // Quantifier + if (pi < pattern.length && (pattern[pi] == '*' || pattern[pi] == '+' || pattern[pi] == '?')) + { + char q = pattern[pi++]; + bool is_lazy = pi < pattern.length && pattern[pi] == '?'; + if (is_lazy) + ++pi; + + ushort body_start = atom_start; + ushort body_end = pc; + ubyte lazy_flag = is_lazy ? 1 : 0; + + if (q == '*') + { + if (!shift_code(body_start, body_end, 1)) + return fail; + ushort after = cast(ushort)(pc + 1); + code[body_start] = Inst(Op.split, lazy_flag, after); + if (!emit(Op.jump, 0, body_start)) + return fail; + } + else if (q == '+') + { + if (!emit(Op.split, lazy_flag, cast(ushort)(pc + 1))) + return fail; + code[pc - 1] = Inst(Op.split, lazy_flag, cast(ushort)(pc + 1)); + if (!emit(Op.jump, 0, body_start)) + return fail; + } + else // '?' + { + if (!shift_code(body_start, body_end, 1)) + return fail; + code[body_start] = Inst(Op.split, lazy_flag, pc); + } + } + } + + if (gdepth != 0) + return fail; // unclosed group + + if (!emit(Op.match)) + return fail; + + anchored = pattern.length > 0 && pattern[0] == '^'; + + // Pack into single allocation: Header ~ Inst[pc] ~ ClassRange[total_ranges] ~ ClassDef[num_classes] + size_t size = Regex.Header.sizeof + pc * Inst.sizeof + total_ranges * ClassRange.sizeof + num_classes * ClassDef.sizeof; + ubyte[] buf = cast(ubyte[])allocator.alloc(size); + if (!buf) + return fail; + + Regex.Header* hdr = cast(Regex.Header*)buf.ptr; + hdr.num_insts = pc; + hdr.num_classes = num_classes; + hdr.num_ranges = total_ranges; + hdr.num_groups = num_groups; + hdr.anchored = anchored; + + size_t off = Regex.Header.sizeof; + memcpy(buf.ptr + off, code.ptr, pc * Inst.sizeof); + off += pc * Inst.sizeof; + memcpy(buf.ptr + off, class_ranges.ptr, total_ranges * ClassRange.sizeof); + off += total_ranges * ClassRange.sizeof; + memcpy(buf.ptr + off, class_defs.ptr, num_classes * ClassDef.sizeof); + + Regex result; + result.data = cast(const(ubyte)[])buf; + return result; +} + + +unittest +{ + RegexMatch m; + + // -- Literals -- + + assert(regex_match("hello world", "world", m)); + assert(m.full == "world"); + assert(m.num_captures == 0); + + assert(regex_match("abc", "abc", m)); + assert(m.full == "abc"); + + assert(!regex_match("hello world", "xyz", m)); + + // match at start, middle, end + assert(regex_match("abc", "a", m)); + assert(m.full == "a"); + assert(regex_match("abc", "b", m)); + assert(m.full == "b"); + assert(regex_match("abc", "c", m)); + assert(m.full == "c"); + + // -- Dot (any char) -- + + assert(regex_match("abc", "a.c", m)); + assert(m.full == "abc"); + + assert(!regex_match("ac", "a.c", m)); // dot requires exactly one char + + assert(regex_match("a\tc", "a.c", m)); // dot matches tab + assert(m.full == "a\tc"); + + // -- Character classes -- + + assert(regex_match("test123", "[0-9]+", m)); + assert(m.full == "123"); + + assert(regex_match("abc123", "[^a-z]+", m)); // negated + assert(m.full == "123"); + + assert(regex_match("x", "[xyz]", m)); + assert(m.full == "x"); + + assert(!regex_match("a", "[xyz]", m)); + + assert(regex_match("B", "[A-Za-z]", m)); // multiple ranges + assert(m.full == "B"); + + assert(regex_match("-", "[a\\-z]", m)); // escaped - in class + assert(m.full == "-"); + + // -- Escape sequences -- + + assert(regex_match("foo 42 bar", `\d+`, m)); + assert(m.full == "42"); + + assert(regex_match("hello world", `\w+`, m)); + assert(m.full == "hello"); + + assert(regex_match("key: value", `\s+`, m)); + assert(m.full == " "); + + // negated escapes + assert(regex_match("abc 123", `\D+`, m)); + assert(m.full == "abc "); + + assert(regex_match("abc 123", `\S+`, m)); + assert(m.full == "abc"); + + assert(regex_match(" abc", `\W+`, m)); + assert(m.full == " "); + + // escaped special characters + assert(regex_match("a.b", `a\.b`, m)); + assert(m.full == "a.b"); + + assert(!regex_match("axb", `a\.b`, m)); + + assert(regex_match("(hi)", `\(hi\)`, m)); + assert(m.full == "(hi)"); + + assert(regex_match("a*b", `a\*b`, m)); + assert(m.full == "a*b"); + + assert(regex_match("a|b", `a\|b`, m)); + assert(m.full == "a|b"); + + assert(regex_match("a\\b", `a\\b`, m)); + assert(m.full == "a\\b"); + + assert(regex_match("[x]", `\[x\]`, m)); + assert(m.full == "[x]"); + + // -- Anchors -- + + assert(regex_match("hello", "^hello$", m)); + assert(m.full == "hello"); + + assert(!regex_match("say hello", "^hello", m)); + assert(regex_match("say hello", "hello$", m)); + assert(m.full == "hello"); + + assert(!regex_match("hello!", "^hello$", m)); + + // ^ on empty string + assert(regex_match("", "^$", m)); + assert(m.full == ""); + + // -- Simple groups (no alternation) -- + + assert(regex_match("abc", "(abc)", m)); + assert(m.full == "abc"); + assert(m.num_captures == 1); + assert(m.captures[0] == "abc"); + + assert(regex_match("abc", "a(b)c", m)); + assert(m.full == "abc"); + assert(m.captures[0] == "b"); + + // -- Alternation -- + + // two branches + assert(regex_match("true", "(true|false)", m)); + assert(m.captures[0] == "true"); + + assert(regex_match("false", "(true|false)", m)); + assert(m.captures[0] == "false"); + + assert(!regex_match("maybe", "^(true|false)$", m)); + + // three branches + assert(regex_match("red", "(red|green|blue)", m)); + assert(m.captures[0] == "red"); + + assert(regex_match("green", "(red|green|blue)", m)); + assert(m.captures[0] == "green"); + + assert(regex_match("blue", "(red|green|blue)", m)); + assert(m.captures[0] == "blue"); + + assert(!regex_match("yellow", "^(red|green|blue)$", m)); + + // alternation with shared prefix/suffix + assert(regex_match("foobar", "(foo|foobar)", m)); + // greedy: tries foo first, succeeds — but we're not anchored, so full match is "foo" + assert(m.captures[0] == "foo"); + + // alternation with surrounding literal + assert(regex_match("catdog", "cat(and|or)?dog", m)); // no "and"/"or", ? makes it optional + assert(m.full == "catdog"); + + assert(regex_match("catanddog", "cat(and|or)dog", m)); + assert(m.captures[0] == "and"); + + assert(regex_match("catordog", "cat(and|or)dog", m)); + assert(m.captures[0] == "or"); + + // -- Greedy quantifiers: *, +, ? -- + + // * — zero or more + assert(regex_match("aaa", "a*", m)); + assert(m.full == "aaa"); + + assert(regex_match("bbb", "a*", m)); // zero-length match at start + assert(m.full == ""); + + assert(regex_match("", "a*", m)); // empty string, zero-length match + assert(m.full == ""); + + // + — one or more + assert(regex_match("aaa", "a+", m)); + assert(m.full == "aaa"); + + assert(!regex_match("bbb", "a+", m)); + + assert(regex_match("baaab", "a+", m)); + assert(m.full == "aaa"); + + // ? — zero or one + assert(regex_match("colour", "colou?r", m)); + assert(m.full == "colour"); + + assert(regex_match("color", "colou?r", m)); + assert(m.full == "color"); + + // greedy * consumes as much as possible + assert(regex_match("bold", "<.*>", m)); + assert(m.full == "bold"); + + // -- Lazy quantifiers: *?, +?, ?? -- + + // lazy * — shortest match + assert(regex_match("bold", "<.*?>", m)); + assert(m.full == ""); + + // lazy + — at least one, but shortest + assert(regex_match("aaa", "a+?", m)); + assert(m.full == "a"); + + // lazy ? — prefer zero + assert(regex_match("ab", "a??b", m)); + assert(m.full == "ab"); // a?? tries empty first but needs 'b', backtracks to 'a' + + // -- Quantifier on groups -- + + // group with + + assert(regex_match("ababab", "(ab)+", m)); + assert(m.full == "ababab"); + // last capture of the repeated group + assert(m.captures[0] == "ab"); + + // group with * + assert(regex_match("xyzxyz", "(xyz)*", m)); + assert(m.full == "xyzxyz"); + + // group with ? + assert(regex_match("foobar", "(foo)?bar", m)); + assert(m.full == "foobar"); + assert(m.captures[0] == "foo"); + + assert(regex_match("bar", "(foo)?bar", m)); + assert(m.full == "bar"); + + // alternation group with quantifier + assert(regex_match("abcabc", "(abc|def)+", m)); + assert(m.full == "abcabc"); + + assert(regex_match("abcdef", "(abc|def)+", m)); + assert(m.full == "abcdef"); + + // -- Nested groups -- + + assert(regex_match("abc", "((a)(b)(c))", m)); + assert(m.num_captures == 4); + assert(m.captures[0] == "abc"); // outer group + assert(m.captures[1] == "a"); + assert(m.captures[2] == "b"); + assert(m.captures[3] == "c"); + + // nested with alternation + assert(regex_match("ab", "((a|x)(b|y))", m)); + assert(m.captures[0] == "ab"); + assert(m.captures[1] == "a"); + assert(m.captures[2] == "b"); + + // -- Quantifier + class interactions -- + + assert(regex_match("abc123def", "[a-z]+", m)); + assert(m.full == "abc"); + + assert(regex_match("abc123def", "[a-z]+?", m)); // lazy + assert(m.full == "a"); + + assert(regex_match("123", "[0-9]?", m)); + assert(m.full == "1"); + + assert(regex_match("abc", "[0-9]?", m)); // zero-length match + assert(m.full == ""); + + // -- Quantifier + escape interactions -- + + assert(regex_match(" \t ", `\s+`, m)); + assert(m.full == " \t "); + + assert(regex_match("hello123world", `\w+\d+\w+`, m)); + assert(m.full == "hello123world"); + + // -- Complex scraping patterns -- + + assert(regex_match("voltage: 52.3V", `voltage:\s*(\d+\.\d+)`, m)); + assert(m.captures[0] == "52.3"); + + assert(regex_match("active", `(\w+)`, m)); + assert(m.captures[0] == "active"); + + assert(regex_match("power: 1500W", `power:\s*(\d+)`, m)); + assert(m.captures[0] == "1500"); + + assert(regex_match("temp=25.6C", `temp=(\d+\.?\d*)`, m)); + assert(m.captures[0] == "25.6"); + + // multiple captures in a real pattern + assert(regex_match("error 404: not found", `(\w+)\s+(\d+):\s+(.+)`, m)); + assert(m.num_captures == 3); + assert(m.captures[0] == "error"); + assert(m.captures[1] == "404"); + assert(m.captures[2] == "not found"); + + // key=value extraction + assert(regex_match("host=192.168.1.1 port=502", `(\w+)=(\S+)`, m)); + assert(m.captures[0] == "host"); + assert(m.captures[1] == "192.168.1.1"); + + // CSV-like: extract fields + assert(regex_match("42,hello,true", `^(\d+),(\w+),(true|false)`, m)); + assert(m.captures[0] == "42"); + assert(m.captures[1] == "hello"); + assert(m.captures[2] == "true"); + + // -- Compile once, match many -- + + Regex re = regex_compile(`(\d+)\s*([A-Z]+)`); + assert(re.valid); + + assert(re.exec("reading: 42 V", m)); + assert(m.num_captures == 2); + assert(m.captures[0] == "42"); + assert(m.captures[1] == "V"); + + assert(re.exec("other: 100 MW", m)); + assert(m.captures[0] == "100"); + assert(m.captures[1] == "MW"); + + assert(!re.exec("no numbers here", m)); + + // -- Edge cases -- + + // empty text, empty pattern + assert(regex_match("", "", m)); + assert(m.full == ""); + + // empty pattern matches empty at start of any text + assert(regex_match("abc", "", m)); + assert(m.full == ""); + + // no captures + assert(regex_match("abc", `\w+`, m)); + assert(m.num_captures == 0); + + // unmatched parens should fail to compile + assert(!regex_compile("abc)").valid); + assert(!regex_compile("(abc").valid); + + // pattern with only anchors + assert(regex_match("", "^$", m)); + assert(!regex_match("x", "^$", m)); + + // .* at start — heavy backtracking but should work + assert(regex_match("the end", ".*end", m)); + assert(m.full == "the end"); + + // .* with capture + assert(regex_match("key: value here", `(\w+):\s*(.*)$`, m)); + assert(m.captures[0] == "key"); + assert(m.captures[1] == "value here"); + + // single char edge + assert(regex_match("x", "x", m)); + assert(m.full == "x"); + + assert(!regex_match("", "x", m)); + + // quantifier on dot + assert(regex_match("abc", ".+", m)); + assert(m.full == "abc"); + + assert(regex_match("a", ".+", m)); + assert(m.full == "a"); + + assert(!regex_match("", ".+", m)); + + assert(regex_match("", ".*", m)); + assert(m.full == ""); +} From 24ce60890aad5efc3370ce7893f4e7b678c17fcd Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 27 Mar 2026 22:54:00 +1000 Subject: [PATCH 106/138] Gracefully support 0-byte sends. --- src/urt/socket.d | 77 +++++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/src/urt/socket.d b/src/urt/socket.d index 16cc88e..2991934 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -307,7 +307,7 @@ Result send(Socket socket, MsgFlags flags, size_t* bytes_sent, const void[][] bu { version (Windows) { - uint sent = void; + uint sent; WSABUF[32] bufs = void; assert(buffers.length <= bufs.length, "Too many buffers!"); @@ -320,10 +320,12 @@ Result send(Socket socket, MsgFlags flags, size_t* bytes_sent, const void[][] bu bufs[n].buf = cast(char*)buffer.ptr; bufs[n++].len = cast(uint)buffer.length; } - - int rc = WSASend(socket.handle, bufs.ptr, n, &sent, /+map_message_flags(flags)+/ 0, null, null); // there are no meaningful flags on Windows - if (rc == SOCKET_ERROR) - return socket_getlasterror(); + if (n > 0) + { + int rc = WSASend(socket.handle, bufs.ptr, n, &sent, /+map_message_flags(flags)+/ 0, null, null); // there are no meaningful flags on Windows + if (rc == SOCKET_ERROR) + return socket_getlasterror(); + } if (bytes_sent) *bytes_sent = sent; return Result.success; @@ -353,7 +355,7 @@ Result sendto(Socket socket, const InetAddress* address, size_t* bytes_sent, con assert(sock_addr, "Invalid socket address"); } - uint sent = void; + uint sent; WSABUF[32] bufs = void; assert(buffers.length <= bufs.length, "Too many buffers!"); @@ -366,10 +368,12 @@ Result sendto(Socket socket, const InetAddress* address, size_t* bytes_sent, con bufs[n].buf = cast(char*)buffer.ptr; bufs[n++].len = cast(uint)buffer.length; } - - int r = WSASendTo(socket.handle, bufs.ptr, n, &sent, /+map_message_flags(flags)+/ 0, sock_addr, cast(int)addr_len, null, null); // there are no meaningful flags on Windows - if (r == SOCKET_ERROR) - return socket_getlasterror(); + if (n > 0) + { + int r = WSASendTo(socket.handle, bufs.ptr, n, &sent, /+map_message_flags(flags)+/ 0, sock_addr, cast(int)addr_len, null, null); // there are no meaningful flags on Windows + if (r == SOCKET_ERROR) + return socket_getlasterror(); + } if (bytes_sent) *bytes_sent = sent; return Result.success; @@ -391,7 +395,7 @@ Result sendmsg(Socket socket, const InetAddress* address, MsgFlags flags, const( version (Windows) { - uint sent = void; + uint sent; WSAMSG msg; WSABUF[32] bufs = void; assert(buffers.length <= bufs.length, "Too many buffers!"); @@ -405,21 +409,24 @@ Result sendmsg(Socket socket, const InetAddress* address, MsgFlags flags, const( bufs[n].buf = cast(char*)buffer.ptr; bufs[n++].len = cast(uint)buffer.length; } + if (n > 0) + { + msg.name = sock_addr; + msg.namelen = cast(int)addr_len; + msg.lpBuffers = bufs.ptr; + msg.dwBufferCount = n; + msg.Control.buf = cast(char*)control.ptr; + msg.Control.len = cast(uint)control.length; + msg.dwFlags = 0; - msg.name = sock_addr; - msg.namelen = cast(int)addr_len; - msg.lpBuffers = bufs.ptr; - msg.dwBufferCount = n; - msg.Control.buf = cast(char*)control.ptr; - msg.Control.len = cast(uint)control.length; - msg.dwFlags = 0; - - int rc = WSASendMsg(socket.handle, &msg, /+map_message_flags(flags)+/ 0, &sent, null, null); // there are no meaningful flags on Windows - if (rc == SOCKET_ERROR) - return socket_getlasterror(); + int rc = WSASendMsg(socket.handle, &msg, /+map_message_flags(flags)+/ 0, &sent, null, null); // there are no meaningful flags on Windows + if (rc == SOCKET_ERROR) + return socket_getlasterror(); + } } else { + ptrdiff_t sent; msghdr hdr; iovec[32] iov = void; assert(buffers.length <= iov.length, "Too many buffers!"); @@ -433,18 +440,20 @@ Result sendmsg(Socket socket, const InetAddress* address, MsgFlags flags, const( iov[n].iov_base = cast(void*)buffer.ptr; iov[n++].iov_len = buffer.length; } - - hdr.msg_name = sock_addr; - hdr.msg_namelen = cast(socklen_t)addr_len; - hdr.msg_iov = iov.ptr; - hdr.msg_iovlen = n; - hdr.msg_control = cast(void*)control.ptr; - hdr.msg_controllen = control.length; - hdr.msg_flags = 0; - - ptrdiff_t sent = _sendmsg(socket.handle, &hdr, map_message_flags(flags)); - if (sent < 0) - return socket_getlasterror(); + if (n > 0) + { + hdr.msg_name = sock_addr; + hdr.msg_namelen = cast(socklen_t)addr_len; + hdr.msg_iov = iov.ptr; + hdr.msg_iovlen = n; + hdr.msg_control = cast(void*)control.ptr; + hdr.msg_controllen = control.length; + hdr.msg_flags = 0; + + sent = _sendmsg(socket.handle, &hdr, map_message_flags(flags)); + if (sent < 0) + return socket_getlasterror(); + } } if (bytes_sent) *bytes_sent = sent; From 143d299143e446d9900c16727a1e6aad7df35127 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 25 Mar 2026 22:44:46 +1000 Subject: [PATCH 107/138] Make the GC alloc functions just use malloc (and leak!)... (useful for debugging) --- src/object.d | 8 +++-- src/urt/inet.d | 69 +++++++++++++++++++++++++---------------- src/urt/mem/package.d | 26 +++++++++++++--- src/urt/si/unit.d | 10 +++--- src/urt/string/string.d | 26 ++++++++++------ src/urt/time.d | 14 +++------ 6 files changed, 96 insertions(+), 57 deletions(-) diff --git a/src/object.d b/src/object.d index 86640ce..bbe7cee 100644 --- a/src/object.d +++ b/src/object.d @@ -708,7 +708,9 @@ ref Tarr _d_arrayappendT(Tarr : T[], T)(return ref scope Tarr x, scope Tarr y) @ T[] _d_newarrayT(T)(size_t length, bool isShared = false) @trusted { - assert(false, "new array requires druntime"); + import urt.mem : malloc; +// assert(false, "new array requires druntime"); + return (cast(T*)malloc(T.sizeof * length))[0 .. length]; } Tarr _d_newarraymTX(Tarr : U[], T, U)(size_t[] dims, bool isShared = false) @trusted @@ -718,7 +720,9 @@ Tarr _d_newarraymTX(Tarr : U[], T, U)(size_t[] dims, bool isShared = false) @tru T* _d_newitemT(T)() @trusted { - assert(false, "new item requires druntime"); + import urt.mem : malloc; +// assert(false, "new item requires druntime"); + return cast(T*)malloc(T.sizeof); } T _d_newThrowable(T)() @trusted diff --git a/src/urt/inet.d b/src/urt/inet.d index be1bddc..7f5af5a 100644 --- a/src/urt/inet.d +++ b/src/urt/inet.d @@ -163,14 +163,17 @@ nothrow @nogc: return offset; } - auto __debugOverview() + version (Windows) { - import urt.mem; - char[] buffer = cast(char[])tempAllocator.alloc(15); - ptrdiff_t len = toString(buffer, null, null); - return buffer[0 .. len]; + auto __debugOverview() + { + import urt.mem; + char[] buffer = debug_alloc!char(15); + ptrdiff_t len = toString(buffer, null, null); + return buffer[0 .. len]; + } + auto __debugExpanded() => b[]; } - auto __debugExpanded() => b[]; } @@ -355,14 +358,17 @@ nothrow @nogc: return offset; } - auto __debugOverview() + version (Windows) { - import urt.mem; - char[] buffer = cast(char[])tempAllocator.alloc(39); - ptrdiff_t len = toString(buffer, null, null); - return buffer[0 .. len]; + auto __debugOverview() + { + import urt.mem; + char[] buffer = debug_alloc!char(39); + ptrdiff_t len = toString(buffer, null, null); + return buffer[0 .. len]; + } + auto __debugExpanded() => s[]; } - auto __debugExpanded() => s[]; } struct IPNetworkAddress @@ -444,12 +450,15 @@ nothrow @nogc: return taken + t; } - auto __debugOverview() + version (Windows) { - import urt.mem; - char[] buffer = cast(char[])tempAllocator.alloc(18); - ptrdiff_t len = toString(buffer, null, null); - return buffer[0 .. len]; + auto __debugOverview() + { + import urt.mem; + char[] buffer = debug_alloc!char(18); + ptrdiff_t len = toString(buffer, null, null); + return buffer[0 .. len]; + } } } @@ -541,12 +550,15 @@ nothrow @nogc: return taken + t; } - auto __debugOverview() + version (Windows) { - import urt.mem; - char[] buffer = cast(char[])tempAllocator.alloc(42); - ptrdiff_t len = toString(buffer, null, null); - return buffer[0 .. len]; + auto __debugOverview() + { + import urt.mem; + char[] buffer = debug_alloc!char(42); + ptrdiff_t len = toString(buffer, null, null); + return buffer[0 .. len]; + } } } @@ -744,12 +756,15 @@ nothrow @nogc: return taken; } - auto __debugOverview() + version (Windows) { - import urt.mem; - char[] buffer = cast(char[])tempAllocator.alloc(47); - ptrdiff_t len = toString(buffer, null, null); - return buffer[0 .. len]; + auto __debugOverview() + { + import urt.mem; + char[] buffer = debug_alloc!char(47); + ptrdiff_t len = toString(buffer, null, null); + return buffer[0 .. len]; + } } } diff --git a/src/urt/mem/package.d b/src/urt/mem/package.d index ec4e82e..725be17 100644 --- a/src/urt/mem/package.d +++ b/src/urt/mem/package.d @@ -16,10 +16,10 @@ extern(C) { nothrow @nogc: - void* malloc(size_t size) @trusted; - void* calloc(size_t num, size_t size) @trusted; - void* realloc(void* ptr, size_t new_size) @trusted; - void free(void* ptr) @trusted; + void* malloc(size_t size) pure @trusted; + void* calloc(size_t num, size_t size) pure @trusted; + void* realloc(void* ptr, size_t new_size) pure @trusted; + void free(void* ptr) pure @trusted; void* memcpy(void* dest, const void* src, size_t n) pure; void* memmove(void* dest, const void* src, size_t n) pure; @@ -39,6 +39,24 @@ nothrow @nogc: // wchar_t* wcsncat(wchar_t* dest, const wchar_t* src, size_t n); } +T debug_alloc(T = void)() pure @trusted + if (is(T == class)) +{ + static T gc_alloc(size_t size) pure nothrow => new T; + return (cast(T function(size_t) pure nothrow @nogc)&gc_alloc)(size); +} +T* debug_alloc(T = void)() pure @trusted + if (!is(T == class)) +{ + static T* gc_alloc(size_t size) pure nothrow => new T; + return (cast(T* function(size_t) pure nothrow @nogc)&gc_alloc)(size); +} +T[] debug_alloc(T = void)(size_t size) pure @trusted +{ + static T[] gc_alloc(size_t size) pure nothrow => new T[size]; + return (cast(T[] function(size_t) pure nothrow @nogc)&gc_alloc)(size); +} + private: diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index a183d98..1fdbe2c 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -826,15 +826,15 @@ nothrow: size_t toHash() const pure => pack; - auto __debugOverview() + version (Windows) { - debug { - char[] buffer = new char[32]; + auto __debugOverview() + { + import urt.mem; + char[] buffer = debug_alloc!char(32); ptrdiff_t len = toString(buffer, null, null); return buffer[0 .. len]; } - else - return pack; } package: diff --git a/src/urt/string/string.d b/src/urt/string/string.d index 7313ca0..96414a4 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -389,10 +389,6 @@ nothrow @nogc: => ptr && ((cast(ushort*)ptr)[-1] >> 15) != 0; private: - auto __debugOverview() const pure { debug return ptr[0 .. length].debugExcapeString(); else return ptr[0 .. length]; } - auto __debugExpanded() const pure => ptr[0 .. length]; - auto __debugStringView() const pure => ptr[0 .. length]; - ushort* refCounter() const pure => ((cast(ushort*)ptr)[-1] >> 15) ? cast(ushort*)ptr - 2 : null; @@ -422,6 +418,13 @@ private: if (refCounted) *cast(ushort*)(ptr - 2) |= 0x8000; } + + version (Windows) + { + auto __debugOverview() const pure => ptr ? ptr[0 .. length].debugExcapeString() : null; + auto __debugExpanded() const pure => ptr ? ptr[0 .. length] : null; + auto __debugStringView() const pure => ptr ? ptr[0 .. length] : null; + } } unittest @@ -942,9 +945,12 @@ private: defaultAllocator().free(buffer[0 .. 4 + *cast(ushort*)buffer]); } - auto __debugOverview() const pure { debug return ptr[0 .. length].debugExcapeString(); else return ptr[0 .. length]; } - auto __debugExpanded() const pure => ptr[0 .. length]; - auto __debugStringView() const pure => ptr[0 .. length]; + version (Windows) + { + auto __debugOverview() const pure => ptr ? ptr[0 .. length].debugExcapeString() : null; + auto __debugExpanded() const pure => ptr ? ptr[0 .. length] : null; + auto __debugStringView() const pure => ptr ? ptr[0 .. length] : null; + } } unittest @@ -1134,11 +1140,11 @@ package(urt) void initStringAllocators() nothrow @nogc }; } -debug +version (Windows) { - char[] debugExcapeString(const char[] s) pure nothrow + char[] debugExcapeString(const char[] s) pure nothrow @nogc { - char[] t = new char[s.length*2]; + char[] t = debug_alloc!char(s.length*2); int d; foreach (i; 0 .. s.length) { diff --git a/src/urt/time.d b/src/urt/time.d index 43592d1..c32eeb6 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -140,17 +140,13 @@ pure nothrow @nogc: } } + version (Windows) auto __debugOverview() const { - debug - { - import urt.mem.temp; - char[] b = cast(char[])talloc(64); - ptrdiff_t len = toString(b, null, null); - return b[0..len]; - } - else - return appTime(this).as!"msecs"; + import urt.mem; + char[] b = debug_alloc!char(64); + ptrdiff_t len = toString(b, null, null); + return b[0..len]; } } From 880be43fe5629985ab0c444f59b6579f5a3571e9 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 27 Mar 2026 22:53:12 +1000 Subject: [PATCH 108/138] Add UUID --- src/urt/uuid.d | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/urt/uuid.d diff --git a/src/urt/uuid.d b/src/urt/uuid.d new file mode 100644 index 0000000..ce30b97 --- /dev/null +++ b/src/urt/uuid.d @@ -0,0 +1,90 @@ +module urt.uuid; + +import urt.conv : format_uint, parse_uint; +import urt.string.format : FormatArg; + +nothrow @nogc: + +enum GUID UUID(string s) = () { UUID g; ptrdiff_t n = g.fromString(s); assert(n == s.length, "Not a valid GUID/UUID"); return g; }(); + +struct GUID +{ +nothrow @nogc: +align(1): + uint data1; + ushort data2; + ushort data3; + ubyte[8] data4; + + bool opEquals(ref const GUID rh) const pure + => data1 == rh.data1 && data2 == rh.data2 && data3 == rh.data3 && data4 == rh.data4; + + bool opCast(T : bool)() const pure + => data1 != 0 || data2 != 0 || data3 != 0 || data4 != typeof(data4).init; + + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + ptrdiff_t toString(char[] buf, const(char)[], const(FormatArg)[]) const pure + { + import urt.string.ascii : hex_digits; + if (!buf.ptr) + return 36; + if (buf.length < 36) + return -1; + format_uint(data1, buf[0 .. 8], 16, 8, '0'); + buf[8] = '-'; + format_uint(data2, buf[9 .. 13], 16, 4, '0'); + buf[13] = '-'; + format_uint(data3, buf[14 .. 18], 16, 4, '0'); + buf[18] = '-'; + buf[19] = hex_digits[data4[0] >> 4]; + buf[20] = hex_digits[data4[0] & 0xf]; + buf[21] = hex_digits[data4[1] >> 4]; + buf[22] = hex_digits[data4[1] & 0xf]; + buf[23] = '-'; + buf[24] = hex_digits[data4[2] >> 4]; + buf[25] = hex_digits[data4[2] & 0xf]; + buf[26] = hex_digits[data4[3] >> 4]; + buf[27] = hex_digits[data4[3] & 0xf]; + buf[28] = hex_digits[data4[4] >> 4]; + buf[29] = hex_digits[data4[4] & 0xf]; + buf[30] = hex_digits[data4[5] >> 4]; + buf[31] = hex_digits[data4[5] & 0xf]; + buf[32] = hex_digits[data4[6] >> 4]; + buf[33] = hex_digits[data4[6] & 0xf]; + buf[34] = hex_digits[data4[7] >> 4]; + buf[35] = hex_digits[data4[7] & 0xf]; + return 36; + } + + ptrdiff_t fromString(const(char)[] s) pure + { + if (s.length < 36 || s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-') + return -1; + size_t n; + ulong d1 = s[0 .. 8].parse_uint(&n, 16); if (n != 8) return -1; + ulong d2 = s[9 .. 13].parse_uint(&n, 16); if (n != 4) return -1; + ulong d3 = s[14 .. 18].parse_uint(&n, 16); if (n != 4) return -1; + data1 = cast(uint)d1; + data2 = cast(ushort)d2; + data3 = cast(ushort)d3; + foreach (i; 0 .. 8) + { + size_t off = i < 2 ? 19 + i*2 : 20 + i*2; + ulong b = s[off .. off + 2].parse_uint(&n, 16); + if (n != 2) return -1; + data4[i] = cast(ubyte)b; + } + return 36; + } + + debug auto __debugOverview() + { + import urt.mem; + char[] buf = debug_alloc!char(36); + toString(buf, null, null); + return buf[0 .. 36]; + } +} + +static assert(GUID.sizeof == 16); +static assert(GUID.alignof == 1); From 52efc039d3168e992e8dcda0c0508ec4c140135d Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 17 Mar 2026 01:13:01 +1000 Subject: [PATCH 109/138] Stub out support for bare-metal builds... --- src/object.d | 62 +++--- src/urt/array.d | 6 +- src/urt/crypto/pki.d | 21 +- src/urt/dbg.d | 33 +++ src/urt/fibre.d | 376 +++++++++++++++++++++++++++++++++- src/urt/file.d | 60 +++--- src/urt/internal/exception.d | 69 ++++--- src/urt/math.d | 5 +- src/urt/mem/package.d | 2 +- src/urt/mem/string.d | 9 +- src/urt/package.d | 158 +++++++------- src/urt/processor.d | 139 ++++++++++++- src/urt/result.d | 28 +++ src/urt/socket.d | 114 ++++++++++- src/urt/string/tailstring.d | 9 +- src/urt/system.d | 4 + src/urt/time.d | 385 +++++++++++++++++++++++------------ 17 files changed, 1172 insertions(+), 308 deletions(-) diff --git a/src/object.d b/src/object.d index bbe7cee..1d3d869 100644 --- a/src/object.d +++ b/src/object.d @@ -43,6 +43,8 @@ else version (Posix) alias dchar wchar_t; else version (WASI) alias dchar wchar_t; +else version (FreeStanding) + alias dchar wchar_t; alias string = immutable(char)[]; alias wstring = immutable(wchar)[]; @@ -841,13 +843,13 @@ string _d_assert_fail(A...)(const scope string comp, auto ref const scope A a) // These are referenced by compiler-generated code even in debug builds. // ────────────────────────────────────────────────────────────────────── -extern (C) void _d_assert_msg(string msg, string file, uint line) nothrow @nogc +extern(C) void _d_assert_msg(string msg, string file, uint line) nothrow @nogc { import urt.exception : assert_handler; assert_handler()(file, line, msg); } -extern (C) void _d_assertp(immutable(char)* file, uint line) nothrow @nogc @trusted +extern(C) void _d_assertp(immutable(char)* file, uint line) nothrow @nogc @trusted { import urt.mem : strlen; import urt.exception : assert_handler; @@ -855,31 +857,31 @@ extern (C) void _d_assertp(immutable(char)* file, uint line) nothrow @nogc @trus assert_handler()(f, line, null); } -extern (C) void _d_assert(string file, uint line) nothrow @nogc +extern(C) void _d_assert(string file, uint line) nothrow @nogc { import urt.exception : assert_handler; assert_handler()(file, line, null); } -extern (C) void _d_arraybounds_indexp(string file, uint line, size_t index, size_t length) nothrow @nogc +extern(C) void _d_arraybounds_indexp(string file, uint line, size_t index, size_t length) nothrow @nogc { import urt.exception : assert_handler; assert_handler()(file, line, "array index out of bounds"); } -extern (C) void _d_arraybounds_slicep(string file, uint line, size_t lower, size_t upper, size_t length) nothrow @nogc +extern(C) void _d_arraybounds_slicep(string file, uint line, size_t lower, size_t upper, size_t length) nothrow @nogc { import urt.exception : assert_handler; assert_handler()(file, line, "array slice out of bounds"); } -extern (C) void _d_arrayboundsp(string file, uint line) nothrow @nogc +extern(C) void _d_arrayboundsp(string file, uint line) nothrow @nogc { import urt.exception : assert_handler; assert_handler()(file, line, "array index out of bounds"); } -extern (C) void _d_arraybounds(string file, uint line) nothrow @nogc +extern(C) void _d_arraybounds(string file, uint line) nothrow @nogc { import urt.exception : assert_handler; assert_handler()(file, line, "array index out of bounds"); @@ -887,7 +889,7 @@ extern (C) void _d_arraybounds(string file, uint line) nothrow @nogc // Unittest assert hooks — the compiler generates these for assert() inside // unittest blocks instead of the regular _d_assertp/_d_assert_msg. -extern (C) void _d_unittestp(immutable(char)* file, uint line) nothrow @nogc @trusted +extern(C) void _d_unittestp(immutable(char)* file, uint line) nothrow @nogc @trusted { import urt.mem : strlen; import urt.exception : assert_handler; @@ -895,13 +897,13 @@ extern (C) void _d_unittestp(immutable(char)* file, uint line) nothrow @nogc @tr assert_handler()(f, line, "unittest assertion failure"); } -extern (C) void _d_unittest_msg(string msg, string file, uint line) nothrow @nogc @trusted +extern(C) void _d_unittest_msg(string msg, string file, uint line) nothrow @nogc @trusted { import urt.exception : assert_handler; assert_handler()(file, line, msg); } -extern (C) void _d_unittest(string file, uint line) nothrow @nogc +extern(C) void _d_unittest(string file, uint line) nothrow @nogc { import urt.exception : assert_handler; assert_handler()(file, line, "unittest assertion failure"); @@ -910,7 +912,7 @@ extern (C) void _d_unittest(string file, uint line) nothrow @nogc // GC allocation hook — compiler lowers `new` to this. In our @nogc world // it should never be called from production code; provided so unittest // blocks that accidentally use `new` can at least link. -extern (C) void* _d_allocmemory(size_t sz) nothrow @nogc @trusted +extern(C) void* _d_allocmemory(size_t sz) nothrow @nogc @trusted { import urt.mem : malloc; return malloc(sz); @@ -929,7 +931,7 @@ version (LDC) // --- Bounds checks (LDC variants without 'p' suffix) ------------------- -extern (C) void _d_arraybounds_index(string file, uint line, size_t index, size_t length) nothrow @nogc +extern(C) void _d_arraybounds_index(string file, uint line, size_t index, size_t length) nothrow @nogc { import urt.exception : assert_handler; if (auto handler = assert_handler) @@ -938,7 +940,7 @@ extern (C) void _d_arraybounds_index(string file, uint line, size_t index, size_ _halt(); } -extern (C) void _d_arraybounds_slice(string file, uint line, size_t lower, size_t upper, size_t length) nothrow @nogc +extern(C) void _d_arraybounds_slice(string file, uint line, size_t lower, size_t upper, size_t length) nothrow @nogc { import urt.exception : assert_handler; if (auto handler = assert_handler) @@ -950,7 +952,7 @@ extern (C) void _d_arraybounds_slice(string file, uint line, size_t lower, size_ // --- Array slice copy (LDC emits this for non-elaborate arr[] = other[]) - // Bounds-checked memcpy: verifies dstlen == srclen, then copies raw bytes. -extern (C) void _d_array_slice_copy(void* dst, size_t dstlen, void* src, size_t srclen, size_t elemsize) nothrow @nogc @trusted +extern(C) void _d_array_slice_copy(void* dst, size_t dstlen, void* src, size_t srclen, size_t elemsize) nothrow @nogc @trusted { assert(dstlen == srclen, "array slice lengths don't match for copy"); import urt.mem : memcpy; @@ -959,7 +961,7 @@ extern (C) void _d_array_slice_copy(void* dst, size_t dstlen, void* src, size_t // --- Class allocation and casting (old-style extern C) ----------------- -extern (C) void* _d_allocclass(TypeInfo_Class ci) nothrow @nogc @trusted +extern(C) void* _d_allocclass(TypeInfo_Class ci) nothrow @nogc @trusted { import urt.mem : malloc, memcpy; auto init = ci.initializer; @@ -969,7 +971,7 @@ extern (C) void* _d_allocclass(TypeInfo_Class ci) nothrow @nogc @trusted return p; } -extern (C) Object _d_dynamic_cast(Object o, TypeInfo_Class c) nothrow @nogc @trusted +extern(C) Object _d_dynamic_cast(Object o, TypeInfo_Class c) nothrow @nogc @trusted { // Traverse classinfo chain to check if o is-a c if (o is null) return null; @@ -983,7 +985,7 @@ extern (C) Object _d_dynamic_cast(Object o, TypeInfo_Class c) nothrow @nogc @tru return null; } -extern (C) void* _d_interface_cast(void* p, TypeInfo_Class c) nothrow @nogc @trusted +extern(C) void* _d_interface_cast(void* p, TypeInfo_Class c) nothrow @nogc @trusted { // TODO: full interface casting requires traversing the Interface[] table // in the target object's classinfo to find the right vtable offset. @@ -993,7 +995,7 @@ extern (C) void* _d_interface_cast(void* p, TypeInfo_Class c) nothrow @nogc @tru // --- Struct array equality (old-style) --------------------------------- -extern (C) int _adEq2(void[] a1, void[] a2, TypeInfo ti) nothrow @nogc @trusted +extern(C) int _adEq2(void[] a1, void[] a2, TypeInfo ti) nothrow @nogc @trusted { if (a1.length != a2.length) return 0; import urt.mem : memcmp; @@ -1004,7 +1006,7 @@ extern (C) int _adEq2(void[] a1, void[] a2, TypeInfo ti) nothrow @nogc @trusted // --- Old-style array allocation (extern C) ------------------------------ -extern (C) void[] _d_newarrayT(const TypeInfo ti, size_t length) nothrow @nogc @trusted +extern(C) void[] _d_newarrayT(const TypeInfo ti, size_t length) nothrow @nogc @trusted { import urt.mem : calloc; auto elemsize = ti.next ? ti.next.tsize : 1; @@ -1012,7 +1014,7 @@ extern (C) void[] _d_newarrayT(const TypeInfo ti, size_t length) nothrow @nogc @ return p[0 .. length * elemsize]; } -extern (C) void[] _d_newarrayiT(const TypeInfo ti, size_t length) nothrow @nogc @trusted +extern(C) void[] _d_newarrayiT(const TypeInfo ti, size_t length) nothrow @nogc @trusted { import urt.mem : calloc; auto elemsize = ti.next ? ti.next.tsize : 1; @@ -1020,7 +1022,7 @@ extern (C) void[] _d_newarrayiT(const TypeInfo ti, size_t length) nothrow @nogc return p[0 .. length * elemsize]; } -extern (C) void[] _d_newarrayU(const TypeInfo ti, size_t length) nothrow @nogc @trusted +extern(C) void[] _d_newarrayU(const TypeInfo ti, size_t length) nothrow @nogc @trusted { import urt.mem : malloc; auto elemsize = ti.next ? ti.next.tsize : 1; @@ -1081,7 +1083,7 @@ template _d_delstructImpl(T) } } -nothrow @nogc @trusted pure extern (C) void _d_delThrowable(scope Throwable) {} +nothrow @nogc @trusted pure extern(C) void _d_delThrowable(scope Throwable) {} // ────────────────────────────────────────────────────────────────────── // _arrayOp — compiler hook for vectorized array slice operations. @@ -1586,14 +1588,12 @@ const: @property string name() nothrow @nogc @trusted { - if (flags & MIname) - { - auto p = cast(immutable char*)addr_of(MIname); - size_t len = 0; - while (p[len] != 0) ++len; - return p[0 .. len]; - } - return null; + // LDC always emits the name after the packed fields, even without + // setting MIname. addr_of(MIname) handles this (see `true ||` guard). + auto p = cast(immutable char*)addr_of(MIname); + size_t len = 0; + while (p[len] != 0) ++len; + return p[0 .. len]; } } @@ -1671,7 +1671,7 @@ private struct Bits128 { ulong[2] v; } private struct Bits80 { ubyte[real.sizeof] v; } private struct Bits160 { ubyte[2 * real.sizeof] v; } -extern (C) nothrow @nogc @trusted +extern(C) nothrow @nogc @trusted { short* _memset16(short* p, short value, size_t count) { diff --git a/src/urt/array.d b/src/urt/array.d index 51360f6..d2e9acb 100644 --- a/src/urt/array.d +++ b/src/urt/array.d @@ -838,8 +838,10 @@ private: // TODO: i'm sure we can imagine a better heuristic... return i > 16 ? i * 2 : 16; } - - auto __debugExpanded() const pure => ptr[0 .. _length]; + version (Windows) + { + auto __debugExpanded() const pure => ptr[0 .. _length]; + } } // SharedArray is a ref-counted array which can be distributed diff --git a/src/urt/crypto/pki.d b/src/urt/crypto/pki.d index 470a9c3..40bd161 100644 --- a/src/urt/crypto/pki.d +++ b/src/urt/crypto/pki.d @@ -24,6 +24,11 @@ nothrow @nogc: { mbedtls_pk_context pk; } + else version (FreeStanding) + { + // PKI backend not yet implemented for bare-metal + void* _stub; + } else static assert(false, "TODO"); @@ -33,6 +38,8 @@ nothrow @nogc: return hcng !is null; else version (Posix) return pk.pk_info !is null; + else + return false; } } @@ -104,7 +111,7 @@ Result generate_keypair(out KeyPair kp) return Result.success; } else - static assert(0, "Not implemented"); + assert(0, "PKI: generate_keypair not implemented for this platform"); } void free_keypair(ref KeyPair kp) @@ -385,7 +392,7 @@ Result load_certificate(const(ubyte)[] cert_data, out CertRef cert) return Result.success; } else - static assert(0, "Not implemented"); + assert(0, "PKI: not implemented for this platform"); } Result associate_key(ref CertRef cert, ref KeyPair key) @@ -527,7 +534,7 @@ Result associate_key(ref CertRef cert, ref KeyPair key) return Result.success; } else - static assert(0, "Not implemented"); + assert(0, "PKI: not implemented for this platform"); } void free_cert(ref CertRef cert) @@ -629,7 +636,7 @@ Result sign_hash(ref KeyPair kp, const(ubyte)[] hash, out Array!ubyte signature) return Result.success; } else - static assert(0, "Not implemented"); + assert(0, "PKI: not implemented for this platform"); } Result export_public_key_raw(ref KeyPair kp, out Array!ubyte x, out Array!ubyte y) @@ -683,7 +690,7 @@ Result export_public_key_raw(ref KeyPair kp, out Array!ubyte x, out Array!ubyte return Result.success; } else - static assert(0, "Not implemented"); + assert(0, "PKI: not implemented for this platform"); } @@ -790,7 +797,7 @@ Result export_private_key(ref KeyPair kp, out Array!ubyte key_out) return build_ec_sec1_der(d, xy_buf[1 .. 33], xy_buf[33 .. 65], key_out); } else - static assert(0, "Not implemented"); + assert(0, "PKI: not implemented for this platform"); } Result import_private_key(const(ubyte)[] key_data, out KeyPair kp) @@ -866,7 +873,7 @@ Result import_private_key(const(ubyte)[] key_data, out KeyPair kp) return Result.success; } else - static assert(0, "Not implemented"); + assert(0, "PKI: not implemented for this platform"); } private: diff --git a/src/urt/dbg.d b/src/urt/dbg.d index 1b0465e..d5458db 100644 --- a/src/urt/dbg.d +++ b/src/urt/dbg.d @@ -55,5 +55,38 @@ else version (ARM) } } } +else version (RISCV64) +{ + pragma(inline, true) + extern(C) void breakpoint() pure nothrow @nogc + { + debug asm pure nothrow @nogc + { + "ebreak"; + } + } +} +else version (RISCV32) +{ + pragma(inline, true) + extern(C) void breakpoint() pure nothrow @nogc + { + debug asm pure nothrow @nogc + { + "ebreak"; + } + } +} +else version (Xtensa) +{ + pragma(inline, true) + extern(C) void breakpoint() pure nothrow @nogc + { + debug asm pure nothrow @nogc + { + "break 1, 15"; + } + } +} else static assert(0, "TODO: Unsupported architecture"); diff --git a/src/urt/fibre.d b/src/urt/fibre.d index 66253fb..0391f9a 100644 --- a/src/urt/fibre.d +++ b/src/urt/fibre.d @@ -753,10 +753,13 @@ else pragma(inline, false) extern(C) void co_swap(cothread_t newCtx, cothread_t oldCtx) @naked { - // just for thumb-1, thumb-2 can to the 32bit thing below.. somehow... apparently...? + // thumb-mode: compiler is generating Thumb instructions (Cortex-M micros) + // thumb2: ISA supports Thumb-2 (true for both Cortex-M4+ and Cortex-A7+) + // We check thumb (active mode), not thumb2 (capability), to avoid + // using the Thumb path on full ARM cores that can use stmia with sp. static if (ProcFeatures.thumb && !ProcFeatures.thumb2) { - static assert(false, "TODO: this needs to be tested somehow... thumb instructions are offset by 1 byte."); + static assert(false, "TODO: Thumb-1 context switch needs testing"); asm nothrow @nogc { ` @@ -778,7 +781,29 @@ else ldmia r0!, {r4-r7} ldmia r1!, {pc} ` - // bx lr ; TODO: why is this even here? the prior instruction loads `pc`... maybe it's a hint to the branch predictor? +// bx lr ; TODO: why is this even here? the prior instruction loads `pc`... maybe it's a hint to the branch predictor? + : // no outputs + : // "r"(newCtx), "r"(oldCtx) // function is @naked, so the ABI takes care of this + : "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "sp", "lr", "memory"; + } + } + else static if (ProcFeatures.thumb) + { + // Thumb mode (Cortex-M): SP cannot appear in stm/ldm register lists. + // Layout: [r4..r11](0-28), [sp](32), [lr/pc](36) — matches co_init_stack + asm nothrow @nogc + { + ` + stmia r1!, {r4-r11} + mov r2, sp + str r2, [r1] + str lr, [r1, #4] + ldmia r0!, {r4-r11} + ldr r2, [r0] + ldr r3, [r0, #4] + mov sp, r2 + bx r3 + ` : // no outputs : // "r"(newCtx), "r"(oldCtx) // function is @naked, so the ABI takes care of this : "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "sp", "lr", "memory"; @@ -786,6 +811,7 @@ else } else { + // ARM mode: SP can appear in stm/ldm asm nothrow @nogc { ` @@ -852,6 +878,350 @@ else } } } + else version (RISCV64) + { + // RISC-V 64-bit: callee-saved sp, ra, s0-s11 (14 regs × 8 bytes) + // With D extension: also fs0-fs11 (12 regs × 8 bytes) + version (D_HardFloat) + enum SaveStateLen = 26; // 14 integer + 12 FP + else + enum SaveStateLen = 14; + + void co_init_stack(void* base, void* top, coentry_t entry) + { + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); + + void** p = cast(void**)base; + p[0] = cast(void*)top; // starting sp + p[1] = entry; // starting ra (entry point) + p[2] = cast(void*)top; // starting s0 (frame pointer) + } + + version (D_HardFloat) + { + pragma(inline, false) + extern(C) void co_swap(cothread_t newCtx, cothread_t oldCtx) @naked + { + asm nothrow @nogc + { + ` + sd sp, 0(a1) + sd ra, 8(a1) + sd s0, 16(a1) + sd s1, 24(a1) + sd s2, 32(a1) + sd s3, 40(a1) + sd s4, 48(a1) + sd s5, 56(a1) + sd s6, 64(a1) + sd s7, 72(a1) + sd s8, 80(a1) + sd s9, 88(a1) + sd s10, 96(a1) + sd s11, 104(a1) + fsd fs0, 112(a1) + fsd fs1, 120(a1) + fsd fs2, 128(a1) + fsd fs3, 136(a1) + fsd fs4, 144(a1) + fsd fs5, 152(a1) + fsd fs6, 160(a1) + fsd fs7, 168(a1) + fsd fs8, 176(a1) + fsd fs9, 184(a1) + fsd fs10, 192(a1) + fsd fs11, 200(a1) + ld sp, 0(a0) + ld s0, 16(a0) + ld s1, 24(a0) + ld s2, 32(a0) + ld s3, 40(a0) + ld s4, 48(a0) + ld s5, 56(a0) + ld s6, 64(a0) + ld s7, 72(a0) + ld s8, 80(a0) + ld s9, 88(a0) + ld s10, 96(a0) + ld s11, 104(a0) + fld fs0, 112(a0) + fld fs1, 120(a0) + fld fs2, 128(a0) + fld fs3, 136(a0) + fld fs4, 144(a0) + fld fs5, 152(a0) + fld fs6, 160(a0) + fld fs7, 168(a0) + fld fs8, 176(a0) + fld fs9, 184(a0) + fld fs10, 192(a0) + fld fs11, 200(a0) + ld ra, 8(a0) + ret + ` + : // no outputs + : // a0=newCtx, a1=oldCtx (@naked, ABI handles register assignment) + : "memory"; + } + } + } + else + { + pragma(inline, false) + extern(C) void co_swap(cothread_t newCtx, cothread_t oldCtx) @naked + { + asm nothrow @nogc + { + ` + sd sp, 0(a1) + sd ra, 8(a1) + sd s0, 16(a1) + sd s1, 24(a1) + sd s2, 32(a1) + sd s3, 40(a1) + sd s4, 48(a1) + sd s5, 56(a1) + sd s6, 64(a1) + sd s7, 72(a1) + sd s8, 80(a1) + sd s9, 88(a1) + sd s10, 96(a1) + sd s11, 104(a1) + ld sp, 0(a0) + ld s0, 16(a0) + ld s1, 24(a0) + ld s2, 32(a0) + ld s3, 40(a0) + ld s4, 48(a0) + ld s5, 56(a0) + ld s6, 64(a0) + ld s7, 72(a0) + ld s8, 80(a0) + ld s9, 88(a0) + ld s10, 96(a0) + ld s11, 104(a0) + ld ra, 8(a0) + ret + ` + : // no outputs + : // a0=newCtx, a1=oldCtx (@naked, ABI handles register assignment) + : "memory"; + } + } + } + } + else version (RISCV32) + { + // SaveStateLen must be defined before co_active_buffer (line 444) + version (RISCV32E) + enum SaveStateLen = 4; // RV32E: sp, ra, s0-s1 + else version (D_HardFloat) + enum SaveStateLen = 26; // RV32I: 14 integer + 12 FP + else + enum SaveStateLen = 14; // RV32I: sp, ra, s0-s11 + + version (RISCV32E) + { + void co_init_stack(void* base, void* top, coentry_t entry) + { + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); + + void** p = cast(void**)base; + p[0] = cast(void*)top; // starting sp + p[1] = entry; // starting ra (entry point) + p[2] = cast(void*)top; // starting s0 (frame pointer) + } + + pragma(inline, false) + extern(C) void co_swap(cothread_t newCtx, cothread_t oldCtx) @naked + { + asm nothrow @nogc + { + ` + sw sp, 0(a1) + sw ra, 4(a1) + sw s0, 8(a1) + sw s1, 12(a1) + lw sp, 0(a0) + lw s0, 8(a0) + lw s1, 12(a0) + lw ra, 4(a0) + ret + ` + : // no outputs + : // a0=newCtx, a1=oldCtx (@naked, ABI handles register assignment) + : "memory"; + } + } + } + else + { + + void co_init_stack(void* base, void* top, coentry_t entry) + { + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); + + void** p = cast(void**)base; + p[0] = cast(void*)top; // starting sp + p[1] = entry; // starting ra (entry point) + p[2] = cast(void*)top; // starting s0 (frame pointer) + } + + version (D_HardFloat) + { + pragma(inline, false) + extern(C) void co_swap(cothread_t newCtx, cothread_t oldCtx) @naked + { + asm nothrow @nogc + { + ` + sw sp, 0(a1) + sw ra, 4(a1) + sw s0, 8(a1) + sw s1, 12(a1) + sw s2, 16(a1) + sw s3, 20(a1) + sw s4, 24(a1) + sw s5, 28(a1) + sw s6, 32(a1) + sw s7, 36(a1) + sw s8, 40(a1) + sw s9, 44(a1) + sw s10, 48(a1) + sw s11, 52(a1) + fsw fs0, 56(a1) + fsw fs1, 60(a1) + fsw fs2, 64(a1) + fsw fs3, 68(a1) + fsw fs4, 72(a1) + fsw fs5, 76(a1) + fsw fs6, 80(a1) + fsw fs7, 84(a1) + fsw fs8, 88(a1) + fsw fs9, 92(a1) + fsw fs10, 96(a1) + fsw fs11, 100(a1) + lw sp, 0(a0) + lw s0, 8(a0) + lw s1, 12(a0) + lw s2, 16(a0) + lw s3, 20(a0) + lw s4, 24(a0) + lw s5, 28(a0) + lw s6, 32(a0) + lw s7, 36(a0) + lw s8, 40(a0) + lw s9, 44(a0) + lw s10, 48(a0) + lw s11, 52(a0) + flw fs0, 56(a0) + flw fs1, 60(a0) + flw fs2, 64(a0) + flw fs3, 68(a0) + flw fs4, 72(a0) + flw fs5, 76(a0) + flw fs6, 80(a0) + flw fs7, 84(a0) + flw fs8, 88(a0) + flw fs9, 92(a0) + flw fs10, 96(a0) + flw fs11, 100(a0) + lw ra, 4(a0) + ret + ` + : // no outputs + : // a0=newCtx, a1=oldCtx (@naked, ABI handles register assignment) + : "memory"; + } + } + } + else + { + pragma(inline, false) + extern(C) void co_swap(cothread_t newCtx, cothread_t oldCtx) @naked + { + asm nothrow @nogc + { + ` + sw sp, 0(a1) + sw ra, 4(a1) + sw s0, 8(a1) + sw s1, 12(a1) + sw s2, 16(a1) + sw s3, 20(a1) + sw s4, 24(a1) + sw s5, 28(a1) + sw s6, 32(a1) + sw s7, 36(a1) + sw s8, 40(a1) + sw s9, 44(a1) + sw s10, 48(a1) + sw s11, 52(a1) + lw sp, 0(a0) + lw s0, 8(a0) + lw s1, 12(a0) + lw s2, 16(a0) + lw s3, 20(a0) + lw s4, 24(a0) + lw s5, 28(a0) + lw s6, 32(a0) + lw s7, 36(a0) + lw s8, 40(a0) + lw s9, 44(a0) + lw s10, 48(a0) + lw s11, 52(a0) + lw ra, 4(a0) + ret + ` + : // no outputs + : // a0=newCtx, a1=oldCtx (@naked, ABI handles register assignment) + : "memory"; + } + } + } + } + } + else version (Xtensa) + { + // Xtensa call0 ABI: callee-saved a0 (return addr), a1 (sp), a12-a15 + // TODO: if windowed ABI is used, need register window spill (entry/retw) + enum SaveStateLen = 6; + + void co_init_stack(void* base, void* top, coentry_t entry) + { + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); + + void** p = cast(void**)base; + p[0] = cast(void*)top; // starting a1 (sp) + p[1] = entry; // starting a0 (return address) + } + + pragma(inline, false) + extern(C) void co_swap(cothread_t newCtx, cothread_t oldCtx) @naked + { + asm nothrow @nogc + { + ` + s32i a1, a3, 0 + s32i a0, a3, 4 + s32i a12, a3, 8 + s32i a13, a3, 12 + s32i a14, a3, 16 + s32i a15, a3, 20 + l32i a1, a2, 0 + l32i a12, a2, 8 + l32i a13, a2, 12 + l32i a14, a2, 16 + l32i a15, a2, 20 + l32i a0, a2, 4 + ret + ` + : // no outputs + : // a2=newCtx, a3=oldCtx (@naked, call0 ABI) + : "memory"; + } + } + } else static assert(false, "TODO: implement for other architectures!"); } diff --git a/src/urt/file.d b/src/urt/file.d index b5a232c..c860ed4 100644 --- a/src/urt/file.d +++ b/src/urt/file.d @@ -44,6 +44,10 @@ else version (Posix) enum POSIX_FADV_SEQUENTIAL = 2; extern(C) int posix_fadvise(int fd, off_t offset, off_t len, int advice) nothrow @nogc; } +else version (FreeStanding) +{ + // No filesystem on bare-metal +} else { static assert(0, "Not implemented"); @@ -107,8 +111,10 @@ struct File void* handle = INVALID_HANDLE_VALUE; else version (Posix) int fd = -1; + else version (FreeStanding) + int fd = -1; else - static assert(0, "Not implemented"); + static assert(0, "File: not implemented for this platform"); } bool file_exists(const(char)[] path) @@ -125,7 +131,7 @@ bool file_exists(const(char)[] path) return stat(path.tstringz, &st) == 0 && S_ISREG(st.st_mode); } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); } Result delete_file(const(char)[] path) @@ -141,7 +147,7 @@ Result delete_file(const(char)[] path) return errno_result(); } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); return Result.success; } @@ -160,7 +166,7 @@ Result rename_file(const(char)[] oldPath, const(char)[] newPath) return posix_result(result); } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); return Result.success; } @@ -178,7 +184,7 @@ Result copy_file(const(char)[] oldPath, const(char)[] newPath, bool overwriteExi assert(false); } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); return Result.success; } @@ -231,7 +237,7 @@ Result get_path(ref const File file, ref char[] buffer) buffer = buffer[0..r]; } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); return Result.success; } @@ -268,7 +274,7 @@ Result get_file_attributes(const(char)[] path, out FileAttributes outAttributes) assert(false); } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); return Result.success; } @@ -312,7 +318,7 @@ Result get_attributes(ref const File file, out FileAttributes outAttributes) assert(false); } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); return InternalResult.unsupported; } @@ -352,20 +358,24 @@ Result save_file(const(char)[] path, const(void)[] data) Result create_directory(const(char)[] path) { + Result r; version (Windows) { if (CreateDirectoryW(path.twstringz, null)) return Result.success; - Result r = getlasterror_result(); + r = getlasterror_result(); } else version (Posix) { if (!core.sys.posix.sys.stat.mkdir(tconcat(path, "\0").ptr, 493 /* 0755 */) != 0) return Result.success; - Result r = errno_result(); + r = errno_result(); } else - static assert(0, "Not implemented"); + { + assert(0, "File: not implemented for this platform"); + return InternalResult.unsupported; + } if (r == InternalResult.already_exists) return Result.success; @@ -503,7 +513,7 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags lseek(file.fd, 0, SEEK_END); } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); return Result.success; } @@ -515,7 +525,7 @@ bool is_open(ref const File file) else version (Posix) return file.fd != -1; else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); } void close(ref File file) @@ -535,7 +545,7 @@ void close(ref File file) file.fd = -1; } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); } ulong get_size(ref const File file) @@ -555,7 +565,7 @@ ulong get_size(ref const File file) return fs.st_size; } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); } Result set_size(ref File file, ulong size) @@ -604,7 +614,7 @@ Result set_size(ref File file, ulong size) return errno_result(); } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); return Result.success; } @@ -621,7 +631,7 @@ ulong get_pos(ref const File file) else version (Posix) return lseek(file.fd, 0, SEEK_CUR); else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); } Result set_pos(ref File file, ulong offset) @@ -640,7 +650,7 @@ Result set_pos(ref File file, ulong offset) return errno_result(); } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); return Result.success; } @@ -666,7 +676,7 @@ Result read(ref File file, void[] buffer, out size_t bytesRead) bytesRead = n; } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); return Result.success; } @@ -698,7 +708,7 @@ Result read_at(ref File file, void[] buffer, ulong offset, out size_t bytesRead) bytesRead = n; } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); return Result.success; } @@ -719,7 +729,7 @@ Result write(ref File file, const(void)[] data, out size_t bytesWritten) bytesWritten = n; } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); return Result.success; } @@ -747,7 +757,7 @@ Result write_at(ref File file, const(void)[] data, ulong offset, out size_t byte bytesWritten = n; } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); return Result.success; } @@ -764,7 +774,7 @@ Result flush(ref File file) return errno_result(); } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); return Result.success; } @@ -799,7 +809,7 @@ FileResult file_result(Result result) } } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); } Result get_temp_filename(ref char[] buffer, const(char)[] dstDir, const(char)[] prefix) @@ -833,7 +843,7 @@ Result get_temp_filename(ref char[] buffer, const(char)[] dstDir, const(char)[] return r; } else - static assert(0, "Not implemented"); + assert(0, "File: not implemented for this platform"); return Result.success; } diff --git a/src/urt/internal/exception.d b/src/urt/internal/exception.d index b6ed498..2fc112d 100644 --- a/src/urt/internal/exception.d +++ b/src/urt/internal/exception.d @@ -110,6 +110,10 @@ extern(C) void _d_createTrace(Throwable t, void*) nothrow @nogc @trusted } _tls_trace.length = n; } + else version (FreeStanding) + { + // No stack trace on bare-metal + } else { // ARM, AArch64, RISC-V, etc. — use _Unwind_Backtrace @@ -139,6 +143,8 @@ extern(C) void _d_printLastTrace(Throwable t) nothrow @nogc @trusted version (Windows) dbghelp_print_trace(_tls_trace.addrs[0 .. _tls_trace.length]); + else version (FreeStanding) + {} else posix_print_trace(_tls_trace.addrs[0 .. _tls_trace.length]); } @@ -449,7 +455,7 @@ version (Windows) debug // Ported from druntime's core.internal.backtrace.{dwarf,elf} and // core.internal.elf.{io,dl}. // -version (Windows) {} else debug +version (Windows) {} else version (FreeStanding) {} else debug { import urt.io : write_err, writeln_err, writef_to, WriteTarget; import urt.mem : strlen, memcpy; @@ -1564,6 +1570,8 @@ private void terminate() nothrow @nogc @trusted writeln_err(" stack trace:"); version (Windows) dbghelp_print_trace(_tls_trace.addrs[0 .. _tls_trace.length]); + else version (FreeStanding) + {} else posix_print_trace(_tls_trace.addrs[0 .. _tls_trace.length]); } @@ -2989,6 +2997,11 @@ else version (RISCV32) enum eh_exception_regno = 10; enum eh_selector_regno = 11; } +else version (Xtensa) +{ + enum eh_exception_regno = 2; // a2 + enum eh_selector_regno = 3; // a3 +} else static assert(0, "Unknown EH register numbers for this architecture"); @@ -3548,33 +3561,43 @@ _Unwind_Reason_Code dwarfeh_personality_common(_Unwind_Action actions, _Unwind_E // Personality function entry points. // ARM EABI uses a different calling convention than the standard Itanium ABI. +// FreeStanding ARM (bare-metal) uses DWARF EH, not ARM EHABI, so use the standard personality. version (ARM) { - version (LDC) + version (FreeStanding) + enum UseArmEhabi = false; + else version (LDC) + enum UseArmEhabi = true; + else + enum UseArmEhabi = false; +} +else + enum UseArmEhabi = false; + +static if (UseArmEhabi) +{ + extern(C) _Unwind_Reason_Code _d_eh_personality(_Unwind_State state, _Unwind_Exception* exception_object, _Unwind_Context* context) nothrow @nogc { - extern(C) _Unwind_Reason_Code _d_eh_personality(_Unwind_State state, _Unwind_Exception* exception_object, _Unwind_Context* context) nothrow @nogc + _Unwind_Action actions; + switch (state & _US_ACTION_MASK) { - _Unwind_Action actions; - switch (state & _US_ACTION_MASK) - { - case _US_VIRTUAL_UNWIND_FRAME: - actions = _UA_SEARCH_PHASE; - break; - case _US_UNWIND_FRAME_STARTING: - actions = _UA_CLEANUP_PHASE; - break; - case _US_UNWIND_FRAME_RESUME: - return _URC_CONTINUE_UNWIND; - default: - dwarf_terminate(__LINE__); - return _URC_FATAL_PHASE1_ERROR; - } - if (state & _US_FORCE_UNWIND) - actions |= _UA_FORCE_UNWIND; - - return dwarfeh_personality_common(actions, exception_object.exception_class, - exception_object, context); + case _US_VIRTUAL_UNWIND_FRAME: + actions = _UA_SEARCH_PHASE; + break; + case _US_UNWIND_FRAME_STARTING: + actions = _UA_CLEANUP_PHASE; + break; + case _US_UNWIND_FRAME_RESUME: + return _URC_CONTINUE_UNWIND; + default: + dwarf_terminate(__LINE__); + return _URC_FATAL_PHASE1_ERROR; } + if (state & _US_FORCE_UNWIND) + actions |= _UA_FORCE_UNWIND; + + return dwarfeh_personality_common(actions, exception_object.exception_class, + exception_object, context); } } else diff --git a/src/urt/math.d b/src/urt/math.d index e92a83c..d510d7e 100644 --- a/src/urt/math.d +++ b/src/urt/math.d @@ -734,7 +734,10 @@ private T _divrem2x1_impl(T)(T[2] a, T b, out T rem) static if (is(T == uint)) { - static assert(false, "TODO!"); + // 32-bit: use 64-bit arithmetic directly + ulong dividend = (cast(ulong)a[1] << 32) | a[0]; + rem = cast(uint)(dividend % b); + return cast(uint)(dividend / b); } else { diff --git a/src/urt/mem/package.d b/src/urt/mem/package.d index 725be17..98acb69 100644 --- a/src/urt/mem/package.d +++ b/src/urt/mem/package.d @@ -63,7 +63,7 @@ private: version(DigitalMars) { // DMD lowers alloca(n) calls to __alloca(n) - extern (C) void* __alloca(int nbytes) + extern(C) void* __alloca(int nbytes) { version (D_InlineAsm_X86) { diff --git a/src/urt/mem/string.d b/src/urt/mem/string.d index ff606ca..a068e51 100644 --- a/src/urt/mem/string.d +++ b/src/urt/mem/string.d @@ -66,9 +66,12 @@ private: this.offset = offset; } - auto __debugOverview() => toString; - auto __debugExpanded() => toString; - auto __debugStringView() => toString; + version (Windows) + { + auto __debugOverview() => toString; + auto __debugExpanded() => toString; + auto __debugStringView() => toString; + } } void init_string_heap(uint string_heap_size) nothrow @nogc diff --git a/src/urt/package.d b/src/urt/package.d index 2662c96..5989f46 100644 --- a/src/urt/package.d +++ b/src/urt/package.d @@ -196,32 +196,6 @@ void run_module_dtors(immutable(ModuleInfo*)[] modules) nothrow @nogc @trusted } } -// ---------------------------------------------------------------------- -// PE .minfo section scanning - finds compiler-generated ModuleInfo pointers. -// Inline PE parsing to avoid core.sys.windows struct __init dependencies. -// ---------------------------------------------------------------------- - -version (Windows) - extern (C) extern __gshared ubyte __ImageBase; - -version (linux) -{ - // Stashed by _d_dso_registry in object.d from ELF .init_array callback (DMD) - extern (C) extern __gshared immutable(ModuleInfo*)* _elf_minfo_beg; - extern (C) extern __gshared immutable(ModuleInfo*)* _elf_minfo_end; - - version (LDC) - { - // LDC emits ModuleInfo pointers into the __minfo ELF section but does - // not generate .init_array calls to _d_dso_registry. The linker - // generates __start___minfo / __stop___minfo boundary symbols for us. - // Declared as the element type (not pointer-to) so &symbol yields the - // section address with the correct type for slicing. - extern (C) extern __gshared immutable(ModuleInfo*) __start___minfo; - extern (C) extern __gshared immutable(ModuleInfo*) __stop___minfo; - } -} - immutable(ModuleInfo*)[] get_module_infos() nothrow @nogc @trusted { version (Windows) @@ -248,62 +222,110 @@ immutable(ModuleInfo*)[] get_module_infos() nothrow @nogc @trusted } else { - return null; + // Freestanding/bare-metal: walk _Dmodule_ref linked list. + // Populated by .init_array at startup. + if (_Dmodule_ref is null) + return null; + + size_t count = 0; + for (auto p = _Dmodule_ref; p !is null; p = p.next) + ++count; + + import urt.mem.allocator : Mallocator; + auto arr = Mallocator.instance.allocArray!(void*)(count); + auto p = _Dmodule_ref; + foreach (i; 0 .. count) { arr[i] = cast(void*)p.mod; p = p.next; } + return cast(immutable(ModuleInfo*)[])arr; } } +// ---------------------------------------------------------------------- +// PE .minfo section scanning - finds compiler-generated ModuleInfo pointers. +// Inline PE parsing to avoid core.sys.windows struct __init dependencies. +// ---------------------------------------------------------------------- + version (Windows) -void[] find_pe_section(void* image_base, string name) nothrow @nogc @trusted { - if (name.length > 8) return null; + extern(C) extern __gshared ubyte __ImageBase; - auto base = cast(ubyte*) image_base; + void[] find_pe_section(void* image_base, string name) nothrow @nogc @trusted + { + if (name.length > 8) return null; - // DOS header: e_magic at offset 0 (2 bytes), e_lfanew at offset 0x3C (4 bytes) - if (base[0] != 0x4D || base[1] != 0x5A) // 'MZ' - return null; + auto base = cast(ubyte*) image_base; - auto lfanew = *cast(int*)(base + 0x3C); - auto pe = base + lfanew; + // DOS header: e_magic at offset 0 (2 bytes), e_lfanew at offset 0x3C (4 bytes) + if (base[0] != 0x4D || base[1] != 0x5A) // 'MZ' + return null; - // PE signature check - if (pe[0] != 'P' || pe[1] != 'E' || pe[2] != 0 || pe[3] != 0) - return null; + auto lfanew = *cast(int*)(base + 0x3C); + auto pe = base + lfanew; - // COFF file header starts at pe+4 - // NumberOfSections at offset 2 (2 bytes) - // SizeOfOptionalHeader at offset 16 (2 bytes) - auto file_header = pe + 4; - ushort num_sections = *cast(ushort*)(file_header + 2); - ushort opt_header_size = *cast(ushort*)(file_header + 16); - - // Section headers start after optional header - auto sections = file_header + 20 + opt_header_size; - - // Each IMAGE_SECTION_HEADER is 40 bytes: - // Name[8] at offset 0 - // VirtualSize at offset 8 - // VirtualAddress at offset 12 - foreach (i; 0 .. num_sections) - { - auto sec = sections + i * 40; - auto sec_name = (cast(char*) sec)[0 .. 8]; + // PE signature check + if (pe[0] != 'P' || pe[1] != 'E' || pe[2] != 0 || pe[3] != 0) + return null; - bool match = true; - foreach (j; 0 .. name.length) + // COFF file header starts at pe+4 + // NumberOfSections at offset 2 (2 bytes) + // SizeOfOptionalHeader at offset 16 (2 bytes) + auto file_header = pe + 4; + ushort num_sections = *cast(ushort*)(file_header + 2); + ushort opt_header_size = *cast(ushort*)(file_header + 16); + + // Section headers start after optional header + auto sections = file_header + 20 + opt_header_size; + + // Each IMAGE_SECTION_HEADER is 40 bytes: + // Name[8] at offset 0 + // VirtualSize at offset 8 + // VirtualAddress at offset 12 + foreach (i; 0 .. num_sections) { - if (sec_name[j] != name[j]) + auto sec = sections + i * 40; + auto sec_name = (cast(char*) sec)[0 .. 8]; + + bool match = true; + foreach (j; 0 .. name.length) { - match = false; - break; + if (sec_name[j] != name[j]) + { + match = false; + break; + } + } + if (match && (name.length == 8 || sec_name[name.length] == 0)) + { + auto virtual_size = *cast(uint*)(sec + 8); + auto virtual_address = *cast(uint*)(sec + 12); + return (base + virtual_address)[0 .. virtual_size]; } } - if (match && (name.length == 8 || sec_name[name.length] == 0)) - { - auto virtual_size = *cast(uint*)(sec + 8); - auto virtual_address = *cast(uint*)(sec + 12); - return (base + virtual_address)[0 .. virtual_size]; - } + return null; + } +} +else version (linux) +{ + // Stashed by _d_dso_registry in object.d from ELF .init_array callback (DMD) + extern(C) extern __gshared immutable(ModuleInfo*)* _elf_minfo_beg; + extern(C) extern __gshared immutable(ModuleInfo*)* _elf_minfo_end; + + version (LDC) + { + // LDC emits ModuleInfo pointers into the __minfo ELF section but does + // not generate .init_array calls to _d_dso_registry. The linker + // generates __start___minfo / __stop___minfo boundary symbols for us. + extern(C) extern __gshared immutable(ModuleInfo*) __start___minfo; + extern(C) extern __gshared immutable(ModuleInfo*) __stop___minfo; + } +} +else +{ + // Freestanding/bare-metal: LDC chains ModuleReference structs into + // this linked list via .init_array at startup. + struct ModuleReference + { + ModuleReference* next; + immutable(ModuleInfo)* mod; } - return null; + extern(C) extern __gshared ModuleReference* _Dmodule_ref; } diff --git a/src/urt/processor.d b/src/urt/processor.d index f35792d..0f7ba27 100644 --- a/src/urt/processor.d +++ b/src/urt/processor.d @@ -24,14 +24,22 @@ version (X86_64) { version = Intel; enum string ProcessorFamily = "x86_64"; + enum string ProcessorName = "x86_64"; } else version (X86) { version = Intel; enum string ProcessorFamily = "x86"; + enum string ProcessorName = "x86"; } else version (AArch64) +{ enum string ProcessorFamily = "ARM64"; + version (LDC) + enum string ProcessorName = __traits(targetCPU); + else + enum string ProcessorName = "aarch64"; +} else version (ARM) { enum string ProcessorFamily = "ARM"; @@ -78,11 +86,125 @@ else version (ARM) enum ProcFeatures = ProcFeaturesT(); } else version (RISCV64) +{ enum string ProcessorFamily = "RISCV64"; + + // Synthesize ISA string: "RV64I" + single-letter extensions in canonical order + enum string ProcessorName = "RV64I" + ~ (ProcFeatures.m ? "M" : "") + ~ (ProcFeatures.a ? "A" : "") + ~ (ProcFeatures.f ? "F" : "") + ~ (ProcFeatures.d ? "D" : "") + ~ (ProcFeatures.c ? "C" : "") + ~ (ProcFeatures.v ? "V" : "") + ~ (ProcFeatures.h ? "H" : "") + ~ (ProcFeatures.xtheadba ? " (T-Head)" : ""); + + struct ProcFeaturesT + { + // Standard extensions + bool a = __traits(targetHasFeature, "a"); // Atomic instructions + bool c = __traits(targetHasFeature, "c"); // Compressed instructions + bool d = __traits(targetHasFeature, "d"); // Double-precision float + bool f = __traits(targetHasFeature, "f"); // Single-precision float + bool h = __traits(targetHasFeature, "h"); // Hypervisor + bool m = __traits(targetHasFeature, "m"); // Integer multiply/divide + bool v = __traits(targetHasFeature, "v"); // Vector extension (RVV 1.0) + // Bit manipulation + bool zba = __traits(targetHasFeature, "zba"); // Address generation + bool zbb = __traits(targetHasFeature, "zbb"); // Basic bit manipulation + bool zbs = __traits(targetHasFeature, "zbs"); // Single-bit instructions + // System + bool zicsr = __traits(targetHasFeature, "zicsr"); // CSR instructions + bool zifencei = __traits(targetHasFeature, "zifencei"); // Instruction-fetch fence + // T-Head vendor extensions (BL808 C906, etc.) + bool xtheadba = __traits(targetHasFeature, "xtheadba"); // Address calculation + bool xtheadbb = __traits(targetHasFeature, "xtheadbb"); // Basic bit manipulation + bool xtheadbs = __traits(targetHasFeature, "xtheadbs"); // Single-bit instructions + bool xtheadcmo = __traits(targetHasFeature, "xtheadcmo"); // Cache management + bool xtheadcondmov = __traits(targetHasFeature, "xtheadcondmov"); // Conditional move + bool xtheadmac = __traits(targetHasFeature, "xtheadmac"); // Multiply-accumulate + bool xtheadmemidx = __traits(targetHasFeature, "xtheadmemidx"); // Indexed memory ops + bool xtheadmempair = __traits(targetHasFeature, "xtheadmempair"); // Paired memory ops + bool xtheadsync = __traits(targetHasFeature, "xtheadsync"); // Multicore sync + bool xtheadvdot = __traits(targetHasFeature, "xtheadvdot"); // Vector dot product + } + enum ProcFeatures = ProcFeaturesT(); +} else version (RISCV32) +{ enum string ProcessorFamily = "RISCV"; + + // Synthesize ISA string: "RV32I" or "RV32E" + extensions + enum string ProcessorName = "RV32" + ~ (ProcFeatures.e ? 'E' : 'I') + ~ (ProcFeatures.m ? "M" : "") + ~ (ProcFeatures.a ? "A" : "") + ~ (ProcFeatures.f ? "F" : "") + ~ (ProcFeatures.d ? "D" : "") + ~ (ProcFeatures.c ? "C" : "") + ~ (ProcFeatures.v ? "V" : ""); + + struct ProcFeaturesT + { + // Standard extensions + bool e = __traits(targetHasFeature, "e"); // RV32E: 16 registers only + bool a = __traits(targetHasFeature, "a"); // Atomic instructions + bool c = __traits(targetHasFeature, "c"); // Compressed instructions + bool d = __traits(targetHasFeature, "d"); // Double-precision float + bool f = __traits(targetHasFeature, "f"); // Single-precision float + bool m = __traits(targetHasFeature, "m"); // Integer multiply/divide + bool v = __traits(targetHasFeature, "v"); // Vector extension (RVV 1.0) + // Bit manipulation + bool zba = __traits(targetHasFeature, "zba"); // Address generation + bool zbb = __traits(targetHasFeature, "zbb"); // Basic bit manipulation + bool zbs = __traits(targetHasFeature, "zbs"); // Single-bit instructions + // System + bool zicsr = __traits(targetHasFeature, "zicsr"); // CSR instructions + bool zifencei = __traits(targetHasFeature, "zifencei"); // Instruction-fetch fence + } + enum ProcFeatures = ProcFeaturesT(); +} else version (Xtensa) +{ enum string ProcessorFamily = "Xtensa"; + + // Synthesize name from key features + enum string ProcessorName = "Xtensa" + ~ (ProcFeatures.windowed ? " Windowed" : " Call0") + ~ (ProcFeatures.fp ? (ProcFeatures.dfpaccel ? " DP" : " SP") : "") + ~ (ProcFeatures.mac16 ? " MAC16" : ""); + + struct ProcFeaturesT + { + // Core ISA options + bool density = __traits(targetHasFeature, "density"); // Density (16-bit) instructions + bool loop = __traits(targetHasFeature, "loop"); // Zero-overhead loops + bool windowed = __traits(targetHasFeature, "windowed"); // Windowed registers (vs call0 ABI) + bool boolean_ = __traits(targetHasFeature, "bool"); // Boolean registers + bool sext = __traits(targetHasFeature, "sext"); // Sign extend instruction + bool nsa = __traits(targetHasFeature, "nsa"); // Normalization shift amount + bool clamps = __traits(targetHasFeature, "clamps"); // Clamp signed + bool minmax = __traits(targetHasFeature, "minmax"); // Min/max instructions + // Multiply/divide + bool mul16 = __traits(targetHasFeature, "mul16"); // 16-bit multiply + bool mul32 = __traits(targetHasFeature, "mul32"); // 32-bit multiply + bool mul32high = __traits(targetHasFeature, "mul32high"); // 32-bit multiply high + bool div32 = __traits(targetHasFeature, "div32"); // 32-bit divide + bool mac16 = __traits(targetHasFeature, "mac16"); // 16-bit MAC (ESP32 LX6) + // Floating point + bool fp = __traits(targetHasFeature, "fp"); // Single-precision float + bool dfpaccel = __traits(targetHasFeature, "dfpaccel"); // Double-precision FP acceleration + // System + bool exception_ = __traits(targetHasFeature, "exception"); // Exception handling + bool interrupt = __traits(targetHasFeature, "interrupt"); // Interrupt handling + bool highpriinterrupts = __traits(targetHasFeature, "highpriinterrupts"); // High-priority interrupts + bool debug_ = __traits(targetHasFeature, "debug"); // Debug support + bool threadptr = __traits(targetHasFeature, "threadptr"); // Thread pointer register + bool coprocessor = __traits(targetHasFeature, "coprocessor"); // Coprocessor interface + } + enum ProcFeatures = ProcFeaturesT(); +} else static assert(0, "Unsupported processor"); @@ -96,10 +218,23 @@ else version (ARM) { enum SupportUnalignedLoadStore = !ProcFeatures.strict_align; } +else version (RISCV64) +{ + enum SupportUnalignedLoadStore = __traits(targetHasFeature, "unaligned-scalar-mem"); +} +else version (RISCV32) +{ + enum SupportUnalignedLoadStore = __traits(targetHasFeature, "unaligned-scalar-mem"); +} else { - // TODO: I think MIPS R6 can do native unalogned loads/stores - enum SupportUnalignedLoadStore = false; + // No arch-level feature flag available (Xtensa, MIPS, etc.) + // Platforms that support unaligned access set -d-version=SupportUnaligned in Makefile + // (e.g., ESP32-S3 Xtensa LX7 has hardware unaligned load/store) + version (SupportUnaligned) + enum SupportUnalignedLoadStore = true; + else + enum SupportUnalignedLoadStore = false; } // Different arch may define this differently... diff --git a/src/urt/result.d b/src/urt/result.d index ed3f676..aa69c10 100644 --- a/src/urt/result.d +++ b/src/urt/result.d @@ -122,3 +122,31 @@ else version (Posix) Result errno_result() => Result(errno); } +else version (FreeStanding) +{ + enum InternalResult : Result + { + success = Result.success, + failed = Result(1), + buffer_too_small = Result(2), + invalid_parameter = Result(3), + data_error = Result(4), + unsupported = Result(5), + out_of_range = Result(6), + already_exists = Result(7), + timeout = Result(8), + aborted = Result(9), + no_memory = Result(10), + } + + // Bare-metal errno support — provided by the IP stack or libc shim + extern (C) private int* __errno_location() nothrow @nogc; + + @property int errno() nothrow @nogc @trusted + { + return *__errno_location(); + } + + Result errno_result() + => Result(errno); +} diff --git a/src/urt/socket.d b/src/urt/socket.d index 2991934..89c847a 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -51,6 +51,88 @@ else version (Posix) enum AF_BRIDGE = 7; // Multiprotocol bridge enum AF_INET6 = 10; // IP version 6 } +else version (FreeStanding) +{ + // Bare-metal: BSD socket constants compatible with lwIP + alias SocketHandle = int; + enum INVALID_SOCKET = -1; + + enum AF_UNSPEC = 0; + enum AF_UNIX = 1; + enum AF_INET = 2; + enum AF_INET6 = 10; + + enum SOCK_STREAM = 1; + enum SOCK_DGRAM = 2; + enum SOCK_RAW = 3; + + enum IPPROTO_IP = 0; + enum IPPROTO_ICMP = 1; + enum IPPROTO_TCP = 6; + enum IPPROTO_UDP = 17; + enum IPPROTO_IPV6 = 41; + enum IPPROTO_RAW = 255; + + enum SOL_SOCKET = 0xFFF; + + enum MSG_PEEK = 0x02; + + alias socklen_t = uint; + struct in_addr { uint s_addr; } + struct in6_addr { ubyte[16] s6_addr; } + struct sockaddr { ushort sa_family; ubyte[14] sa_data; } + struct sockaddr_in { ushort sin_family; ushort sin_port; in_addr sin_addr; ubyte[8] sin_zero; } + struct sockaddr_in6 { ushort sin6_family; ushort sin6_port; uint sin6_flowinfo; in6_addr sin6_addr; uint sin6_scope_id; } + struct sockaddr_storage { ushort ss_family; ubyte[126] _pad; } + struct linger { ushort l_onoff; ushort l_linger; } + struct ip_mreq { in_addr imr_multiaddr; in_addr imr_interface; } + struct iovec { void* iov_base; size_t iov_len; } + struct msghdr { void* msg_name; socklen_t msg_namelen; iovec* msg_iov; size_t msg_iovlen; void* msg_control; size_t msg_controllen; int msg_flags; } + + enum SO_ERROR = 0x1007; + + enum POLLRDNORM = 0x0040; + enum POLLWRNORM = 0x0100; + enum POLLERR = 0x0008; + enum POLLHUP = 0x0010; + enum POLLNVAL = 0x0020; + + enum AI_PASSIVE = 0x01; + enum AI_CANONNAME = 0x02; + enum AI_NUMERICHOST = 0x04; + enum AI_V4MAPPED = 0x08; + enum AI_ALL = 0x10; + enum AI_ADDRCONFIG = 0x20; + enum AI_NUMERICSERV = 0x400; + + struct pollfd { int fd; short events; short revents; } + struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; sockaddr* ai_addr; char* ai_canonname; addrinfo* ai_next; } + + // BSD socket function stubs — will be provided by the IP stack + extern(C) nothrow @nogc + { + int poll(pollfd*, uint, int); + SocketHandle socket(int domain, int type, int protocol); + int _bind(SocketHandle, const(sockaddr)*, socklen_t); + int _listen(SocketHandle, int); + int _connect(SocketHandle, const(sockaddr)*, socklen_t); + SocketHandle _accept(SocketHandle, sockaddr*, socklen_t*); + ptrdiff_t _send(SocketHandle, const(void)*, size_t, int); + ptrdiff_t _sendto(SocketHandle, const(void)*, size_t, int, const(sockaddr)*, socklen_t); + ptrdiff_t _sendmsg(SocketHandle, const(msghdr)*, int); + ptrdiff_t _recv(SocketHandle, void*, size_t, int); + ptrdiff_t _recvfrom(SocketHandle, void*, size_t, int, sockaddr*, socklen_t*); + int _shutdown(SocketHandle, int); + int setsockopt(SocketHandle, int, int, const(void)*, socklen_t); + int getsockopt(SocketHandle, int, int, void*, socklen_t*); + int getsockname(SocketHandle, sockaddr*, socklen_t*); + int getpeername(SocketHandle, sockaddr*, socklen_t*); + int getaddrinfo(const(char)*, const(char)*, const(addrinfo)*, addrinfo**); + void freeaddrinfo(addrinfo*); + int gethostname(char*, size_t); + int close(int); + } +} else static assert(false, "Platform not supported"); @@ -203,12 +285,11 @@ Result create_socket(AddressFamily af, SocketType type, Protocol proto, out Sock Result close(Socket socket) { + int result; version (Windows) - int result = closesocket(socket.handle); - else version (Posix) - int result = close(socket.handle); + result = closesocket(socket.handle); else - assert(false, "Not implemented!"); + result = close(socket.handle); if (result < 0) return socket_getlasterror(); @@ -620,7 +701,7 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval // determine the option 'level' OptLevel level = get_optlevel(option); version (HasIPv6) {} else - assert(level != OptLevel.ipv6 && level != OptLevel.ICMPv6, "Platform does not support IPv6!"); + assert(level != OptLevel.ipv6 && level != OptLevel.icmpv6, "Platform does not support IPv6!"); // platforms don't all agree on option data formats! const(void)* arg = optval; @@ -982,21 +1063,22 @@ Result poll(PollFd[] pollFds, Duration timeout, out uint numEvents) assert(pollFds.length <= MaxFds, "Too many fds!"); version (Windows) WSAPOLLFD[MaxFds] fds; - else version (Posix) + else pollfd[MaxFds] fds; for (size_t i = 0; i < pollFds.length; ++i) { fds[i].fd = pollFds[i].socket.handle; fds[i].revents = 0; - fds[i].events = ((pollFds[i].request_events & PollEvents.read) ? POLLRDNORM : 0) | - ((pollFds[i].request_events & PollEvents.write) ? POLLWRNORM : 0); + fds[i].events = cast(short)(((pollFds[i].request_events & PollEvents.read) ? POLLRDNORM : 0) | + ((pollFds[i].request_events & PollEvents.write) ? POLLWRNORM : 0)); } + int r; version (Windows) - int r = WSAPoll(fds.ptr, cast(uint)pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); + r = WSAPoll(fds.ptr, cast(uint)pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); else version (Posix) - int r = _poll(fds.ptr, pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); + r = _poll(fds.ptr, pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); else - assert(false, "Not implemented!"); + r = poll(fds.ptr, cast(uint)pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); if (r < 0) { numEvents = 0; @@ -1536,6 +1618,16 @@ else version (Darwin) OptInfo( TCP_NODELAY, OptType.bool_, OptType.int_ ), ]; } +else version (FreeStanding) +{ + // Bare-metal stub — socket options not yet supported + __gshared immutable OptInfo[SocketOption.max] s_socketOptions = () { + OptInfo[SocketOption.max] r; + foreach (ref o; r) + o = OptInfo(-1, OptType.unsupported, OptType.unsupported); + return r; + }(); +} else static assert(false, "TODO"); diff --git a/src/urt/string/tailstring.d b/src/urt/string/tailstring.d index 0c7b77d..383ac30 100644 --- a/src/urt/string/tailstring.d +++ b/src/urt/string/tailstring.d @@ -94,9 +94,12 @@ private: this.offset = offset; } - auto __debugOverview() => toString; - auto __debugExpanded() => toString; - auto __debugStringView() => toString; + version (Windows) + { + auto __debugOverview() => toString; + auto __debugExpanded() => toString; + auto __debugStringView() => toString; + } } diff --git a/src/urt/system.d b/src/urt/system.d index 585404f..afb7a59 100644 --- a/src/urt/system.d +++ b/src/urt/system.d @@ -105,6 +105,10 @@ void set_system_idle_params(IdleParams params) { // TODO: ...we're not likely to run on a POSIX desktop system any time soon... } + else version (FreeStanding) + { + // Bare-metal: no idle state management needed + } else static assert(0, "Not implemented"); } diff --git a/src/urt/time.d b/src/urt/time.d index c32eeb6..6615250 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -12,6 +12,10 @@ else version (Posix) { import core.sys.posix.time; } +else version (BL808) +{ + import sys.bl808.timer; +} nothrow @nogc: @@ -67,9 +71,9 @@ pure nothrow @nogc: static if (is(T == Time!c, Clock c) && c != clock) { static if (clock == Clock.Monotonic && c == Clock.SystemTime) - return SysTime(ticks + ticks_since_boot); + return SysTime(ticks + sys_time_offset); else - return MonoTime(ticks - ticks_since_boot); + return MonoTime(ticks - sys_time_offset); } else static assert(false, "constraint out of sync"); @@ -88,9 +92,9 @@ pure nothrow @nogc: static if (clock != c) { static if (clock == Clock.Monotonic) - t1 += ticks_since_boot; + t1 += sys_time_offset; else - t2 += ticks_since_boot; + t2 += sys_time_offset; } return Duration(t1 - t2); } @@ -303,8 +307,11 @@ pure nothrow @nogc: return offset; } - auto __debugOverview() const - => cast(double)this; + version (Windows) + { + auto __debugOverview() const + => cast(double)this; + } } alias Timer = FixedTimer!(); @@ -713,6 +720,14 @@ MonoTime getTime() clock_gettime(CLOCK_MONOTONIC, &ts); return MonoTime(ts.tv_sec * 1_000_000_000 + ts.tv_nsec); } + else version (BL808) + { + return MonoTime(mtime_read()); + } + else version (FreeStanding) + { + assert(0, "getTime: not yet implemented for bare-metal"); + } else { static assert(false, "TODO"); @@ -733,6 +748,14 @@ SysTime getSysTime() clock_gettime(CLOCK_REALTIME, &ts); return SysTime(ts.tv_sec * 1_000_000_000 + ts.tv_nsec); } + else version (BL808) + { + return SysTime(mtime_read() + sys_time_offset); + } + else version (FreeStanding) + { + assert(0, "getSysTime: not yet implemented for bare-metal"); + } else { static assert(false, "TODO"); @@ -741,44 +764,17 @@ SysTime getSysTime() SysTime getSysTime(DateTime time) pure { - version (Windows) - return datetime_to_filetime(time); - else version (Posix) - { - timespec ts = datetime_to_realtime(time); - return SysTime(ts.tv_sec * 1_000_000_000 + ts.tv_nsec); - } - else - static assert(false, "TODO"); + return from_unix_time_ns(datetime_to_unix_ns(time)); } DateTime getDateTime() { - version (Windows) - return filetime_to_datetime(getSysTime()); - else version (Posix) - { - timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - return realtime_to_datetime(ts); - } - else - static assert(false, "TODO"); + return getDateTime(getSysTime()); } DateTime getDateTime(SysTime time) pure { - version (Windows) - return filetime_to_datetime(time); - else version (Posix) - { - timespec ts; - ts.tv_sec = cast(time_t)(time.ticks / 1_000_000_000); - ts.tv_nsec = cast(uint)(time.ticks % 1_000_000_000); - return realtime_to_datetime(ts); - } - else - static assert(false, "TODO"); + return unix_ns_to_datetime(unixTimeNs(time)); } Duration getAppTime() @@ -792,9 +788,13 @@ Duration appTime(SysTime t) pure ulong unixTimeNs(SysTime t) pure { version (Windows) - return (t.ticks - 116444736000000000UL) * 100UL; + return (t.ticks - unix_epoch_as_filetime) * 100UL; else version (Posix) return t.ticks; + else version (BL808) + return t.ticks * nsec_multiplier; + else version (FreeStanding) + return t.ticks; else static assert(false, "TODO"); } @@ -802,9 +802,13 @@ ulong unixTimeNs(SysTime t) pure SysTime from_unix_time_ns(ulong ns) pure { version (Windows) - return SysTime(ns / 100UL + 116444736000000000UL); + return SysTime(ns / 100UL + unix_epoch_as_filetime); else version (Posix) return SysTime(ns); + else version (BL808) + return SysTime(ns / nsec_multiplier); + else version (FreeStanding) + return SysTime(ns); else static assert(false, "TODO"); } @@ -812,6 +816,44 @@ SysTime from_unix_time_ns(ulong ns) pure Duration abs(Duration d) pure => Duration(d.ticks < 0 ? -d.ticks : d.ticks); +bool wall_time_set() +{ + return has_wall_time; +} + +void set_utc_time(ulong unix_ns) +{ + cast()sys_time_offset = unix_ns / nsec_multiplier - getTime().ticks; + has_wall_time = true; + + version (Windows) + { + import urt.internal.sys.windows; + + ulong ft = unix_ns / 100 + unix_epoch_as_filetime; + SYSTEMTIME st; + FileTimeToSystemTime(cast(FILETIME*)&ft, &st); + SetSystemTime(&st); + } + else version (Posix) + { + timespec ts; + ts.tv_sec = cast(time_t)(unix_ns / 1_000_000_000); + ts.tv_nsec = cast(uint)(unix_ns % 1_000_000_000); + clock_settime(CLOCK_REALTIME, &ts); + } + else version (BL808) + { + auto p = hbn_persist(); + ulong mtime_ticks = unix_ns / nsec_multiplier; + ulong sec = mtime_ticks / mtime_freq_hz; + ulong frac = mtime_ticks % mtime_freq_hz; + ulong hbn_ticks = sec * rtc_freq_hz + frac * rtc_freq_hz / mtime_freq_hz; + p.utc_offset = cast(long)hbn_ticks - cast(long)rtc_read(); + p.magic = HbnPersist.HBN_MAGIC; + } +} + private: @@ -822,17 +864,30 @@ __gshared immutable uint[9] digit_multipliers = [ 100_000_000, 10_000_000, 1_000 version (Windows) { + enum ulong unix_epoch_as_filetime = 116_444_736_000_000_000UL; + immutable uint ticks_per_second; immutable uint nsec_multiplier; - immutable ulong ticks_since_boot; } else version (Posix) { enum uint ticks_per_second = 1_000_000_000; enum uint nsec_multiplier = 1; - immutable ulong ticks_since_boot; +} +else version (BL808) +{ + enum uint ticks_per_second = mtime_freq_hz; + enum uint nsec_multiplier = 1_000_000_000 / mtime_freq_hz; +} +else version (FreeStanding) +{ + enum uint ticks_per_second = 1_000_000_000; + enum uint nsec_multiplier = 1; } +immutable ulong sys_time_offset; +__gshared bool has_wall_time; + package(urt) void init_clock() { cast()startTime = getTime(); @@ -850,14 +905,15 @@ package(urt) void init_clock() // we want the ftime for QPC 0; which should be the boot time // we'll repeat this 100 times and take the minimum, and we should be within probably nanoseconds of the correct value LARGE_INTEGER qpc; - ulong ftime, bootTime = ulong.max; + ulong ftime, boot_time = ulong.max; foreach (i; 0 .. 100) { QueryPerformanceCounter(&qpc); GetSystemTimePreciseAsFileTime(cast(FILETIME*)&ftime); - bootTime = min(bootTime, ftime - qpc.QuadPart); + boot_time = min(boot_time, ftime - qpc.QuadPart); } - cast()ticks_since_boot = bootTime; + cast()sys_time_offset = boot_time; + has_wall_time = true; } else version (Posix) { @@ -865,14 +921,25 @@ package(urt) void init_clock() // this doesn't really give time since boot, since MONOTIME is not guaranteed to be zero at system startup... timespec mt, rt; - ulong bootTime = ulong.max; + ulong boot_time = ulong.max; foreach (i; 0 .. 100) { clock_gettime(CLOCK_MONOTONIC, &mt); clock_gettime(CLOCK_REALTIME, &rt); - bootTime = min(bootTime, rt.tv_sec*1_000_000_000 + rt.tv_nsec - mt.tv_sec*1_000_000_000 - mt.tv_nsec); + boot_time = min(boot_time, rt.tv_sec*1_000_000_000 + rt.tv_nsec - mt.tv_sec*1_000_000_000 - mt.tv_nsec); } - cast()ticks_since_boot = bootTime; + cast()sys_time_offset = boot_time; + has_wall_time = true; + } + else version (BL808) + { + rtc_enable(); + recalc_sys_time_offset(); + } + else version (FreeStanding) + { + // Bare-metal: no wall-clock reference until set_utc_time() is called. + cast()sys_time_offset = 0; } else static assert(false, "TODO"); @@ -1002,98 +1069,160 @@ unittest assert(dt.fromString("2024-1-1 01:60:56") == -1); assert(dt.fromString("2024-1-1 01:01:60") == -1); assert(dt.fromString("10000-1-1 1:01:01") == -1); + + // ---- unix_ns_to_datetime / datetime_to_unix_ns ---- + + // Unix epoch: 1970-01-01 00:00:00 UTC, Thursday + dt = unix_ns_to_datetime(0); + assert(dt.year == 1970 && dt.month == Month.January && dt.day == 1); + assert(dt.hour == 0 && dt.minute == 0 && dt.second == 0 && dt.ns == 0); + assert(dt.wday == Day.Thursday); + + // Round-trip at epoch + assert(datetime_to_unix_ns(dt) == 0); + + // 2000-01-01 00:00:00 UTC = 946684800 seconds, Saturday + dt = unix_ns_to_datetime(946_684_800UL * 1_000_000_000); + assert(dt.year == 2000 && dt.month == Month.January && dt.day == 1); + assert(dt.wday == Day.Saturday); + assert(datetime_to_unix_ns(dt) == 946_684_800UL * 1_000_000_000); + + // 2024-02-29 (leap year) 12:00:00 = 1709208000 seconds, Thursday + dt = unix_ns_to_datetime(1_709_208_000UL * 1_000_000_000); + assert(dt.year == 2024 && dt.month == Month.February && dt.day == 29); + assert(dt.hour == 12 && dt.minute == 0 && dt.second == 0); + assert(dt.wday == Day.Thursday); + assert(datetime_to_unix_ns(dt) == 1_709_208_000UL * 1_000_000_000); + + // 1900-03-01 - 1900 is NOT a leap year (century rule) + // 1900-03-01 00:00:00 = -2203891200 seconds... negative, skip + // Instead test 2100-03-01 (also not a leap year) + // 2100-03-01 00:00:00 = 4107542400 seconds, Monday + dt = unix_ns_to_datetime(4_107_542_400UL * 1_000_000_000); + assert(dt.year == 2100 && dt.month == Month.March && dt.day == 1); + assert(dt.wday == Day.Monday); + + // 2000 IS a leap year (400-year rule), Feb 29 exists + // 2000-02-29 00:00:00 = 951782400 seconds, Tuesday + dt = unix_ns_to_datetime(951_782_400UL * 1_000_000_000); + assert(dt.year == 2000 && dt.month == Month.February && dt.day == 29); + assert(dt.wday == Day.Tuesday); + + // Sub-second precision: 2025-06-15 08:30:45.123456789 + dt.year = 2025; dt.month = Month.June; dt.day = 15; + dt.hour = 8; dt.minute = 30; dt.second = 45; dt.ns = 123_456_789; + ulong ns = datetime_to_unix_ns(dt); + auto dt2 = unix_ns_to_datetime(ns); + assert(dt2.year == 2025 && dt2.month == Month.June && dt2.day == 15); + assert(dt2.hour == 8 && dt2.minute == 30 && dt2.second == 45); + assert(dt2.ns == 123_456_789); + + // Dec 31 ? Jan 1 boundary + dt = unix_ns_to_datetime(1_735_689_599UL * 1_000_000_000); // 2024-12-31 23:59:59 + assert(dt.year == 2024 && dt.month == Month.December && dt.day == 31); + assert(dt.hour == 23 && dt.minute == 59 && dt.second == 59); + dt = unix_ns_to_datetime(1_735_689_600UL * 1_000_000_000); // 2025-01-01 00:00:00 + assert(dt.year == 2025 && dt.month == Month.January && dt.day == 1); + assert(dt.wday == Day.Wednesday); } -version (Windows) +DateTime unix_ns_to_datetime(ulong ns) pure { - DateTime filetime_to_datetime(SysTime ftime) pure - { - version (BigEndian) - static assert(false, "Only works in little endian!"); - - SYSTEMTIME stime; - alias PureHACK = extern(Windows) BOOL function(const(FILETIME)*, LPSYSTEMTIME) pure nothrow @nogc; - (cast(PureHACK)&FileTimeToSystemTime)(cast(FILETIME*)&ftime.ticks, &stime); - - DateTime dt; - dt.year = stime.wYear; - dt.month = cast(Month)stime.wMonth; - dt.wday = cast(Day)stime.wDayOfWeek; - dt.day = cast(ubyte)stime.wDay; - dt.hour = cast(ubyte)stime.wHour; - dt.minute = cast(ubyte)stime.wMinute; - dt.second = cast(ubyte)stime.wSecond; - dt.ns = (ftime.ticks % 10_000_000) * 100; - - debug assert(stime.wMilliseconds == dt.msec); - - return dt; - } + ulong total_sec = ns / 1_000_000_000; + uint remainder_ns = cast(uint)(ns % 1_000_000_000); - SysTime datetime_to_filetime(ref DateTime dt) pure - { - version (BigEndian) - static assert(false, "Only works in little endian!"); - - SYSTEMTIME stime; - stime.wYear = dt.year; - stime.wMonth = cast(ushort)dt.month; - stime.wDayOfWeek = cast(ushort)dt.wday; - stime.wDay = cast(ushort)dt.day; - stime.wHour = cast(ushort)dt.hour; - stime.wMinute = cast(ushort)dt.minute; - stime.wSecond = cast(ushort)dt.second; - stime.wMilliseconds = cast(ushort)(dt.ns / 1_000_000); - - SysTime ftime; - alias PureHACK = extern(Windows) BOOL function(const(SYSTEMTIME)*, FILETIME*) pure nothrow @nogc; - if (!(cast(PureHACK)&SystemTimeToFileTime)(&stime, cast(FILETIME*)&ftime)) - assert(false, "TODO: WHAT TO DO?"); - - debug assert(ftime.ticks % 10_000_000 == (dt.ns / 1_000_000) * 10_000); - ftime.ticks = ftime.ticks - ftime.ticks % 10_000_000 + dt.ns / 100; - - return ftime; - } + uint sod = cast(uint)(total_sec % 86_400); + long days = cast(long)(total_sec / 86_400); + + DateTime dt; + dt.hour = cast(ubyte)(sod / 3600); + dt.minute = cast(ubyte)(sod % 3600 / 60); + dt.second = cast(ubyte)(sod % 60); + dt.ns = remainder_ns; + + dt.wday = cast(Day)((days + 4) % 7); + + days += 719_468; + long era = (days >= 0 ? days : days - 146_096) / 146_097; + uint doe = cast(uint)(days - era * 146_097); + uint yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365; + long y = yoe + era * 400; + uint doy = doe - (365 * yoe + yoe / 4 - yoe / 100); + uint mp = (5 * doy + 2) / 153; + uint d = doy - (153 * mp + 2) / 5 + 1; + uint m = mp < 10 ? mp + 3 : mp - 9; + if (m <= 2) + ++y; + + dt.year = cast(short)y; + dt.month = cast(Month)m; + dt.day = cast(ubyte)d; + + return dt; } -else version (Posix) + +ulong datetime_to_unix_ns(DateTime dt) pure { - DateTime realtime_to_datetime(timespec ts) pure - { - tm t; - alias PureHACK = extern(C) tm* function(time_t* timer, tm* buf) pure nothrow @nogc; - (cast(PureHACK)&gmtime_r)(&ts.tv_sec, &t); - - DateTime dt; - dt.year = cast(short)(t.tm_year + 1900); - dt.month = cast(Month)(t.tm_mon + 1); - dt.wday = cast(Day)t.tm_wday; - dt.day = cast(ubyte)t.tm_mday; - dt.hour = cast(ubyte)t.tm_hour; - dt.minute = cast(ubyte)t.tm_min; - dt.second = cast(ubyte)t.tm_sec; - dt.ns = cast(uint)ts.tv_nsec; - - return dt; - } + long y = dt.year; + uint m = dt.month; + uint d = dt.day; + + if (m <= 2) + --y; + long era = (y >= 0 ? y : y - 399) / 400; + uint yoe = cast(uint)(y - era * 400); + uint doy = (153 * (m > 2 ? m - 3 : m + 9) + 2) / 5 + d - 1; + uint doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + long days = era * 146_097 + doe - 719_468; + + ulong total_sec = cast(ulong)(days * 86_400 + dt.hour*3600 + dt.minute*60 + dt.second); + + return total_sec * 1_000_000_000 + dt.ns; +} - timespec datetime_to_realtime(ref DateTime time) pure +version (BL808) +{ + __gshared ulong last_hbn; + + /// call periodically to correct HBN drift against mtime + /// also detects 40-bit HBN counter wrap (~388 days) and compensate + void correct_drift() { - tm t; - t.tm_year = time.year - 1900; - t.tm_mon = cast(int)time.month - 1; - t.tm_mday = time.day; - t.tm_hour = time.hour; - t.tm_min = time.minute; - t.tm_sec = time.second; + auto p = hbn_persist(); + if (p.magic != HbnPersist.HBN_MAGIC) + return; - alias PureHACK = extern(C) time_t function(tm* timer) pure nothrow @nogc; - time_t sec = (cast(PureHACK)&mktime)(&t); + ulong now_hbn = rtc_read(); - timespec ts; - ts.tv_sec = sec; - ts.tv_nsec = time.ns; - return ts; + // detect 40-bit wrap: counter went backwards since last check + if (now_hbn < last_hbn) + p.utc_offset += ulong(1) << 40; + last_hbn = now_hbn; + + ulong sys_mtime = mtime_read() + sys_time_offset; + + // What does HBN + offset think it is (converted to mtime ticks)? + ulong hbn_total = now_hbn + p.utc_offset; + ulong sys_hbn = hbn_total / rtc_freq_hz * mtime_freq_hz + + hbn_total % rtc_freq_hz * mtime_freq_hz / rtc_freq_hz; + + // difference is accumulated drift; fold into utc_offset + long drift_mtime = sys_mtime - sys_hbn; + p.utc_offset += drift_mtime * rtc_freq_hz / mtime_freq_hz; + } + + void recalc_sys_time_offset() + { + auto p = hbn_persist(); + if (p.magic == HbnPersist.HBN_MAGIC) + { + last_hbn = rtc_read(); + long hbn_total = cast(long)(last_hbn + p.utc_offset); + long hbn_unix_mtime = hbn_total / rtc_freq_hz * mtime_freq_hz + + hbn_total % rtc_freq_hz * mtime_freq_hz / rtc_freq_hz; + cast()sys_time_offset = cast(ulong)(hbn_unix_mtime - cast(long)mtime_read()); + has_wall_time = true; + } } } From 2e7978696df70a942fd5a46b4774e31301412949 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 29 Mar 2026 03:45:55 +1000 Subject: [PATCH 110/138] Initial BL808 build... - BL808 passing unit tests! --- Makefile | 6 +- src/object.d | 13 +- src/sys/bl808/crash.d | 121 ++++++++ src/sys/bl808/gpio.d | 128 +++++++++ src/sys/bl808/hbn_ram.c | 12 + src/sys/bl808/i2c.d | 5 + src/sys/bl808/ipc.d | 49 ++++ src/sys/bl808/irq.d | 131 +++++++++ src/sys/bl808/package.d | 50 ++++ src/sys/bl808/spi.d | 5 + src/sys/bl808/start.S | 407 +++++++++++++++++++++++++++ src/sys/bl808/syscalls.d | 75 +++++ src/sys/bl808/timer.d | 241 ++++++++++++++++ src/sys/bl808/uart.d | 594 +++++++++++++++++++++++++++++++++++++++ src/sys/bl808/wifi.d | 6 + src/sys/bl808/xram.d | 225 +++++++++++++++ src/urt/conv.d | 29 +- src/urt/endian.d | 4 +- src/urt/exception.d | 36 ++- src/urt/fibre.d | 34 +-- src/urt/file.d | 3 +- src/urt/mem/alloc.d | 133 +++++---- src/urt/mem/ring.d | 2 +- src/urt/platform.d | 2 +- src/urt/system.d | 173 +++++++++++- src/urt/time.d | 4 +- 26 files changed, 2356 insertions(+), 132 deletions(-) create mode 100644 src/sys/bl808/crash.d create mode 100644 src/sys/bl808/gpio.d create mode 100644 src/sys/bl808/hbn_ram.c create mode 100644 src/sys/bl808/i2c.d create mode 100644 src/sys/bl808/ipc.d create mode 100644 src/sys/bl808/irq.d create mode 100644 src/sys/bl808/package.d create mode 100644 src/sys/bl808/spi.d create mode 100644 src/sys/bl808/start.S create mode 100644 src/sys/bl808/syscalls.d create mode 100644 src/sys/bl808/timer.d create mode 100644 src/sys/bl808/uart.d create mode 100644 src/sys/bl808/wifi.d create mode 100644 src/sys/bl808/xram.d diff --git a/Makefile b/Makefile index c48f93b..497d35c 100644 --- a/Makefile +++ b/Makefile @@ -22,9 +22,13 @@ DEPFILE := $(OBJDIR)/$(TARGETNAME).d DFLAGS := $(DFLAGS) -preview=bitfields -preview=rvaluerefparam -preview=nosharedaccess -preview=in -SOURCES := $(shell find "$(SRCDIR)" -type f -name '*.d') +SOURCES := $(shell find "$(SRCDIR)" -type f -name '*.d' -not -path '$(SRCDIR)/sys/*') SOURCES := $(SOURCES) $(SRCDIR)/urt/internal/mbedtls.c +ifeq ($(PLATFORM),riscv64) + SOURCES := $(SOURCES) $(shell find "$(SRCDIR)/sys" -type f -name '*.d' 2>/dev/null) +endif + # Set target file based on build type and OS ifeq ($(BUILD_TYPE),exe) BUILD_CMD_FLAGS := diff --git a/src/object.d b/src/object.d index 1d3d869..cb0ded9 100644 --- a/src/object.d +++ b/src/object.d @@ -1598,14 +1598,17 @@ const: } // ────────────────────────────────────────────────────────────────────── -// _d_dso_registry — ELF shared-object module registry +// Module registration — LDC uses _Dmodule_ref linked list // -// On ELF targets the compiler generates .init_array/.fini_array entries -// that call _d_dso_registry with pointers to the module-info section. -// We stash the module range here; uRT's C main() handles constructor -// execution and unittest running via get_module_infos(). +// On ELF targets the compiler generates .init_array entries that chain +// ModuleReference structs into _Dmodule_ref. On Linux, glibc's crt0 +// calls .init_array automatically. On bare-metal, start.S does it. // ────────────────────────────────────────────────────────────────────── +version (Windows) {} +else + extern(C) __gshared void* _Dmodule_ref = null; + version (linux) { struct CompilerDSOData diff --git a/src/sys/bl808/crash.d b/src/sys/bl808/crash.d new file mode 100644 index 0000000..efa5582 --- /dev/null +++ b/src/sys/bl808/crash.d @@ -0,0 +1,121 @@ +/// BL808 crash handler +/// +/// Called from _trap_exception in start.S. Prints exception info, +/// register dump, and stack backtrace to UART0. +/// Requires -frame-pointer=all for reliable backtrace. +module sys.bl808.crash; + +import sys.bl808.uart; + +private: + +// Linker symbols from the linker script +extern(C) extern __gshared { + pragma(mangle, "_stack_top") void* _stack_top_ptr; + pragma(mangle, "_bss_end") void* _bss_end_ptr; +} + +immutable const(char)*[16] exception_names = [ + "Instruction address misaligned", + "Instruction access fault", + "Illegal instruction", + "Breakpoint", + "Load address misaligned", + "Load access fault", + "Store address misaligned", + "Store access fault", + "Environment call from U-mode", + "Environment call from S-mode", + "Reserved", + "Environment call from M-mode", + "Instruction page fault", + "Load page fault", + "Reserved", + "Store page fault", +]; + +immutable const(char)*[31] rnames = [ + "ra ", "sp ", "gp ", "tp ", + "t0 ", "t1 ", "t2 ", + "s0 ", "s1 ", + "a0 ", "a1 ", "a2 ", "a3 ", + "a4 ", "a5 ", "a6 ", "a7 ", + "s2 ", "s3 ", "s4 ", "s5 ", + "s6 ", "s7 ", "s8 ", "s9 ", + "s10", "s11", + "t3 ", "t4 ", "t5 ", "t6 ", +]; + +public: + +/// regs[] layout matches the save order in _trap_exception: +/// [0]=ra [1]=sp [2]=gp [3]=tp [4]=t0 [5]=t1 [6]=t2 +/// [7]=s0/fp [8]=s1 [9]=a0...[16]=a7 [17]=s2...[26]=s11 [27]=t3...[30]=t6 +extern(C) void _crash_handler(ulong* regs, ulong mcause, ulong mepc, ulong mtval) @nogc nothrow +{ + uart0_print("\n\n*** CRASH ***\nException: "); + + ulong cause = mcause & 0x7FFF_FFFF_FFFF_FFFF; + if (cause < 16) + uart0_print(exception_names[cast(uint) cause]); + else + uart0_print("Unknown exception"); + + uart0_print(" (cause="); + uart0_hex(mcause); + uart0_print(")\n mepc = "); + uart0_hex(mepc); + uart0_print("\n mtval = "); + uart0_hex(mtval); + uart0_print("\n\nRegisters:\n"); + + foreach (i; 0 .. 31) + { + uart0_print(" "); + uart0_print(rnames[i]); + uart0_print(" = "); + uart0_hex(regs[i]); + if ((i % 4) == 3 || i == 30) + uart0_putc('\n'); + } + + // Stack backtrace via frame pointer chain + // RV64 convention: fp-8 = saved ra, fp-16 = saved previous fp + uart0_print("\nBacktrace:\n [0] "); + uart0_hex(mepc); + uart0_print(" (faulting PC)\n"); + + ulong fp = regs[7]; // s0 = frame pointer + ulong stack_hi = cast(ulong) _stack_top_ptr; + ulong stack_lo = cast(ulong) _bss_end_ptr; + + foreach (depth; 1 .. 32) + { + if (fp < stack_lo || fp >= stack_hi || (fp & 0x7) != 0) + break; + + ulong ret_addr = *cast(ulong*)(fp - 8); + ulong prev_fp = *cast(ulong*)(fp - 16); + + if (ret_addr == 0) + break; + + uart0_print(" ["); + if (depth < 10) + uart0_putc(cast(char)('0' + depth)); + else + { + uart0_putc(cast(char)('0' + depth / 10)); + uart0_putc(cast(char)('0' + depth % 10)); + } + uart0_print("] "); + uart0_hex(ret_addr); + uart0_putc('\n'); + + if (prev_fp == 0 || prev_fp <= fp) + break; + fp = prev_fp; + } + + uart0_print("\n*** HALTED ***\n"); +} diff --git a/src/sys/bl808/gpio.d b/src/sys/bl808/gpio.d new file mode 100644 index 0000000..5000206 --- /dev/null +++ b/src/sys/bl808/gpio.d @@ -0,0 +1,128 @@ +/// BL808 GPIO and WS2812 LED driver +/// +/// GPIO config registers: GLB_BASE + 0x8C4 + pin*4 +/// bits[4:0] = function (11 = SWGPIO, software-controlled) +/// bit[6] = input enable +/// bit[11] = output enable +/// bit[17] = output value +/// bit[24] = pull-up enable +/// bit[25] = pull-down enable +/// +/// WS2812B on the M1s Dock board: GPIO8 +module sys.bl808.gpio; + +@nogc nothrow: + +private: + +enum uint GLB_BASE = 0x2000_0000; +enum uint GPIO_CFG_BASE = GLB_BASE + 0x8C4; + +// GPIO_CFG field values +enum uint GPIO_FUN_SWGPIO = 11; +enum uint GPIO_OUTPUT_EN = 1u << 11; +enum uint GPIO_OUTPUT_HIGH = 1u << 17; + +// WS2812 LED pin on M1s Dock +enum uint WS2812_PIN = 8; + +// Timing loop counts — calibrated for ~480MHz D0 core clock. +// WS2812B spec: T0H=400ns, T0L=850ns, T1H=800ns, T1L=450ns (±150ns). +// At 480MHz: 1 cycle ≈ 2.08ns, so T0H ≈ 192 cycles, T1H ≈ 384 cycles. +// Loop body (addi + bnez compressed) ≈ 2 cycles → divide by 2. +enum uint WS_T0H_LOOPS = 80; // ~400ns at 400MHz +enum uint WS_T0L_LOOPS = 170; // ~850ns at 400MHz +enum uint WS_T1H_LOOPS = 160; // ~800ns at 400MHz +enum uint WS_T1L_LOOPS = 90; // ~450ns at 400MHz +enum uint WS_RESET_US = 60; // µs for reset pulse (>50µs required) + +pragma(inline, true) +void gpio_cfg_write(uint pin, uint value) +{ + *cast(uint*)(GPIO_CFG_BASE + pin * 4) = value; +} + +pragma(inline, true) +void delay_loops(ulong n) +{ + // =r → output (ulong/i64); 0 → input tied to output 0 (same register, same type). + import ldc.llvmasm; + cast(void) __asm!ulong(` + 1: addi $0, $0, -1 + bnez $0, 1b + `, "=r,0", n); +} + +void gpio_set(uint pin, bool high) +{ + uint cfg = GPIO_FUN_SWGPIO | GPIO_OUTPUT_EN; + if (high) + cfg |= GPIO_OUTPUT_HIGH; + gpio_cfg_write(pin, cfg); +} + +public: + +/// Configure a pin as software-controlled output, initially low. +void gpio_output_init(uint pin) +{ + gpio_cfg_write(pin, GPIO_FUN_SWGPIO | GPIO_OUTPUT_EN); +} + +/// Set pin output level. +void gpio_output_set(uint pin, bool high) +{ + gpio_set(pin, high); +} + +/// Send one WS2812 bit (0 = short high, 1 = long high) on the given pin. +private void ws2812_bit(uint pin, bool one) +{ + if (one) + { + gpio_set(pin, true); + delay_loops(WS_T1H_LOOPS); + gpio_set(pin, false); + delay_loops(WS_T1L_LOOPS); + } + else + { + gpio_set(pin, true); + delay_loops(WS_T0H_LOOPS); + gpio_set(pin, false); + delay_loops(WS_T0L_LOOPS); + } +} + +/// Send one byte (MSB first) to a WS2812 LED. +private void ws2812_byte(uint pin, ubyte b) +{ + foreach (i; 0 .. 8) + ws2812_bit(pin, (b & (0x80 >> i)) != 0); +} + +/// Send one GRB pixel to the WS2812 at the given pin. +/// Colours: r/g/b = 0..255. +void ws2812_send(uint pin, ubyte r, ubyte g, ubyte b) +{ + gpio_output_init(pin); + ws2812_byte(pin, g); // WS2812 order: G, R, B + ws2812_byte(pin, r); + ws2812_byte(pin, b); + // Reset: hold low for >50µs + gpio_set(pin, false); + delay_loops(WS_RESET_US * 200); // ~60µs at 400MHz +} + +/// Set the on-board WS2812 LED colour. +void led_set(ubyte r, ubyte g, ubyte b) +{ + ws2812_send(WS2812_PIN, r, g, b); +} + +/// Boot-stage colour helpers +void led_red() { led_set(32, 0, 0); } +void led_green() { led_set(0, 32, 0); } +void led_blue() { led_set(0, 0, 32); } +void led_white() { led_set(16, 16, 16); } +void led_off() { led_set(0, 0, 0); } diff --git a/src/sys/bl808/hbn_ram.c b/src/sys/bl808/hbn_ram.c new file mode 100644 index 0000000..b0a679a --- /dev/null +++ b/src/sys/bl808/hbn_ram.c @@ -0,0 +1,12 @@ +/* HBN RAM persistent storage — placed in .hbn_ram section by linker. + * Survives hibernate if VBAT is maintained. */ + +#include + +struct hbn_persist { + uint32_t magic; + int64_t utc_offset; /* HBN ticks from RTC epoch to Unix epoch */ +}; + +__attribute__((section(".hbn_ram"), used)) +struct hbn_persist _hbn_persist; diff --git a/src/sys/bl808/i2c.d b/src/sys/bl808/i2c.d new file mode 100644 index 0000000..289d245 --- /dev/null +++ b/src/sys/bl808/i2c.d @@ -0,0 +1,5 @@ +/// BL808 I2C peripheral +/// +/// Direct register access to BL808 I2C controller. +/// TODO: implement when needed for hardware I/O. +module sys.bl808.i2c; diff --git a/src/sys/bl808/ipc.d b/src/sys/bl808/ipc.d new file mode 100644 index 0000000..6ca6444 --- /dev/null +++ b/src/sys/bl808/ipc.d @@ -0,0 +1,49 @@ +/// BL808 inter-processor communication +/// +/// Initializes XRAM ring buffers and provides typed send/receive +/// for peripheral commands and network frames. +module sys.bl808.ipc; + +import sys.bl808.xram; + +@nogc nothrow: + +/// Global ring buffer handles (initialized by ipc_init) +__gshared XramRing[RingId.max] rings; + +/// Initialize all XRAM ring buffers from the shared memory layout. +/// Call once at startup after M0 has initialized the XRAM region. +void ipc_init() +{ + // TODO: read ring layout from XRAM_BASE + // The M0 firmware sets up ring_pos structures at fixed offsets. + // For now this is a placeholder — actual offsets need to be + // determined by examining the running M0 firmware's XRAM layout. +} + +/// Send a peripheral command (GPIO/SPI/PWM/Flash) +bool peri_send(PeriType type, const(ubyte)[] payload) +{ + PeriHeader hdr; + hdr.type = type; + hdr.err = 0; + hdr.len = cast(ushort) payload.length; + + auto written = rings[RingId.peripheral].write((cast(ubyte*)&hdr)[0 .. 4]); + if (written != PeriHeader.sizeof) + return false; + + if (payload.length > 0) + { + written = rings[RingId.peripheral].write(payload); + if (written != payload.length) + return false; + } + return true; +} + +/// Receive a peripheral response header. Returns false if ring is empty. +bool peri_recv(ref PeriHeader hdr) +{ + return PeriHeader.sizeof == rings[RingId.peripheral].read((cast(ubyte*)&hdr)[0 .. 4]); +} diff --git a/src/sys/bl808/irq.d b/src/sys/bl808/irq.d new file mode 100644 index 0000000..3e2a848 --- /dev/null +++ b/src/sys/bl808/irq.d @@ -0,0 +1,131 @@ +module sys.bl808.irq; + +import core.volatile; + +nothrow @nogc: + + +// ================================================================ +// CPU interrupt control +// ================================================================ + +// Enable interrupt delivery. Returns previous state. +bool enable_interrupts() +{ + ulong prev; + asm nothrow @nogc { "csrrsi %0, mstatus, 0x8" : "=r" (prev); } + return (prev & 0x8) != 0; +} + +// Disable interrupt delivery. Returns previous state. +bool disable_interrupts() +{ + ulong prev; + asm nothrow @nogc { "csrrci %0, mstatus, 0x8" : "=r" (prev); } + return (prev & 0x8) != 0; +} + +// Set interrupt delivery state. Returns previous state. +bool set_interrupts(bool state) +{ + return state ? enable_interrupts() : disable_interrupts(); +} + +// Halt CPU until an interrupt is pending. Near-zero power. +void wait_for_interrupt() +{ + asm nothrow @nogc { "wfi"; } +} + +// ================================================================ +// Interrupt source control +// ================================================================ + +// Interrupt classes (mie register bits) +enum IrqClass : uint { software = 3, timer = 7, external = 11 } + +// Enable an interrupt class. Returns previous state. +bool enable_irq(IrqClass c) +{ + ulong prev; + asm nothrow @nogc { "csrrs %0, mie, %1" : "=r" (prev) : "r" (1UL << c); } + return (prev & (1UL << c)) != 0; +} + +// Disable an interrupt class. Returns previous state. +bool disable_irq(IrqClass c) +{ + ulong prev; + asm nothrow @nogc { "csrrc %0, mie, %1" : "=r" (prev) : "r" (1UL << c); } + return (prev & (1UL << c)) != 0; +} + +enum uint irq_max = 80; + +alias IrqHandler = void function(uint irq) nothrow @nogc; + +// Set the external interrupt handler. Receives the PLIC IRQ number. +// Returns the previous handler for chaining. +IrqHandler irq_set_handler(IrqHandler handler) +{ + auto prev = irq_handler; + irq_handler = handler; + return prev; +} + +// Enable an individual PLIC IRQ (set priority > 0 and enable bit) +bool enable_irq(uint irq) +{ + if (irq >= irq_max) + return false; + auto prio = cast(uint*)(plic_base + irq * 4); + volatileStore(prio, 1); + auto en = cast(uint*)(plic_enable + (irq / 32) * 4); + uint mask = 1U << (irq % 32); + uint prev = volatileLoad(en); + volatileStore(en, prev | mask); + return (prev & mask) != 0; +} + +// Disable an individual PLIC IRQ. Returns previous state. +bool disable_irq(uint irq) +{ + if (irq >= irq_max) + return false; + auto en = cast(uint*)(plic_enable + (irq / 32) * 4); + uint mask = 1U << (irq % 32); + uint prev = volatileLoad(en); + volatileStore(en, prev & ~mask); + return (prev & mask) != 0; +} + +private: + +enum ulong plic_base = 0xE000_0000; +enum ulong plic_enable = 0xE000_2000; + +__gshared IrqHandler irq_handler; + +public: + +// Diagnostic counters (temporary) +__gshared uint irq_count = 0; +__gshared uint[irq_max] irq_histogram; + +package: + +// Called from start.S _trap_mext +extern(C) void _irq_dispatch(uint irq) +{ + ++irq_count; + if (irq < irq_max) + ++irq_histogram[irq]; + if (irq_handler !is null) + irq_handler(irq); +} + +// Called from start.S _trap_mtimer +extern(C) void _timer_irq_handler() +{ + // TODO: hook up to urt.time tick +} diff --git a/src/sys/bl808/package.d b/src/sys/bl808/package.d new file mode 100644 index 0000000..01b0dbf --- /dev/null +++ b/src/sys/bl808/package.d @@ -0,0 +1,50 @@ +/// BL808 D0 core platform package +/// +/// Provides sys_init() as the single entry point for all +/// hardware initialization. Call from main() before anything else. +module sys.bl808; + +public import sys.bl808.uart; +public import sys.bl808.irq; +public import sys.bl808.timer; +public import sys.bl808.xram; +public import sys.bl808.ipc; + +@nogc nothrow: + +private extern(C) void __register_frame_info(const void*, void*); +private extern(C) extern const ubyte __eh_frame_start; +private ubyte[48] __eh_frame_object; // pre-allocated storage for libgcc + +/// Initialize all D0 core hardware. +/// Call once at the top of main() before any other OpenWatt code. +/// +/// Order matters: +/// 1. UART — so we have debug output for everything after +/// 2. IRQ table — already done by start.S (_init_interrupts) +/// 3. Timer — periodic tick for main loop +/// 4. IPC — XRAM ring buffers to M0 +extern(C) void sys_init() +{ + // Register .eh_frame with libgcc's unwinder so that DWARF exception + // handling works (required for fibre abort, etc.). + __register_frame_info(&__eh_frame_start, &__eh_frame_object); + + // UART0 is already initialized by M0 before D0 boots. + // Just confirm we're alive. + uart0_puts("BL808 D0: sys_init\n"); + + // Timer: set up 20Hz tick (50ms) for the main loop + // TODO: wire this to Application.run() instead of a stub + timer_set_periodic(50_000, &tick_stub); + + // IPC: initialize XRAM ring buffers + ipc_init(); + + uart0_puts("BL808 D0: ready\n"); +} + +private void tick_stub() @nogc nothrow +{ + // placeholder — will drive urt.time / Application frame tick +} diff --git a/src/sys/bl808/spi.d b/src/sys/bl808/spi.d new file mode 100644 index 0000000..12a4f08 --- /dev/null +++ b/src/sys/bl808/spi.d @@ -0,0 +1,5 @@ +/// BL808 SPI peripheral +/// +/// Direct register access to BL808 SPI controller. +/// TODO: implement when needed for hardware I/O. +module sys.bl808.spi; diff --git a/src/sys/bl808/start.S b/src/sys/bl808/start.S new file mode 100644 index 0000000..5f59bdf --- /dev/null +++ b/src/sys/bl808/start.S @@ -0,0 +1,407 @@ +/* BL808 D0 core (T-Head C906 RV64GC) startup + * + * Based on Bouffalo SDK startup.S + vectors.S, consolidated into + * a single file with no SDK header dependencies. + * + * Boot flow: + * 1. CPU setup — wait for M0, disable IRQ, extensions, FPU, trap vectors + * 2. Core regs — gp, tp, sp + * 3. Memory access — TZC release, clear PLIC + * 4. Section init — .got, .tdata, .tbss, .bss, .data + * 5. Caches — I-cache, D-cache, prefetch + * 6. Interrupts — dispatch table, enable MIE+MEIE + * 7. D runtime — sys_init, .init_array, main + * + * T-Head custom CSR numbers (not in standard riscv asm): + * mxstatus = 0x7C0 (T-Head ISA extension enable) + * mhcr = 0x7C1 (hardware cache control) + * mhint = 0x7C5 (performance hints — prefetch, AMR) + */ +#define CSR_MXSTATUS 0x7C0 +#define CSR_MHCR 0x7C1 +#define CSR_MHINT 0x7C5 + + .section .text.entry, "ax" + .global _start + .type _start, @function + .align 2 + +_start: + /* ── 1. CPU setup ─────────────────────────────────────────────── */ + + /* Wait for M0 to finish clock/peripheral init. + * M0 switches D0's CPU clock (XTAL→PLL) AFTER starting D0, which + * glitches the pipeline if we're executing. */ + li t0, 1000000 /* ~80ms at 24MHz, ~5ms at 400MHz */ +.Lwait_m0: + addi t0, t0, -1 + bnez t0, .Lwait_m0 + + /* Disable interrupts */ + csrc mstatus, 0x0008 /* MIE */ + csrc mstatus, 0x0002 /* SIE */ + + /* Enable T-Head ISA extensions (THEADISAEE + MM) */ + li t0, (1 << 22) | (1 << 15) + csrs CSR_MXSTATUS, t0 + + /* Enable FPU (FS = Initial) and vector unit (VS = Initial) */ + li t0, (1 << 13) | (1 << 23) + csrs mstatus, t0 + + /* Set trap vector table (vectored mode) */ + la t0, __vectors + ori t0, t0, 1 + csrw mtvec, t0 + + /* ── 2. Core registers ────────────────────────────────────────── */ + + .option push + .option norelax + la gp, __global_pointer$ + .option pop + la tp, _tdata_start + la sp, _stack_top + + /* ── 3. Memory access ─────────────────────────────────────────── */ + + /* Release TZC security so D0 can access full PSRAM */ + li t0, 0x20005380 /* TZC_SEC_TZC_PSRAMA_TZSRG_CTRL */ + lw t1, 0(t0) + li t2, -65537 /* ~(1 << 16) sign-extended */ + and t1, t1, t2 + sw t1, 0(t0) + + /* Clear PLIC interrupt enables and pending bits */ + li t0, 0xE0002000 /* PLIC enable base */ + li t1, 0xE0001000 /* PLIC pending base */ + li t2, 0 + li t3, 4 /* 4 words = 128 bits */ +.Lclear_plic: + sw t2, 0(t0) + sw t2, 0(t1) + addi t0, t0, 4 + addi t1, t1, 4 + addi t3, t3, -1 + bnez t3, .Lclear_plic + + /* ── 4. Section init ──────────────────────────────────────────── */ + + /* Copy .got from flash to SRAM (must come first — gp-relative access) */ + la t0, _got_load + la t1, _got_start + la t2, _got_end +.Lcopy_got: + bgeu t1, t2, .Lgot_done + ld t3, 0(t0) + sd t3, 0(t1) + addi t0, t0, 8 + addi t1, t1, 8 + j .Lcopy_got +.Lgot_done: + + /* Copy .tdata from flash to SRAM (TLS initialized data) */ + la t0, _tdata_load + la t1, _tdata_start + la t2, _tdata_end +.Lcopy_tdata: + bgeu t1, t2, .Ltdata_done + ld t3, 0(t0) + sd t3, 0(t1) + addi t0, t0, 8 + addi t1, t1, 8 + j .Lcopy_tdata +.Ltdata_done: + + /* Zero .tbss in SRAM (TLS zero-initialized data) */ + la t0, _tbss_start + la t1, _tbss_end +.Lzero_tbss: + bgeu t0, t1, .Ltbss_done + sd zero, 0(t0) + addi t0, t0, 8 + j .Lzero_tbss +.Ltbss_done: + + /* Zero BSS in PSRAM */ + la t0, _bss_start + la t1, _bss_end +.Lzero_bss: + bgeu t0, t1, .Lbss_done + sd zero, 0(t0) + addi t0, t0, 8 + j .Lzero_bss +.Lbss_done: + + /* Copy .data from flash to PSRAM */ + la t0, _data_load + la t1, _data_start + la t2, _data_end +.Lcopy_data: + bgeu t1, t2, .Ldata_done + ld t3, 0(t0) + sd t3, 0(t1) + addi t0, t0, 8 + addi t1, t1, 8 + j .Lcopy_data +.Ldata_done: + + /* ── 5. Caches ────────────────────────────────────────────────── */ + + li t0, 0x3 /* IE + DE */ + csrs CSR_MHCR, t0 + li t0, (1 << 2) | (1 << 8) | (1 << 12) + csrs CSR_MHINT, t0 + + /* ── 6. Interrupts ────────────────────────────────────────────── */ + + csrs mstatus, 0x0008 /* MIE */ + li t0, (1 << 11) /* MEIE */ + csrs mie, t0 + + /* ── 7. D runtime ─────────────────────────────────────────────── */ + + call sys_init + + /* .init_array — LDC module info registration */ + la t0, __init_array_start + la t1, __init_array_end +.Linit_array_loop: + bgeu t0, t1, .Linit_array_done + ld t2, 0(t0) + jalr ra, t2 + addi t0, t0, 8 + j .Linit_array_loop +.Linit_array_done: + + /* Jump to uRT main(int argc, char** argv) */ + li a0, 0 /* argc = 0 */ + li a1, 0 /* argv = NULL */ + call main + + /* If main returns, spin */ +.Lhalt: + wfi + j .Lhalt + + +/* ================================================================ + * Machine-mode trap vector table (vectored mode) + * + * mtvec[1:0] = 01 means: on interrupt with cause N, jump to + * mtvec_base + N * 4. Each entry is a single jump instruction. + * ================================================================ */ + + .section .text, "ax" + .align 6 + .global __vectors + .type __vectors, @object +__vectors: + .option push + .option norvc + j _trap_exception /* 0: exception (sync trap) */ + j _trap_default /* 1: S-mode software interrupt */ + j _trap_default /* 2: reserved */ + j _trap_msoft /* 3: M-mode software interrupt */ + j _trap_default /* 4: reserved */ + j _trap_default /* 5: S-mode timer */ + j _trap_default /* 6: reserved */ + j _trap_mtimer /* 7: M-mode timer */ + j _trap_default /* 8: reserved */ + j _trap_default /* 9: S-mode external */ + j _trap_default /* 10: reserved */ + j _trap_mext /* 11: M-mode external interrupt */ + .option pop + + +/* ================================================================ + * Trap handlers + * ================================================================ */ + + .align 2 +_trap_default: + j _trap_default + + .align 2 +_trap_msoft: + mret + +/* + * ISR save/restore macros. + * + * Integer caller-saved: ra, t0-t6, a0-a7 (16 regs × 8 = 128 bytes) + * FP caller-saved: ft0-ft11, fa0-fa7 (20 regs × 8 = 160 bytes) + * Total frame: 288 bytes + */ + +.macro ISR_SAVE + addi sp, sp, -288 + sd ra, 0(sp) + sd t0, 8(sp) + sd t1, 16(sp) + sd t2, 24(sp) + sd t3, 32(sp) + sd t4, 40(sp) + sd t5, 48(sp) + sd t6, 56(sp) + sd a0, 64(sp) + sd a1, 72(sp) + sd a2, 80(sp) + sd a3, 88(sp) + sd a4, 96(sp) + sd a5, 104(sp) + sd a6, 112(sp) + sd a7, 120(sp) + fsd ft0, 128(sp) + fsd ft1, 136(sp) + fsd ft2, 144(sp) + fsd ft3, 152(sp) + fsd ft4, 160(sp) + fsd ft5, 168(sp) + fsd ft6, 176(sp) + fsd ft7, 184(sp) + fsd ft8, 192(sp) + fsd ft9, 200(sp) + fsd ft10, 208(sp) + fsd ft11, 216(sp) + fsd fa0, 224(sp) + fsd fa1, 232(sp) + fsd fa2, 240(sp) + fsd fa3, 248(sp) + fsd fa4, 256(sp) + fsd fa5, 264(sp) + fsd fa6, 272(sp) + fsd fa7, 280(sp) +.endm + +.macro ISR_RESTORE + fld ft0, 128(sp) + fld ft1, 136(sp) + fld ft2, 144(sp) + fld ft3, 152(sp) + fld ft4, 160(sp) + fld ft5, 168(sp) + fld ft6, 176(sp) + fld ft7, 184(sp) + fld ft8, 192(sp) + fld ft9, 200(sp) + fld ft10, 208(sp) + fld ft11, 216(sp) + fld fa0, 224(sp) + fld fa1, 232(sp) + fld fa2, 240(sp) + fld fa3, 248(sp) + fld fa4, 256(sp) + fld fa5, 264(sp) + fld fa6, 272(sp) + fld fa7, 280(sp) + ld ra, 0(sp) + ld t0, 8(sp) + ld t1, 16(sp) + ld t2, 24(sp) + ld t3, 32(sp) + ld t4, 40(sp) + ld t5, 48(sp) + ld t6, 56(sp) + ld a0, 64(sp) + ld a1, 72(sp) + ld a2, 80(sp) + ld a3, 88(sp) + ld a4, 96(sp) + ld a5, 104(sp) + ld a6, 112(sp) + ld a7, 120(sp) + addi sp, sp, 288 +.endm + + .align 2 +_trap_mtimer: + ISR_SAVE + call _timer_irq_handler + ISR_RESTORE + mret + + .align 2 +_trap_mext: + ISR_SAVE + + /* Read PLIC claim register to get IRQ number */ + li t0, 0xE0200004 + lw a0, 0(t0) + beqz a0, .Lmext_done + + call _irq_dispatch + + /* Write IRQ number back to PLIC claim to complete */ + li t0, 0xE0200004 + sw a0, 0(t0) + +.Lmext_done: + ISR_RESTORE + mret + + .align 2 +_trap_exception: + /* Redirect mtvec to a simple spin so double-faults don't recurse */ + la t0, .Lcrash_spin2 + csrw mtvec, t0 + + /* Save ALL registers to stack for the crash handler */ + addi sp, sp, -256 + sd ra, 0(sp) + sd sp, 8(sp) /* saved sp (pre-exception value + 256) */ + sd gp, 16(sp) + sd tp, 24(sp) + sd t0, 32(sp) + sd t1, 40(sp) + sd t2, 48(sp) + sd s0, 56(sp) /* fp */ + sd s1, 64(sp) + sd a0, 72(sp) + sd a1, 80(sp) + sd a2, 88(sp) + sd a3, 96(sp) + sd a4, 104(sp) + sd a5, 112(sp) + sd a6, 120(sp) + sd a7, 128(sp) + sd s2, 136(sp) + sd s3, 144(sp) + sd s4, 152(sp) + sd s5, 160(sp) + sd s6, 168(sp) + sd s7, 176(sp) + sd s8, 184(sp) + sd s9, 192(sp) + sd s10, 200(sp) + sd s11, 208(sp) + sd t3, 216(sp) + sd t4, 224(sp) + sd t5, 232(sp) + sd t6, 240(sp) + + /* Fix saved sp to the value before our addi */ + addi t0, sp, 256 + sd t0, 8(sp) + + /* a0 = pointer to saved register block on stack */ + mv a0, sp + /* a1 = mcause */ + csrr a1, mcause + /* a2 = mepc (faulting PC) */ + csrr a2, mepc + /* a3 = mtval (faulting address/instruction) */ + csrr a3, mtval + + call _crash_handler + + /* If crash handler returns or double-faults, spin here */ +.Lcrash_spin: + wfi + j .Lcrash_spin + + /* Non-vectored mtvec target for double-fault: spin immediately */ + .align 2 +.Lcrash_spin2: + wfi + j .Lcrash_spin2 diff --git a/src/sys/bl808/syscalls.d b/src/sys/bl808/syscalls.d new file mode 100644 index 0000000..003893d --- /dev/null +++ b/src/sys/bl808/syscalls.d @@ -0,0 +1,75 @@ +/// BL808 newlib syscall stubs and POSIX networking stubs +/// +/// _write routes stdout/stderr to UART0. +/// Network stubs return -1 until replaced by XRAM WiFi IPC. +module sys.bl808.syscalls; + +import sys.bl808.uart; + +private: + +extern(C) extern __gshared { + pragma(mangle, "_bss_end") void* _bss_end_ptr; + pragma(mangle, "__heap_end") void* _heap_end_ptr; +} + +__gshared int errno_val = 0; + +public: + +// ================================================================ +// newlib syscall stubs +// ================================================================ + +extern(C) int _close(int fd) @nogc nothrow { return -1; } +extern(C) int _read(int fd, void* buf, size_t n) @nogc nothrow { return 0; } + +extern(C) int _write(int fd, const(void)* buf, size_t n) @nogc nothrow +{ + if (fd == 1 || fd == 2) + uart0_puts((cast(const(char)*) buf)[0 .. n]); + return cast(int) n; +} + +extern(C) int _lseek(int fd, int offset, int whence) @nogc nothrow { return 0; } +extern(C) int _fstat(int fd, void* st) @nogc nothrow { return 0; } +extern(C) int _isatty(int fd) @nogc nothrow { return 1; } + +extern(C) void* _sbrk(int incr) @nogc nothrow +{ + __gshared char* heap = null; + if (heap is null) + heap = cast(char*)&_bss_end_ptr; + char* new_heap = heap + incr; + if (new_heap > cast(char*)&_heap_end_ptr) + return cast(void*)-1; // ENOMEM + char* prev = heap; + heap = new_heap; + return prev; +} + +extern(C) void _exit(int code) @nogc nothrow { while (true) {} } +extern(C) int _kill(int pid, int sig) @nogc nothrow { return -1; } +extern(C) int _getpid() @nogc nothrow { return 1; } + +// ================================================================ +// POSIX networking stubs (replaced by XRAM WiFi IPC when ready) +// ================================================================ + +extern(C) int* __errno_location() @nogc nothrow { return &errno_val; } +extern(C) int socket(int domain, int type, int protocol) @nogc nothrow { return -1; } +extern(C) int close(int fd) @nogc nothrow { return -1; } +extern(C) int poll(void* fds, uint nfds, int timeout) @nogc nothrow { return -1; } +extern(C) int _accept(int fd, void* addr, void* len) @nogc nothrow { return -1; } +extern(C) ptrdiff_t _recv(int fd, void* buf, size_t len, int flags) @nogc nothrow { return -1; } +extern(C) ptrdiff_t _recvfrom(int fd, void* buf, size_t len, int flags, void* src, void* al) @nogc nothrow { return -1; } +extern(C) ptrdiff_t _sendmsg(int fd, const(void)* msg, int flags) @nogc nothrow { return -1; } +extern(C) int _shutdown(int fd, int how) @nogc nothrow { return -1; } +extern(C) int _bind(int fd, const(void)* addr, uint len) @nogc nothrow { return -1; } +extern(C) int _listen(int fd, int backlog) @nogc nothrow { return -1; } +extern(C) int _connect(int fd, const(void)* addr, uint len) @nogc nothrow { return -1; } +extern(C) int setsockopt(int fd, int level, int name, const(void)* val, uint len) @nogc nothrow { return -1; } +extern(C) int getsockname(int fd, void* addr, uint* len) @nogc nothrow { return -1; } +extern(C) int getpeername(int fd, void* addr, uint* len) @nogc nothrow { return -1; } +extern(C) int getaddrinfo(const(char)* node, const(char)* service, const(void)* hints, void** res) @nogc nothrow { return -1; } +extern(C) void freeaddrinfo(void* res) @nogc nothrow {} diff --git a/src/sys/bl808/timer.d b/src/sys/bl808/timer.d new file mode 100644 index 0000000..d7a7e91 --- /dev/null +++ b/src/sys/bl808/timer.d @@ -0,0 +1,241 @@ +/// BL808 C906 timer support +/// +/// Two time sources: +/// +/// 1. mtime (RISC-V standard) — 1 MHz monotonic counter. +/// Read via rdtime. Survives WFI/clock scaling, resets on system reset. +/// Used for monotonic timekeeping (getTime / Duration / Timer). +/// +/// 2. HBN RTC — 32,768 Hz counter in the Hibernate block. +/// 40-bit, survives deep sleep (HBN) if VBAT is maintained. +/// Resets on full power cycle. Used with a stored UTC offset +/// for wall-clock time across sleep/wake cycles. +/// +/// CLINT layout (T-Head C906): +/// CORET_BASE = 0xE400_0000 (PLIC_BASE + 0x400_0000) +/// MTIMECMPL0 @ CORET_BASE + 0x4000 +/// MTIMECMPH0 @ CORET_BASE + 0x4004 +/// +/// HBN layout: +/// HBN_BASE = 0x2000_F000 +/// HBN_CTL @ +0x00 — bit 0: RTC enable +/// HBN_TIME_L @ +0x04 — compare value low (alarm) +/// HBN_TIME_H @ +0x08 — compare value high (alarm) +/// HBN_RTC_TIME_L @ +0x0C — latched counter low (read-only) +/// HBN_RTC_TIME_H @ +0x10 — latched counter high [7:0] + latch trigger [31] +module sys.bl808.timer; + +import core.volatile; + +@nogc nothrow: + +// ================================================================ +// Hardware addresses +// ================================================================ + +private enum ulong CORET_BASE = 0xE400_0000; +private enum ulong MTIMECMPL0 = CORET_BASE + 0x4000; +private enum ulong MTIMECMPH0 = CORET_BASE + 0x4004; + +private enum ulong HBN_BASE = 0x2000_F000; +private enum ulong HBN_CTL = HBN_BASE + 0x00; +private enum ulong HBN_RTC_TIME_L = HBN_BASE + 0x0C; +private enum ulong HBN_RTC_TIME_H = HBN_BASE + 0x10; + +// ================================================================ +// mtime frequency +// +// The BL808 mtime counter runs at 1MHz (from the XTAL/PLL +// divided down). Confirm on hardware by measuring against +// a known delay or reading the clock tree registers. +// ================================================================ + +enum uint mtime_freq_hz = 1_000_000; + +// ================================================================ +// Time reading +// ================================================================ + +/// Read the monotonic mtime counter via rdtime. +/// Does not stop during WFI, unaffected by clock scaling. +ulong mtime_read() +{ + ulong t; + asm @nogc nothrow { "rdtime %0" : "=r" (t); } + return t; +} + +/// Read CPU cycle counter (for profiling, NOT timekeeping). +/// Stops during WFI, rate changes with clock scaling. +ulong mcycle_read() +{ + ulong c; + asm @nogc nothrow { "rdcycle %0" : "=r" (c); } + return c; +} + +// ================================================================ +// Timer interrupt (periodic tick) +// ================================================================ + +private __gshared ulong tick_interval = 0; +private __gshared void function() @nogc nothrow tick_callback = null; + +/// Set up a periodic timer interrupt. +/// interval_us: microseconds between ticks +/// callback: called from _timer_irq_handler (keep it short!) +void timer_set_periodic(ulong interval_us, void function() @nogc nothrow callback) +{ + import sys.bl808.irq : IrqClass, enable_irq; + + tick_interval = interval_us; + tick_callback = callback; + + ulong now = mtime_read(); + mtimecmp_write(now + tick_interval); + enable_irq(IrqClass.timer); +} + +/// Stop the periodic timer +void timer_stop() +{ + import sys.bl808.irq : IrqClass, disable_irq; + + disable_irq(IrqClass.timer); + mtimecmp_write(ulong.max); + tick_callback = null; +} + +/// Called from start.S _trap_mtimer. +/// Sets next deadline and invokes the user callback. +extern(C) void _timer_irq_handler() +{ + if (tick_interval > 0) + { + // Advance deadline relative to current compare value + // (not current time — avoids drift) + ulong cmp = mtimecmp_read(); + mtimecmp_write(cmp + tick_interval); + } + + if (tick_callback !is null) + tick_callback(); +} + +// ================================================================ +// MTIMECMP register access +// +// Split into two 32-bit writes. Write high word to max first +// to prevent a spurious interrupt when the low word is updated. +// ================================================================ + +/// Set mtimecmp for a one-shot wakeup (used by sleep). +/// The periodic timer handler will re-arm on the next tick if active. +void mtimecmp_write_oneshot(ulong value) +{ + mtimecmp_write(value); +} + +private void mtimecmp_write(ulong value) +{ + auto lo = cast(uint*)MTIMECMPL0; + auto hi = cast(uint*)MTIMECMPH0; + + // Write 0xFFFFFFFF to high first to prevent spurious fire + volatileStore(hi, 0xFFFF_FFFF); + volatileStore(lo, cast(uint)(value & 0xFFFF_FFFF)); + volatileStore(hi, cast(uint)(value >> 32)); +} + +private ulong mtimecmp_read() +{ + auto lo = cast(uint*)MTIMECMPL0; + auto hi = cast(uint*)MTIMECMPH0; + + // Read high-low-high to handle rollover + uint h1 = volatileLoad(hi); + uint l = volatileLoad(lo); + uint h2 = volatileLoad(hi); + if (h1 != h2) + l = volatileLoad(lo); + return (cast(ulong)h2 << 32) | l; +} + +// ================================================================ +// HBN RTC (32,768 Hz, 40-bit, survives hibernate) +// ================================================================ + +enum uint rtc_freq_hz = 32_768; + +/// Enable the HBN RTC counter (bit 0 of HBN_CTL). +/// Does NOT reset the counter — call rtc_reset() first if needed. +/// Note: read-modify-write on HBN_CTL is not interrupt-safe. +/// If called after interrupts are enabled, wrap with mstatus.MIE guard. +void rtc_enable() +{ + auto ctl = cast(uint*)HBN_CTL; + volatileStore(ctl, volatileLoad(ctl) | 0x01); +} + +/// Disable and reset the HBN RTC counter to zero. +/// Note: same mstatus.MIE guard applies — see rtc_enable(). +void rtc_reset() +{ + auto ctl = cast(uint*)HBN_CTL; + volatileStore(ctl, volatileLoad(ctl) & ~uint(0x01)); +} + +/// Read the 40-bit HBN RTC counter. +/// Latches the value first (toggle bit 31 of RTC_TIME_H), then reads. +ulong rtc_read() +{ + auto lo = cast(uint*)HBN_RTC_TIME_L; + auto hi = cast(uint*)HBN_RTC_TIME_H; + + // Latch: set bit 31, then clear it + uint h = volatileLoad(hi); + volatileStore(hi, h | (1u << 31)); + volatileStore(hi, h & ~(1u << 31)); + + uint l = volatileLoad(lo); + h = volatileLoad(hi); + return (ulong(h & 0xFF) << 32) | l; +} + +/// Convert RTC ticks (32,768 Hz) to seconds. +ulong rtc_ticks_to_sec(ulong ticks) +{ + return ticks / rtc_freq_hz; +} + +/// Convert seconds to RTC ticks. +ulong rtc_sec_to_ticks(ulong sec) +{ + return sec * rtc_freq_hz; +} + +// ================================================================ +// HBN RAM (4KB, survives hibernate if VBAT maintained) +// +// The actual storage is in hbn_ram.c, placed in .hbn_ram section +// by the linker. This avoids hardcoding addresses and lets the +// linker manage the HBN RAM region. +// ================================================================ + +/// Persistent state across hibernate cycles. +/// Backed by .hbn_ram section (see hbn_ram.c / linker script). +struct HbnPersist +{ + enum uint HBN_MAGIC = 0x4F57_4254; // "OWBT" (OpenWatt Boot Time) + + uint magic; + long utc_offset; // HBN ticks from RTC epoch to Unix epoch +} + +/// Access the persistent state in HBN RAM. +HbnPersist* hbn_persist() +{ + return cast(HbnPersist*)&_hbn_persist; +} + +private extern extern(C) __gshared HbnPersist _hbn_persist; diff --git a/src/sys/bl808/uart.d b/src/sys/bl808/uart.d new file mode 100644 index 0000000..f0f0e1b --- /dev/null +++ b/src/sys/bl808/uart.d @@ -0,0 +1,594 @@ +// BL808 UART driver +// +// Four UART peripherals sharing the same register-level IP block (BL6xx/BL8xx): +// +// UART0 0x2000_A000 MCU domain M0's console (COM7 on M1s Dock) +// UART1 0x2000_A100 MCU domain General purpose +// UART2 0x2000_AA00 MCU domain Shares address with ISO11898 CAN (mutually exclusive) +// UART3 0x3000_2000 MM domain D0's console (COM8 on M1s Dock) +// +// M0 initializes UART0 before releasing D0. M0 initializes UART3 clock +// ("UART CLK select MM XCLK") shortly after starting D0. +// +// GPIO pin muxing is handled by M0 at boot. UART0/3 are pre-configured. +// UART1/2 require M0 cooperation or future GPIO mux support from D0. +// +// Interrupt availability from D0: +// UART3 — PLIC IRQ 20 (IRQ_NUM_BASE + 4), interrupt-driven +// UART0/1/2 — IRQs are in M0's PLIC domain, must be polled from D0 +// +// All UARTs use software ring buffers (512 bytes RX, 512 bytes TX). +// UART3 fills/drains them via ISR. UART0/1/2 require explicit uart_poll(). +module sys.bl808.uart; + +import core.volatile; +import sys.bl808.irq; + +nothrow @nogc: + + +// ══════════════════════════════════════════════════════════════════════════════ +// Register definitions +// ══════════════════════════════════════════════════════════════════════════════ + +enum UartId : uint { uart0 = 0, uart1 = 1, uart2 = 2, uart3 = 3 } + +private immutable uint[4] uart_base = [ + 0x2000_A000, // UART0 + 0x2000_A100, // UART1 + 0x2000_AA00, // UART2 (shared with ISO11898 CAN) + 0x3000_2000, // UART3 +]; + +// D0 PLIC IRQ numbers (IRQ_NUM_BASE = 16 for D0) +private enum uint UART3_PLIC_IRQ = 20; // IRQ_NUM_BASE + 4 + +// Register offsets +private enum : uint +{ + UTX_CONFIG = 0x00, + URX_CONFIG = 0x04, + BIT_PRD = 0x08, + DATA_CONFIG = 0x0C, + UTX_IR_POSITION = 0x10, + URX_IR_POSITION = 0x14, + URX_RTO_TIMER = 0x18, + SW_MODE = 0x1C, + INT_STS = 0x20, + INT_MASK = 0x24, + INT_CLEAR = 0x28, + INT_EN = 0x2C, + STATUS = 0x30, + FIFO_CONFIG_0 = 0x80, + FIFO_CONFIG_1 = 0x84, + FIFO_WDATA = 0x88, + FIFO_RDATA = 0x8C, +} + +// UTX_CONFIG (0x00) bits +private enum : uint +{ + CR_UTX_EN = 1 << 0, + CR_UTX_CTS_EN = 1 << 1, + CR_UTX_FRM_EN = 1 << 2, + CR_UTX_LIN_EN = 1 << 3, + CR_UTX_PRT_EN = 1 << 4, + CR_UTX_PRT_SEL = 1 << 5, // 0 = even, 1 = odd + CR_UTX_BIT_CNT_D_SHIFT = 8, // [10:8] data bits: value + 4 (so 4 = 8-bit) + CR_UTX_BIT_CNT_D_MASK = 0x7 << 8, + CR_UTX_BIT_CNT_P_SHIFT = 12, // [13:12] stop bits + CR_UTX_BIT_CNT_P_MASK = 0x3 << 12, +} + +// URX_CONFIG (0x04) bits +private enum : uint +{ + CR_URX_EN = 1 << 0, + CR_URX_PRT_EN = 1 << 4, + CR_URX_PRT_SEL = 1 << 5, + CR_URX_BIT_CNT_D_SHIFT = 8, + CR_URX_BIT_CNT_D_MASK = 0x7 << 8, +} + +// FIFO_CONFIG_0 (0x80) bits +private enum : uint +{ + DMA_TX_EN = 1 << 0, + DMA_RX_EN = 1 << 1, + TX_FIFO_CLR = 1 << 2, + RX_FIFO_CLR = 1 << 3, + TX_FIFO_OVERFLOW = 1 << 4, + TX_FIFO_UNDERFLOW = 1 << 5, + RX_FIFO_OVERFLOW = 1 << 6, + RX_FIFO_UNDERFLOW = 1 << 7, +} + +// FIFO_CONFIG_1 (0x84) bits +private enum : uint +{ + TX_FIFO_CNT_MASK = 0x3F, // [5:0] available TX slots + RX_FIFO_CNT_SHIFT = 8, + RX_FIFO_CNT_MASK = 0x3F << 8, // [13:8] available RX bytes + TX_FIFO_TH_SHIFT = 16, + TX_FIFO_TH_MASK = 0x1F << 16, // [20:16] + RX_FIFO_TH_SHIFT = 24, + RX_FIFO_TH_MASK = 0x1F << 24, // [28:24] +} + +// INT_STS / INT_MASK / INT_CLEAR / INT_EN bit positions +private enum : uint +{ + INT_UTX_END = 1 << 0, + INT_URX_END = 1 << 1, + INT_UTX_FIFO = 1 << 2, + INT_URX_FIFO = 1 << 3, + INT_URX_RTO = 1 << 4, + INT_URX_PCE = 1 << 5, + INT_UTX_FER = 1 << 6, + INT_URX_FER = 1 << 7, + INT_MASK_ALL = 0xFF, +} + +// FIFO depth +enum UART_FIFO_MAX = 32; + +// RX FIFO threshold — interrupt fires when RX FIFO count >= this value. +// Set to 16 so we drain before the 32-byte FIFO overflows. +private enum RX_FIFO_THRESHOLD = 16; + +// TX FIFO threshold — interrupt fires when TX FIFO count >= this value +// (i.e., space is available). Set low so we refill aggressively. +private enum TX_FIFO_THRESHOLD = 8; + +// RX timeout — number of bit periods of silence before RX timeout interrupt. +// Catches partial frames shorter than RX_FIFO_THRESHOLD. +private enum RX_TIMEOUT_BITS = 80; // ~10 byte times at any baud rate + +// UART clock frequency — M0 sets all UARTs to XCLK = 40 MHz. +// TODO: read GLB_UART_CFG0 (GLB_BASE + 0x150) to determine actual clock +// source. Bits: [2:0] = divider, [4] = clock enable, [7] = clk_sel, +// [22] = clk_sel2. The 2-bit selector chooses between MCU PBCLK (0), +// 160 MHz PLL mux (1), or XCLK (2). Actual uart_clk = source / (div + 1). +private enum uint UART_CLK_HZ = 40_000_000; + + +// Software ring buffer — reuse urt.mem.ring with fixed 512-byte capacity. +import urt.mem.ring : RingBuffer; +private alias Ring = RingBuffer!512; + + +// ══════════════════════════════════════════════════════════════════════════════ +// Driver API +// ══════════════════════════════════════════════════════════════════════════════ + +enum UartParity : ubyte { none, odd, even } +enum UartStopBits : ubyte { half, one, one_point_five, two } + +struct UartConfig +{ + uint baud_rate = 9600; + ubyte data_bits = 8; // 5..8 + UartStopBits stop_bits = UartStopBits.one; + UartParity parity = UartParity.none; +} + +// Per-UART state +private __gshared Ring[4] rx_ring; +private __gshared Ring[4] tx_ring; +private __gshared bool[4] uart_open_flag; +private __gshared IrqHandler prev_irq_handler; +private __gshared bool irq_handler_installed; + +// Open a UART: configure baud rate, frame format, clear FIFOs, enable TX+RX. +// UART3 gets interrupt-driven I/O. UART0/1/2 require uart_poll(). +// Returns false if id is out of range. +bool uart_open(UartId id, UartConfig cfg) +{ + if (id > UartId.max) + return false; + + immutable base = uart_base[id]; + + // Disable TX and RX while reconfiguring + auto tx_cfg = reg_read(base, UTX_CONFIG); + auto rx_cfg = reg_read(base, URX_CONFIG); + tx_cfg &= ~CR_UTX_EN; + rx_cfg &= ~CR_URX_EN; + reg_write(base, UTX_CONFIG, tx_cfg); + reg_write(base, URX_CONFIG, rx_cfg); + + // Baud rate divisor + immutable uint div = cast(uint)((cast(ulong)UART_CLK_HZ * 10 / cfg.baud_rate + 5) / 10); + reg_write(base, BIT_PRD, ((div - 1) << 16) | (div - 1)); + + // TX config: data bits, stop bits, parity + tx_cfg &= ~(CR_UTX_BIT_CNT_D_MASK | CR_UTX_BIT_CNT_P_MASK | CR_UTX_PRT_EN | CR_UTX_PRT_SEL | CR_UTX_FRM_EN); + tx_cfg |= cast(uint)(cfg.data_bits - 4) << CR_UTX_BIT_CNT_D_SHIFT; + tx_cfg |= cast(uint)cfg.stop_bits << CR_UTX_BIT_CNT_P_SHIFT; + tx_cfg |= CR_UTX_FRM_EN; + if (cfg.parity != UartParity.none) + { + tx_cfg |= CR_UTX_PRT_EN; + if (cfg.parity == UartParity.odd) + tx_cfg |= CR_UTX_PRT_SEL; + } + + // RX config: data bits, parity (stop bits are TX-only in hardware) + rx_cfg &= ~(CR_URX_BIT_CNT_D_MASK | CR_URX_PRT_EN | CR_URX_PRT_SEL); + rx_cfg |= cast(uint)(cfg.data_bits - 4) << CR_URX_BIT_CNT_D_SHIFT; + if (cfg.parity != UartParity.none) + { + rx_cfg |= CR_URX_PRT_EN; + if (cfg.parity == UartParity.odd) + rx_cfg |= CR_URX_PRT_SEL; + } + + // Clear software ring buffers + rx_ring[id].purge(); + tx_ring[id].purge(); + + // Mask all interrupts initially + reg_write(base, INT_MASK, INT_MASK_ALL); + + // Clear FIFOs + auto fifo0 = reg_read(base, FIFO_CONFIG_0); + reg_write(base, FIFO_CONFIG_0, fifo0 | TX_FIFO_CLR | RX_FIFO_CLR); + + // Set FIFO thresholds + auto fifo1 = reg_read(base, FIFO_CONFIG_1); + fifo1 &= ~(TX_FIFO_TH_MASK | RX_FIFO_TH_MASK); + fifo1 |= TX_FIFO_THRESHOLD << TX_FIFO_TH_SHIFT; + fifo1 |= RX_FIFO_THRESHOLD << RX_FIFO_TH_SHIFT; + reg_write(base, FIFO_CONFIG_1, fifo1); + + // Set RX timeout + reg_write(base, URX_RTO_TIMER, RX_TIMEOUT_BITS); + + // Enable TX + RX + tx_cfg |= CR_UTX_EN; + rx_cfg |= CR_URX_EN; + reg_write(base, UTX_CONFIG, tx_cfg); + reg_write(base, URX_CONFIG, rx_cfg); + + uart_open_flag[id] = true; + + // UART3: set up interrupt-driven I/O + if (id == UartId.uart3) + { + // Install our IRQ handler (chain with previous) + if (!irq_handler_installed) + { + prev_irq_handler = irq_set_handler(&uart_irq_handler); + irq_handler_installed = true; + } + + // Enable UART3 PLIC IRQ + enable_irq(UART3_PLIC_IRQ); + + // Unmask RX FIFO threshold + RX timeout interrupts + // TX FIFO interrupt is unmasked on demand when tx_ring has data + auto mask = reg_read(base, INT_MASK); + mask &= ~(INT_URX_FIFO | INT_URX_RTO); + reg_write(base, INT_MASK, mask); + + // Enable interrupt generation + reg_write(base, INT_EN, INT_URX_FIFO | INT_URX_RTO | INT_UTX_FIFO); + } + + return true; +} + +// Disable TX and RX, mask interrupts. +void uart_close(UartId id) +{ + if (id > UartId.max) + return; + + immutable base = uart_base[id]; + + // Mask all interrupts + reg_write(base, INT_MASK, INT_MASK_ALL); + + if (id == UartId.uart3) + disable_irq(UART3_PLIC_IRQ); + + auto tx_cfg = reg_read(base, UTX_CONFIG); + auto rx_cfg = reg_read(base, URX_CONFIG); + tx_cfg &= ~CR_UTX_EN; + rx_cfg &= ~CR_URX_EN; + reg_write(base, UTX_CONFIG, tx_cfg); + reg_write(base, URX_CONFIG, rx_cfg); + + uart_open_flag[id] = false; +} + +// Poll hardware FIFOs and transfer to/from ring buffers. +// Required for UART0/1/2 (no D0 interrupt). Harmless for UART3. +void uart_poll(UartId id) +{ + if (id > UartId.max || !uart_open_flag[id]) + return; + drain_rx_fifo(id); + fill_tx_fifo(id); +} + +// Non-blocking read: pull from RX ring buffer, return bytes read. +ptrdiff_t uart_read(UartId id, void[] buffer) +{ + if (id > UartId.max) + return -1; + + immutable prev = disable_interrupts(); + auto n = rx_ring[id].read(buffer); + set_interrupts(prev); + return cast(ptrdiff_t)n; +} + +// Non-blocking write: push into TX ring buffer, kick TX if needed. +// Returns bytes accepted (may be less than data.length if ring is full). +ptrdiff_t uart_write(UartId id, const(void)[] data) +{ + if (id > UartId.max) + return -1; + + immutable prev = disable_interrupts(); + + auto n = tx_ring[id].write(data); + + // Kick: fill hardware FIFO from ring + fill_tx_fifo(id); + + // For UART3, unmask TX FIFO interrupt so ISR continues draining + if (id == UartId.uart3 && !tx_ring[id].empty) + { + auto mask = reg_read(uart_base[id], INT_MASK); + mask &= ~INT_UTX_FIFO; + reg_write(uart_base[id], INT_MASK, mask); + } + + set_interrupts(prev); + return cast(ptrdiff_t)n; +} + +// Return number of bytes available to read from RX ring. +ptrdiff_t uart_rx_pending(UartId id) +{ + if (id > UartId.max) + return -1; + + immutable prev = disable_interrupts(); + auto p = rx_ring[id].pending; + set_interrupts(prev); + return cast(ptrdiff_t)p; +} + +// Clear RX ring buffer and hardware FIFO. Returns bytes discarded. +ptrdiff_t uart_flush(UartId id) +{ + if (id > UartId.max) + return -1; + + immutable prev = disable_interrupts(); + + immutable p = rx_ring[id].pending; + rx_ring[id].purge(); + + // Also clear hardware RX FIFO + immutable base = uart_base[id]; + auto fifo0 = reg_read(base, FIFO_CONFIG_0); + reg_write(base, FIFO_CONFIG_0, fifo0 | RX_FIFO_CLR); + + set_interrupts(prev); + return cast(ptrdiff_t)p; +} + +// Check and clear FIFO error flags (overflow/underflow). +// Returns true if any error was detected. +bool uart_check_errors(UartId id) +{ + if (id > UartId.max) + return true; + + immutable base = uart_base[id]; + immutable fifo0 = reg_read(base, FIFO_CONFIG_0); + immutable errors = fifo0 & (TX_FIFO_OVERFLOW | TX_FIFO_UNDERFLOW | RX_FIFO_OVERFLOW | RX_FIFO_UNDERFLOW); + + if (errors != 0) + { + // Clear error flags by writing them back + reg_write(base, FIFO_CONFIG_0, fifo0); + return true; + } + + return false; +} + + +// ══════════════════════════════════════════════════════════════════════════════ +// Interrupt handler and FIFO transfer +// ══════════════════════════════════════════════════════════════════════════════ + +private: + +// Drain hardware RX FIFO into software ring buffer. +void drain_rx_fifo(UartId id) +{ + immutable base = uart_base[id]; + ubyte[1] b = void; + + while (rx_ring[id].available > 0) + { + immutable avail = (reg_read(base, FIFO_CONFIG_1) & RX_FIFO_CNT_MASK) >> RX_FIFO_CNT_SHIFT; + if (avail == 0) + break; + b[0] = cast(ubyte)reg_read(base, FIFO_RDATA); + rx_ring[id].write(b); + } +} + +// Fill hardware TX FIFO from software ring buffer. +void fill_tx_fifo(UartId id) +{ + immutable base = uart_base[id]; + ubyte[1] b = void; + + while (!tx_ring[id].empty) + { + immutable space = reg_read(base, FIFO_CONFIG_1) & TX_FIFO_CNT_MASK; + if (space == 0) + break; + tx_ring[id].read(b); + reg_write(base, FIFO_WDATA, cast(uint)b[0]); + } +} + +// PLIC IRQ handler — services UART3 interrupts. +// Chains to previous handler for non-UART IRQs. +void uart_irq_handler(uint irq) +{ + if (irq == UART3_PLIC_IRQ) + { + immutable base = uart_base[UartId.uart3]; + immutable sts = reg_read(base, INT_STS); + immutable mask = reg_read(base, INT_MASK); + immutable active = sts & ~mask; + + // RX FIFO threshold or RX timeout — drain into ring + if (active & (INT_URX_FIFO | INT_URX_RTO)) + { + drain_rx_fifo(UartId.uart3); + if (active & INT_URX_RTO) + reg_write(base, INT_CLEAR, INT_URX_RTO); + } + + // TX FIFO has space — refill from ring + if (active & INT_UTX_FIFO) + { + fill_tx_fifo(UartId.uart3); + // If ring is drained, mask TX interrupt until more data arrives + if (tx_ring[UartId.uart3].empty) + { + auto m = reg_read(base, INT_MASK); + m |= INT_UTX_FIFO; + reg_write(base, INT_MASK, m); + } + } + } + else if (prev_irq_handler !is null) + prev_irq_handler(irq); +} + + +// ══════════════════════════════════════════════════════════════════════════════ +// Early-boot debug output (used before driver is initialized) +// ══════════════════════════════════════════════════════════════════════════════ + +public: + +private auto u0_cfg() { return cast(uint*)cast(ulong)(uart_base[0] + FIFO_CONFIG_1); } +private auto u0_wr() { return cast(uint*)cast(ulong)(uart_base[0] + FIFO_WDATA); } +private auto u3_cfg() { return cast(uint*)cast(ulong)(uart_base[3] + FIFO_CONFIG_1); } +private auto u3_wr() { return cast(uint*)cast(ulong)(uart_base[3] + FIFO_WDATA); } + +// ── UART0 (COM7, MCU domain) ──────────────────────────────────────────────── + +void uart0_putc(char c) +{ + while ((volatileLoad(u0_cfg) & 0x3F) == 0) {} + volatileStore(u0_wr, cast(uint)c); +} + +void uart0_puts(const(char)[] s) +{ + foreach (c; s) + { + if (c == '\n') + uart0_putc('\r'); + uart0_putc(c); + } +} + +void uart0_print(const(char)* s) +{ + while (*s != 0) + { + if (*s == '\n') + uart0_putc('\r'); + uart0_putc(*s); + ++s; + } +} + +void uart0_hex(ulong val) +{ + uart0_putc('0'); + uart0_putc('x'); + int start = 60; + while (start > 0 && ((val >> start) & 0xF) == 0) + start -= 4; + for (int i = start; i >= 0; i -= 4) + { + uint nibble = cast(uint)((val >> i) & 0xF); + uart0_putc(nibble < 10 ? cast(char)('0' + nibble) : cast(char)('a' + nibble - 10)); + } +} + +// ── UART3 (COM8, MM domain — D0's console) ────────────────────────────────── + +void uart3_putc(char c) +{ + while ((volatileLoad(u3_cfg) & 0x3F) == 0) {} + volatileStore(u3_wr, cast(uint)c); +} + +void uart3_puts(const(char)[] s) +{ + foreach (c; s) + { + if (c == '\n') + uart3_putc('\r'); + uart3_putc(c); + } +} + +void uart3_print(const(char)* s) +{ + while (*s != 0) + { + if (*s == '\n') + uart3_putc('\r'); + uart3_putc(*s); + ++s; + } +} + +void uart3_hex(ulong val) +{ + uart3_putc('0'); + uart3_putc('x'); + int start = 60; + while (start > 0 && ((val >> start) & 0xF) == 0) + start -= 4; + for (int i = start; i >= 0; i -= 4) + { + uint nibble = cast(uint)((val >> i) & 0xF); + uart3_putc(nibble < 10 ? cast(char)('0' + nibble) : cast(char)('a' + nibble - 10)); + } +} + + +// ══════════════════════════════════════════════════════════════════════════════ +// Register access helpers +// ══════════════════════════════════════════════════════════════════════════════ + +private: + +uint reg_read(uint base, uint offset) +{ + return volatileLoad(cast(uint*)cast(ulong)(base + offset)); +} + +void reg_write(uint base, uint offset, uint value) +{ + volatileStore(cast(uint*)cast(ulong)(base + offset), value); +} diff --git a/src/sys/bl808/wifi.d b/src/sys/bl808/wifi.d new file mode 100644 index 0000000..cbac5ee --- /dev/null +++ b/src/sys/bl808/wifi.d @@ -0,0 +1,6 @@ +/// BL808 WiFi interface via XRAM IPC to M0 +/// +/// M0 runs the WiFi/BT stack. D0 sends commands (connect, disconnect) +/// and exchanges Ethernet frames via the XRAM NET ring buffer. +/// TODO: implement using ipc module. +module sys.bl808.wifi; diff --git a/src/sys/bl808/xram.d b/src/sys/bl808/xram.d new file mode 100644 index 0000000..7c24185 --- /dev/null +++ b/src/sys/bl808/xram.d @@ -0,0 +1,225 @@ +/// BL808 XRAM inter-processor communication +/// +/// D0 ↔ M0 shared memory ring buffers at 0x2202_0000 (16KB). +/// Each ring has 16-bit head/tail cursors in shared memory, +/// requiring volatile access and memory barriers. +/// +/// Ring IDs (fixed by M0 firmware): +/// 0 = LOG_C906 D0 log output +/// 1 = LOG_E902 LP core log +/// 2 = NET Ethernet frames (WiFi bridge) +/// 3 = PERIPHERAL GPIO/SPI/PWM/Flash control +/// 4 = RPC Remote procedure calls +module sys.bl808.xram; + +import core.volatile; + +@nogc nothrow: + +// ================================================================ +// Constants +// ================================================================ + +enum ulong XRAM_BASE = 0x2202_0000; +enum uint XRAM_SIZE = 16 * 1024; + +enum RingId : uint +{ + log_c906 = 0, + log_e902 = 1, + net = 2, + peripheral = 3, + rpc = 4, + max = 5, +} + +// ================================================================ +// Peripheral message header (4 bytes) +// Used on PERIPHERAL ring for GPIO/SPI/PWM/Flash ops +// ================================================================ + +struct PeriHeader +{ + ubyte type; + ubyte err; + ushort len; +} + +enum PeriType : ubyte +{ + flash = 0x31, + pwm = 0x32, + spi = 0x33, +} + +// ================================================================ +// Net message header (12 bytes) +// Used on NET ring for Ethernet frames and WiFi commands +// ================================================================ + +struct NetHeader +{ + ubyte[4] magic; // "ring" = [0x72, 0x69, 0x6e, 0x67] + ushort len; + ubyte type; // high nibble: msg type, low nibble: dev type + ubyte flag; + ushort crc16; + ushort reserved; +} + +enum NetMsgType : ubyte +{ + command = 0, + frame = 1, + sniffer_pkt = 2, +} + +// ================================================================ +// WiFi operation commands +// ================================================================ + +enum WifiOp : uint +{ + init_ = 0, + deinit = 1, + connect = 2, + disconnect = 3, + upload_stream = 4, +} + +struct WifiConnect +{ + char[32] ssid; + char[63] passwd; +} + +// ================================================================ +// Shared-memory ring buffer +// +// Layout in XRAM (set by M0 firmware): +// struct { uint16_t head, tail; uint32_t buffer_size; uint8_t buffer[]; } +// +// Head is advanced by the reader, tail by the writer. +// Both cores access these via volatile loads/stores. +// ================================================================ + +struct XramRing +{ + @nogc nothrow @trusted: + + ushort* head_ptr; + ushort* tail_ptr; + ubyte* buffer_ptr; + uint buffer_size; + + /// Bytes available to read + uint pending() + { + uint h = volatileLoad(head_ptr); + uint t = volatileLoad(tail_ptr); + if (t >= h) + return t - h; + else + return buffer_size - h + t; + } + + /// Bytes available to write + uint available() + { + uint h = volatileLoad(head_ptr); + uint t = volatileLoad(tail_ptr); + if (t >= h) + return buffer_size - t + h - 1; + else + return h - t - 1; + } + + bool empty() + { + return volatileLoad(head_ptr) == volatileLoad(tail_ptr); + } + + void reset() + { + volatileStore(head_ptr, cast(ushort) 0); + volatileStore(tail_ptr, cast(ushort) 0); + fence(); + } + + /// Read up to dst.length bytes from the ring. Returns bytes read. + uint read(ubyte[] dst) + { + fence(); // ensure we see latest writes from other core + + uint h = volatileLoad(head_ptr); + uint t = volatileLoad(tail_ptr); + uint avail = (t >= h) ? (t - h) : (buffer_size - h + t); + uint len = (dst.length < avail) ? cast(uint) dst.length : avail; + + if (len == 0) + return 0; + + uint first = buffer_size - h; + if (len <= first) + { + dst[0 .. len] = buffer_ptr[h .. h + len]; + h += len; + if (h == buffer_size) + h = 0; + } + else + { + dst[0 .. first] = buffer_ptr[h .. buffer_size]; + uint second = len - first; + dst[first .. len] = buffer_ptr[0 .. second]; + h = second; + } + + fence(); // ensure our reads complete before advancing head + volatileStore(head_ptr, cast(ushort) h); + return len; + } + + /// Write data to the ring. Returns bytes written. + uint write(const(ubyte)[] src) + { + uint h = volatileLoad(head_ptr); + uint t = volatileLoad(tail_ptr); + uint space = (t >= h) ? (buffer_size - t + h - 1) : (h - t - 1); + uint len = (src.length < space) ? cast(uint) src.length : space; + + if (len == 0) + return 0; + + uint tail_room = buffer_size - t; + if (len <= tail_room) + { + buffer_ptr[t .. t + len] = src[0 .. len]; + t += len; + if (t == buffer_size) + t = 0; + } + else + { + buffer_ptr[t .. buffer_size] = src[0 .. tail_room]; + uint second = len - tail_room; + buffer_ptr[0 .. second] = src[tail_room .. len]; + t = second; + } + + fence(); // ensure writes visible before advancing tail + volatileStore(tail_ptr, cast(ushort) t); + return len; + } +} + +/// Memory barrier — RISC-V fence instruction +private void fence() @nogc nothrow +{ + version (RISCV64) + asm @nogc nothrow { "fence rw, rw"; } + else version (RISCV32) + asm @nogc nothrow { "fence rw, rw"; } + else + asm @nogc nothrow { ""; } +} diff --git a/src/urt/conv.d b/src/urt/conv.d index 55755a7..c84c68e 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -6,6 +6,19 @@ public import urt.string.format : toString; nothrow @nogc: +// Workaround for LLVM bug: riscv-isel hangs when stores into a stack buffer +// and memcmp on that buffer are visible in the same function with +// +unaligned-scalar-mem. Preventing inlining keeps them in separate functions. +// See: https://github.com/llvm/llvm-project/issues/XXXXX +pragma(inline, false) private bool streq(const(char)[] a, const(char)[] b) pure +{ + if (a.length != b.length) + return false; + foreach (i; 0 .. a.length) + if (a[i] != b[i]) + return false; + return true; +} // on error or not-a-number cases, bytes_taken will contain 0 @@ -566,21 +579,21 @@ unittest assert(format_int(-123, null, 10, 2) == 4); size_t len = format_int(0, buffer); - assert(buffer[0 .. len] == "0"); + assert(streq(buffer[0 .. len], "0")); len = format_int(14, buffer); - assert(buffer[0 .. len] == "14"); + assert(streq(buffer[0 .. len], "14")); len = format_int(14, buffer, 2); - assert(buffer[0 .. len] == "1110"); + assert(streq(buffer[0 .. len], "1110")); len = format_int(14, buffer, 8, 3); - assert(buffer[0 .. len] == " 16"); + assert(streq(buffer[0 .. len], " 16")); len = format_int(14, buffer, 16, 4, '0'); - assert(buffer[0 .. len] == "000E"); + assert(streq(buffer[0 .. len], "000E")); len = format_int(-14, buffer, 16, 3, '0'); - assert(buffer[0 .. len] == "-0E"); + assert(streq(buffer[0 .. len], "-0E")); len = format_int(12345, buffer, 10, 3); - assert(buffer[0 .. len] == "12345"); + assert(streq(buffer[0 .. len], "12345")); len = format_int(-123, buffer, 10, 6); - assert(buffer[0 .. len] == " -123"); + assert(streq(buffer[0 .. len], " -123")); } diff --git a/src/urt/endian.d b/src/urt/endian.d index 2ee3da5..904eb2d 100644 --- a/src/urt/endian.d +++ b/src/urt/endian.d @@ -65,9 +65,9 @@ ulong endianToNative(T, bool little)(ref const ubyte[8] bytes) { // ctfe can't do the memory reinterpreting static if (little) - return cast(T)(bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24 | cast(ulong)bytes[4] << 32 | cast(ulong)bytes[5] << 40 | cast(ulong)bytes[6] << 48 | cast(ulong)bytes[7] << 56); + return cast(T)(bytes[0] | bytes[1] << 8 | bytes[2] << 16 | ulong(bytes[3]) << 24 | ulong(bytes[4]) << 32 | ulong(bytes[5]) << 40 | ulong(bytes[6]) << 48 | ulong(bytes[7]) << 56); else - return cast(T)(cast(ulong)bytes[0] << 56 | cast(ulong)bytes[1] << 48 | cast(ulong)bytes[2] << 40 | cast(ulong)bytes[3] << 32 | bytes[4] << 24 | bytes[5] << 16 | bytes[6] << 8 | bytes[7]); + return cast(T)(ulong(bytes[0]) << 56 | ulong(bytes[1]) << 48 | ulong(bytes[2]) << 40 | ulong(bytes[3]) << 32 | ulong(bytes[4]) << 24 | bytes[5] << 16 | bytes[6] << 8 | bytes[7]); } static if (SupportUnalignedLoadStore) { diff --git a/src/urt/exception.d b/src/urt/exception.d index fdb500d..21c5d10 100644 --- a/src/urt/exception.d +++ b/src/urt/exception.d @@ -27,20 +27,34 @@ void urt_assert(string file, size_t line, string msg) nothrow @nogc if (msg.length == 0) msg = "Assertion failed"; - debug + version (BL808) { - import urt.io : writef_to, WriteTarget; - import urt.dbg; - - version (Windows) - writef_to!(WriteTarget.debugstring, true)("{0}({1}): {2}", file, line, msg); - writef_to!(WriteTarget.stdout, true)("{0}({1}): {2}", file, line, msg); - - breakpoint(); + import sys.bl808.uart : uart0_puts, uart0_hex; + import urt.mem.temp : tconcat; + uart0_puts(tconcat("\n*** ASSERT: ", msg, " at ", file, ':', line, '\n')); + while (true) + { + // SPIN! + // (we should probably reboot) + } } else { - import urt.internal.stdc : exit; - exit(-1); + debug + { + import urt.io : writef_to, WriteTarget; + import urt.dbg; + + version (Windows) + writef_to!(WriteTarget.debugstring, true)("{0}({1}): {2}", file, line, msg); + writef_to!(WriteTarget.stdout, true)("{0}({1}): {2}", file, line, msg); + + breakpoint(); + } + else + { + import urt.internal.stdc : exit; + exit(-1); + } } } diff --git a/src/urt/fibre.d b/src/urt/fibre.d index 0391f9a..fc2b04e 100644 --- a/src/urt/fibre.d +++ b/src/urt/fibre.d @@ -37,13 +37,13 @@ alias YieldHandler = ResumeHandler function(ref Fibre yielding, AwakenEvent awak struct Fibre { -@nogc: +nothrow @nogc: this() @disable; this(ref typeof(this)) @disable; // disable copy this(typeof(this)) @disable; // disable move - this(size_t stackSize) nothrow + this(size_t stackSize) { if (!mainFibre) mainFibre = co_active(); @@ -54,7 +54,7 @@ struct Fibre finished = true; // init in a state ready to be recycled... aborted = false; - static void fibreFunc() + static void fibreFunc() nothrow { import urt.system : abort; @@ -84,8 +84,8 @@ struct Fibre } catch (Throwable e) { - import urt.log; - writeDebugf("fibre abort: {0}:{1} - {2}", e.file, e.line, e.msg); + import urt.io; + writef_to!(WriteTarget.stderr, true)("abort: {0}:{1} - {2}", e.file, e.line, e.msg); abort(); } @@ -102,14 +102,14 @@ struct Fibre fibre = co_create(stackSize, &fibreFunc, &this); } - this(FibreEntryDelegate fibreEntry, YieldHandler yieldHandler, size_t stackSize = DefaultStackSize) nothrow + this(FibreEntryDelegate fibreEntry, YieldHandler yieldHandler, size_t stackSize = DefaultStackSize) { this(cast(FibreEntryFunc)fibreEntry.funcptr, yieldHandler, fibreEntry.ptr, stackSize); is_delegate = true; } - this(FibreEntryFunc fibreEntry, YieldHandler yieldHandler, void* userData = null, size_t stackSize = DefaultStackSize) nothrow + this(FibreEntryFunc fibreEntry, YieldHandler yieldHandler, void* userData = null, size_t stackSize = DefaultStackSize) { this(stackSize); @@ -120,7 +120,7 @@ struct Fibre finished = false; } - ~this() nothrow + ~this() { assert(co_active() != fibre, "Can't delete the current fibre!"); @@ -133,12 +133,12 @@ struct Fibre co_delete(fibre); } - void resume() nothrow + void resume() { co_switch(fibre); } - void abort() nothrow + void abort() { assert(co_active() == mainFibre, "Can't abort when active; use urt.fibre.abort() instead."); @@ -147,7 +147,7 @@ struct Fibre co_switch(fibre); } - void recycle(FibreEntryDelegate fibreEntry) pure nothrow + void recycle(FibreEntryDelegate fibreEntry) pure { assert(isFinished(), "Can't recycle a fibre that hasn't finished yet!"); @@ -159,7 +159,7 @@ struct Fibre aborted = false; } - void recycle(FibreEntryFunc fibreEntry, void* userData = null) pure nothrow + void recycle(FibreEntryFunc fibreEntry, void* userData = null) pure { assert(isFinished(), "Can't recycle a fibre that hasn't finished yet!"); @@ -171,7 +171,7 @@ struct Fibre aborted = false; } - void reset() pure nothrow + void reset() pure { assert(isFinished(), "Can't restart a fibre that hasn't finished yet!"); @@ -180,13 +180,13 @@ struct Fibre aborted = false; } - bool isFinished() const pure nothrow + bool isFinished() const pure => finished; - bool wasAborted() const pure nothrow + bool wasAborted() const pure => aborted; - size_t stackSize() const pure nothrow + size_t stackSize() const pure { assert(fibre, "Fibre not created!"); auto fdata = co_get_fibre_data(fibre); @@ -340,7 +340,7 @@ unittest nothrow: alias cothread_t = void*; -alias coentry_t = void function() @nogc; +alias coentry_t = void function() nothrow @nogc; version (UseWindowsFibreAPI) { diff --git a/src/urt/file.d b/src/urt/file.d index c860ed4..c18728b 100644 --- a/src/urt/file.d +++ b/src/urt/file.d @@ -848,7 +848,8 @@ Result get_temp_filename(ref char[] buffer, const(char)[] dstDir, const(char)[] } -unittest +version (FreeStanding) {} +else unittest { import urt.string; diff --git a/src/urt/mem/alloc.d b/src/urt/mem/alloc.d index 3f8f362..d04e09b 100644 --- a/src/urt/mem/alloc.d +++ b/src/urt/mem/alloc.d @@ -6,26 +6,43 @@ nothrow @nogc: void[] alloc(size_t size) nothrow @nogc { - // TODO: we might pin the length to a debug table somewhere... return malloc(size)[0 .. size]; } +void[] realloc(void[] mem, size_t newSize) nothrow @nogc +{ + // TODO: we might pin the length to a debug table somewhere... + return urt.mem.realloc(mem.ptr, newSize)[0 .. newSize]; +} + +void free(void[] mem) nothrow @nogc +{ + // maybe check the length passed to free matches the alloc? + // ... or you know, just don't do that. + urt.mem.free(mem.ptr); +} + void[] alloc_aligned(size_t size, size_t alignment) nothrow @nogc { - import urt.util : is_power_of_2, max; + import urt.util : align_down, is_power_of_2, max; + alignment = max(alignment, (void*).sizeof); assert(is_power_of_2(alignment), "Alignment must be a power of two!"); version (Windows) { - import urt.util : align_down; - - // This is how Visual Studio's _aligned_malloc works... - // see C:\Program Files (x86)\Windows Kits\10\Source\10.0.15063.0\ucrt\heap\align.cpp - // - // This is implemented so memsize() can return the correct result. - // + void* mem = _aligned_malloc(size, alignment); + return mem ? mem[0 .. size] : null; + } + else version (Posix) + { + import core.sys.posix.stdlib; + void* mem; + return posix_memalign(&mem, alignment, size) ? null : mem[0 .. size]; + } + else version (FreeStanding) + { size_t header_size = (void*).sizeof + alignment; size_t total = header_size + size; @@ -39,25 +56,8 @@ void[] alloc_aligned(size_t size, size_t alignment) nothrow @nogc return (cast(void*)allocptr)[0 .. size]; } - else version (Posix) - { - import core.sys.posix.stdlib; - void* mem; - return posix_memalign(&mem, alignment, size) ? null : mem[0 .. size]; - } else - { - void[] mem = malloc(size)[0 .. size]; - // HACK: just for now... - assert((cast(size_t)mem.ptr & (alignment - 1)) == 0, "Memory not aligned!"); - return mem; - } -} - -void[] realloc(void[] mem, size_t newSize) nothrow @nogc -{ - // TODO: we might pin the length to a debug table somewhere... - return urt.mem.realloc(mem.ptr, newSize)[0 .. newSize]; + assert(false, "Unsupported platform"); } void[] realloc_aligned(void[] mem, size_t newSize, size_t alignment) nothrow @nogc @@ -77,62 +77,48 @@ void[] realloc_aligned(void[] mem, size_t newSize, size_t alignment) nothrow @no return newAlloc; } -// NOTE: This function is only compatible with alloc_aligned! -void[] expand(void[] mem, size_t newSize) nothrow @nogc -{ - version (Windows) - { - if (mem.ptr is null) - return null; - void* ptr = (cast(void**)mem.ptr)[-1]; - size_t head = (cast(size_t)mem.ptr - cast(size_t)ptr); - void* r = _expand(ptr, head + newSize); - if (r is null) - return null; - return mem.ptr[0 .. newSize]; - } - else - { - if (newSize <= memsize(mem.ptr)) - return mem.ptr[0 .. newSize]; - return null; - } -} - -void free(void[] mem) nothrow @nogc -{ - // maybe check the length passed to free matches the alloc? - // ... or you know, just don't do that. - - urt.mem.free(mem.ptr); -} - void free_aligned(void[] mem) nothrow @nogc { + if (mem.ptr is null) + return; version (Windows) + _aligned_free(mem.ptr); + else version (Posix) + urt.mem.free(mem.ptr); + else version (FreeStanding) { - if (mem.ptr is null) - return; void* p = (cast(void**)mem.ptr)[-1]; urt.mem.free(p); } else - urt.mem.free(mem.ptr); + assert(false, "Unsupported platform"); +} + +// NOTE: This function is only compatible with alloc_aligned! +void[] expand(void[] mem, size_t newSize) nothrow @nogc +{ + if (mem.ptr is null) + return null; + if (newSize <= memsize(mem.ptr)) + return mem.ptr[0 .. newSize]; + return null; } +// NOTE: This function is only compatible with alloc_aligned! size_t memsize(void* ptr) nothrow @nogc { + if (ptr is null) + return 0; version (Windows) + return _aligned_msize(ptr); + else version (Posix) + return malloc_usable_size(ptr); + else version (FreeStanding) { - if (ptr is null) - return 0; void* mem = (cast(void**)ptr)[-1]; - return _msize(mem) - (cast(size_t)ptr - cast(size_t)mem); + size_t offset = cast(size_t)ptr - cast(size_t)mem; + return malloc_usable_size(mem) - offset; } - else version (Posix) - return malloc_usable_size(ptr); - else version (Darwin) - return malloc_size(ptr); else assert(false, "Unsupported platform"); } @@ -141,20 +127,29 @@ size_t memsize(void* ptr) nothrow @nogc unittest { void[] mem = alloc_aligned(16, 8); + assert(mem !is null); size_t s = memsize(mem.ptr); + assert(s >= 16); mem = expand(mem, 8); + assert(mem !is null); mem = expand(mem, 16); + assert(mem !is null); free_aligned(mem); } version (Windows) { - extern(C) void* _expand(void* memblock, size_t size) nothrow @nogc; - extern(C) size_t _msize(void* _Block); + extern(C) void* _aligned_malloc(size_t size, size_t alignment) nothrow @nogc; + extern(C) void _aligned_free(void* memblock) nothrow @nogc; + extern(C) size_t _aligned_msize(void* memblock) nothrow @nogc; } version (Posix) { extern(C) size_t malloc_usable_size(void *__ptr); } +else version (FreeStanding) +{ + extern(C) size_t malloc_usable_size(void *__ptr); +} diff --git a/src/urt/mem/ring.d b/src/urt/mem/ring.d index 2bfdb48..6396eab 100644 --- a/src/urt/mem/ring.d +++ b/src/urt/mem/ring.d @@ -84,7 +84,7 @@ private: // declare some stuff outside the template... -size_t read_ring(void[] buffer, const void[] data, ref uint readCur, const int writeCur, const size_t capacity) +size_t read_ring(void[] buffer, const void[] data, ref uint readCur, const uint writeCur, const size_t capacity) { if (writeCur >= readCur) { diff --git a/src/urt/platform.d b/src/urt/platform.d index 6db5f2c..917dbc2 100644 --- a/src/urt/platform.d +++ b/src/urt/platform.d @@ -26,6 +26,6 @@ else version (Darwin) else version (FreeBSD) enum string Platform = "FreeBSD"; else version (FreeStanding) - enum string Platform = "Bare-metal"; + enum string Platform = "bare-metal"; else static assert(0, "Unsupported platform"); diff --git a/src/urt/system.d b/src/urt/system.d index afb7a59..aee7291 100644 --- a/src/urt/system.d +++ b/src/urt/system.d @@ -9,8 +9,8 @@ nothrow @nogc: enum IdleParams : ubyte { - SystemRequired = 1, // stop the system from going to sleep - DisplayRequired = 2, // keep the display turned on + system_required = 1, // stop the system from going to sleep + display_required = 2, // keep the display turned on } extern(C) noreturn abort(); @@ -29,10 +29,21 @@ void sleep(Duration duration) import urt.internal.sys.windows.winbase : Sleep; Sleep(cast(uint)duration.as!"msecs"); } - else + else version (BL808) { - // TODO: use nanosleep; usleep is deprecated! + import sys.bl808.irq : IrqClass, enable_irq, disable_irq, wait_for_interrupt; + import sys.bl808.timer : mtime_read, mtimecmp_write_oneshot; + ulong deadline = mtime_read() + duration.as!"usecs"; + mtimecmp_write_oneshot(deadline); + auto was_enabled = enable_irq(IrqClass.timer); + while (mtime_read() < deadline) + wait_for_interrupt(); + if (!was_enabled) + disable_irq(IrqClass.timer); + } + else + { usleep(cast(uint)duration.as!"usecs"); } } @@ -41,8 +52,11 @@ struct SystemInfo { string os_name; string processor; - ulong total_memory; - ulong available_memory; + ulong total_memory; // total physical RAM or heap region + ulong used_memory; // actively allocated + ulong reserved_memory; // claimed from OS/sbrk (>= used, includes freed blocks) + ulong avail_memory; // system-wide available for new allocations + ulong peak_memory; // high-water mark (0 if unavailable) Duration uptime; } @@ -50,7 +64,7 @@ SystemInfo get_sysinfo() { SystemInfo r; r.os_name = Platform; - r.processor = ProcessorFamily; + r.processor = ProcessorName; version (Windows) { MEMORYSTATUSEX mem; @@ -58,7 +72,15 @@ SystemInfo get_sysinfo() if (GlobalMemoryStatusEx(&mem)) { r.total_memory = mem.ullTotalPhys; - r.available_memory = mem.ullAvailPhys; + r.avail_memory = mem.ullAvailPhys; + } + PROCESS_MEMORY_COUNTERS pmc; + pmc.cb = PROCESS_MEMORY_COUNTERS.sizeof; + if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, pmc.sizeof)) + { + r.reserved_memory = pmc.WorkingSetSize; // resident pages + r.used_memory = pmc.PagefileUsage; // committed private bytes + r.peak_memory = pmc.PeakWorkingSetSize; } r.uptime = msecs(GetTickCount64()); } @@ -70,8 +92,14 @@ SystemInfo get_sysinfo() if (sysinfo(&info) < 0) assert(false, "sysinfo() failed!"); - r.total_memory = cast(ulong)info.totalram * info.mem_unit; - r.available_memory = cast(ulong)info.freeram * info.mem_unit; + auto unit = cast(ulong)info.mem_unit; + r.total_memory = info.totalram * unit; + r.avail_memory = info.freeram * unit; + + // Process-level stats from /proc/self/status + r.reserved_memory = read_proc_self_field("VmRSS:"); // resident set + r.used_memory = read_proc_self_field("VmSize:"); // virtual size (committed) + r.peak_memory = read_proc_self_field("VmPeak:"); r.uptime = seconds(info.uptime); } else version (Posix) @@ -79,12 +107,25 @@ SystemInfo get_sysinfo() import core.sys.posix.unistd; int pages = sysconf(_SC_PHYS_PAGES); + int avail = sysconf(_SC_AVPHYS_PAGES); int page_size = sysconf(_SC_PAGE_SIZE); assert(pages >= 0 && page_size >= 0, "sysconf() failed!"); r.total_memory = cast(ulong)pages * page_size; - static assert(false, "TODO: need `available_memory`"); + r.avail_memory = (avail >= 0) ? cast(ulong)avail * page_size : 0; + r.used_memory = r.total_memory - r.avail_memory; + r.reserved_memory = r.used_memory; + } + else version (BL808) + { + auto mi = mallinfo(); + r.total_memory = heap_len(); + r.used_memory = mi.uordblks; + r.reserved_memory = mi.arena; + r.avail_memory = r.total_memory - mi.arena + mi.fordblks; + r.peak_memory = mi.max_total_mem; + r.uptime = getAppTime(); } return r; } @@ -99,7 +140,7 @@ void set_system_idle_params(IdleParams params) enum EXECUTION_STATE ES_DISPLAY_REQUIRED = 0x00000002; enum EXECUTION_STATE ES_CONTINUOUS = 0x80000000; - SetThreadExecutionState(ES_CONTINUOUS | ((params & IdleParams.SystemRequired) ? ES_SYSTEM_REQUIRED : 0) | ((params & IdleParams.DisplayRequired) ? ES_DISPLAY_REQUIRED : 0)); + SetThreadExecutionState(ES_CONTINUOUS | ((params & IdleParams.system_required) ? ES_SYSTEM_REQUIRED : 0) | ((params & IdleParams.display_required) ? ES_DISPLAY_REQUIRED : 0)); } else version (Posix) { @@ -120,15 +161,119 @@ unittest assert(info.uptime > Duration.zero); import urt.io; - writelnf("System info: {0} - {1}, mem: {2}kb ({3}kb)", info.os_name, info.processor, info.total_memory / (1024), info.available_memory / (1024)); + writelnf("System: {0} - {1}", info.os_name, info.processor); + writelnf(" total: {0}kb used: {1}kb reserved: {2}kb free: {3}kb peak: {4}kb", + info.total_memory / 1024, info.used_memory / 1024, + info.reserved_memory / 1024, info.avail_memory / 1024, + info.peak_memory / 1024); + + version (BL808) + { + import sys.bl808.irq : irq_count, irq_histogram; + writelnf(" IRQ total: {0}", irq_count); + foreach (i; 0 .. irq_histogram.length) + { + if (irq_histogram[i] > 0) + writelnf(" IRQ {0}: {1}", i, irq_histogram[i]); + } + } } package: +version (BL808) +{ + extern(C) extern __gshared { + void* __heap_start; + void* __heap_end; + } + + struct Mallinfo + { + size_t arena; // total space from sbrk + size_t ordblks; // number of free chunks + size_t smblks; // unused + size_t hblks; // unused + size_t hblkhd; // unused + size_t uordblks; // total allocated space + size_t fordblks; // total free space + size_t keepcost; // releasable space + size_t aordblks; // number of allocated chunks + size_t max_total_mem; // max total allocated space + } + extern(C) Mallinfo mallinfo() @nogc nothrow; + + extern(C) void* _sbrk(int incr) @nogc nothrow; + + size_t heap_len() + => cast(size_t)&__heap_end - cast(size_t)&__heap_start; +} + +version (linux) +{ + // Read a field from /proc/self/status, returns value in bytes (field is in kB) + ulong read_proc_self_field(string field) nothrow @nogc + { + import urt.file : File, open, read, close, FileOpenMode; + + File f; + if (!f.open("/proc/self/status", FileOpenMode.ReadExisting)) + return 0; + + char[4096] buf = void; + size_t n; + auto r = f.read(buf, n); + f.close(); + if (!r || n == 0) + return 0; + + auto content = buf[0 .. n]; + // Find field name in content + for (size_t i = 0; i + field.length < content.length; ++i) + { + if (content[i .. i + field.length] == field) + { + // Skip whitespace after field name + size_t j = i + field.length; + while (j < content.length && (content[j] == ' ' || content[j] == '\t')) + ++j; + // Parse number + ulong val = 0; + while (j < content.length && content[j] >= '0' && content[j] <= '9') + { + val = val * 10 + (content[j] - '0'); + ++j; + } + // /proc/self/status reports in kB + return val * 1024; + } + } + return 0; + } +} + version (Windows) { - import urt.internal.sys.windows.winbase : GlobalMemoryStatusEx, MEMORYSTATUSEX; + import urt.internal.sys.windows.winbase : GlobalMemoryStatusEx, GetCurrentProcess, MEMORYSTATUSEX; + + struct PROCESS_MEMORY_COUNTERS + { + uint cb; + uint PageFaultCount; + size_t PeakWorkingSetSize; + size_t WorkingSetSize; + size_t QuotaPeakPagedPoolUsage; + size_t QuotaPagedPoolUsage; + size_t QuotaPeakNonPagedPoolUsage; + size_t QuotaNonPagedPoolUsage; + size_t PagefileUsage; + size_t PeakPagefileUsage; + } + + extern(Windows) int GetProcessMemoryInfo(void* Process, PROCESS_MEMORY_COUNTERS* ppsmemCounters, uint cb) nothrow @nogc; + + pragma(lib, "psapi"); extern(Windows) ulong GetTickCount64(); diff --git a/src/urt/time.d b/src/urt/time.d index 6615250..b0107fb 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -857,7 +857,7 @@ void set_utc_time(ulong unix_ns) private: -immutable MonoTime startTime; +__gshared immutable MonoTime startTime; __gshared immutable string[12] g_month_names = [ "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" ]; __gshared immutable uint[9] digit_multipliers = [ 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1 ]; @@ -885,7 +885,7 @@ else version (FreeStanding) enum uint nsec_multiplier = 1; } -immutable ulong sys_time_offset; +__gshared immutable ulong sys_time_offset; __gshared bool has_wall_time; package(urt) void init_clock() From abb1750a8ea93d4e2df09f64d161ed67c54e5257 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 1 Apr 2026 02:40:46 +1000 Subject: [PATCH 111/138] Purified the allocators. --- src/object.d | 89 ++++++++++++++++++++++-------------- src/urt/conv.d | 5 ++- src/urt/hash.d | 2 +- src/urt/math.d | 25 ++++++----- src/urt/mem/alloc.d | 31 +++++++------ src/urt/mem/allocator.d | 99 ++++++++++++++++++++++++----------------- src/urt/mem/region.d | 4 +- src/urt/mem/string.d | 15 ++++--- src/urt/mem/temp.d | 98 +++++++++++++++++++++++++++------------- src/urt/string/string.d | 46 +++++++++++-------- 10 files changed, 256 insertions(+), 158 deletions(-) diff --git a/src/object.d b/src/object.d index cb0ded9..70cf9e0 100644 --- a/src/object.d +++ b/src/object.d @@ -97,6 +97,22 @@ public import urt.util : min, max, swap; // return t.move; //} +// ────────────────────────────────────────────────────────────────────── +// ^^ helpers +// ────────────────────────────────────────────────────────────────────── + +static if (__VERSION__ >= 2113) +{ + static import urt.math; + alias _d_pow = urt.math.pow; + + auto _d_sqrt(T)(T x) + { + // TODO: should we have a `float` one? + import urt.math : sqrt; + return sqrt(x); + } +} // ────────────────────────────────────────────────────────────────────── // Object — root of the class hierarchy @@ -1350,6 +1366,7 @@ void destroy(bool initialize = true, T)(ref T obj) if (!is(T == struct) && !is(T @property immutable(T)[] idup(T)(T[] a) @trusted { + assert(__ctfe, "idup is only supported at compile time"); // CTFE-compatible: the interpreter handles ~= natively. // At runtime this would assert via _d_arrayappendcTX. immutable(T)[] r; @@ -1360,6 +1377,7 @@ void destroy(bool initialize = true, T)(ref T obj) if (!is(T == struct) && !is(T @property T[] dup(T)(const(T)[] a) @trusted { + assert(__ctfe, "dup is only supported at compile time"); T[] r; foreach (ref e; a) r ~= cast(T) e; @@ -1380,52 +1398,47 @@ bool _xopCmp(in void*, in void*) // hashOf — used by AAs and anywhere .toHash is needed // ────────────────────────────────────────────────────────────────────── -size_t hashOf(T)(auto ref T val, size_t seed = 0) @trusted pure nothrow @nogc +size_t hashOf(T)(auto ref T val, size_t seed = 0) pure nothrow @nogc @trusted { + import urt.hash : fnv1a, fnv1a64, fnv1_initial; + static if (is(T : const(char)[])) { - // FNV-1a for strings - size_t h = seed == 0 ? 2166136261 : seed; - foreach (c; cast(const(ubyte)[]) val) - { - h ^= c; - h *= 16777619; - } - return h; + static if (is(size_t == uint)) + return fnv1a(cast(ubyte[])val, seed ? seed : fnv1_initial!uint); + else + return fnv1a64(cast(ubyte[])val, seed ? seed : fnv1_initial!ulong); } else static if (is(T V : V*)) { // Pointers — CTFE compatible if (__ctfe) { - if (val is null) return seed; + if (val is null) + return seed; assert(0, "Unable to hash non-null pointer at compile time"); } - size_t v = cast(size_t) val; + size_t v = cast(size_t)val; return _fnv(v ^ (v >> 4), seed); } else static if (__traits(isIntegral, T)) { // Integers — CTFE compatible, no reinterpreting cast static if (T.sizeof <= size_t.sizeof) - return _fnv(cast(size_t) val, seed); + return _fnv(cast(size_t)val, seed); else return _fnv(cast(size_t)(val ^ (val >>> (size_t.sizeof * 8))), seed); } else static if (__traits(isFloating, T)) { // At CTFE we cannot reinterpret float bits; use lossy integer cast + // it'd be better if we could work out the magnitude and multipley the significant bits into an integer if (__ctfe) - return _fnv(cast(size_t) cast(long) val, seed); - // Runtime: walk the bytes - auto p = cast(const ubyte*)&val; - size_t h = seed == 0 ? 2166136261 : seed; - foreach (i; 0 .. T.sizeof) - { - h ^= p[i]; - h *= 16777619; - } - return h; + return _fnv(cast(size_t)cast(long)val, seed); + static if (is(size_t == uint)) + return fnv1a((cast(ubyte*)&val)[0..T.sizeof], seed ? seed : fnv1_initial!uint); + else + return fnv1a64((cast(ubyte*)&val)[0..T.sizeof], seed ? seed : fnv1_initial!uint); } else static if (is(T == struct)) { @@ -1437,28 +1450,38 @@ size_t hashOf(T)(auto ref T val, size_t seed = 0) @trusted pure nothrow @nogc } else static if (is(T == enum)) { - import urt.internal.traits : Unconst; static if (is(T EType == enum)) - return hashOf(cast(EType) val, seed); + return hashOf(cast(EType)val, seed); else return _fnv(0, seed); } else - { return seed; - } } -private size_t _fnv(size_t val, size_t seed) nothrow @nogc pure @safe +private size_t _fnv(size_t val, size_t seed) pure nothrow @nogc @trusted { - size_t h = seed == 0 ? 2166136261 : seed; - foreach (i; 0 .. size_t.sizeof) + import urt.hash : fnv1a, fnv1a64, fnv1_initial; + + // maybe it's better to write out the algorithm inline...? + if (__ctfe) { - h ^= val & 0xFF; - h *= 16777619; - val >>= 8; + static if (is(size_t == uint)) + { + ubyte[4] bytes = [ val & 0xFF, (val >> 8) & 0xFF, (val >> 16) & 0xFF, (val >> 24) & 0xFF ]; + return fnv1a(bytes, seed ? seed : fnv1_initial!uint); + } + else + { + ubyte[8] bytes = [ val & 0xFF, (val >> 8) & 0xFF, (val >> 16) & 0xFF, (val >> 24) & 0xFF, + (val >> 32) & 0xFF, (val >> 40) & 0xFF, (val >> 48) & 0xFF, (val >> 56) & 0xFF ]; + return fnv1a64(bytes, seed ? seed : fnv1_initial!ulong); + } } - return h; + static if (is(size_t == uint)) + return fnv1a((cast(ubyte*)&val)[0..4], seed ? seed : fnv1_initial!uint); + else + return fnv1a64((cast(ubyte*)&val)[0..8], seed ? seed : fnv1_initial!ulong); } // ────────────────────────────────────────────────────────────────────── diff --git a/src/urt/conv.d b/src/urt/conv.d index c84c68e..97c994b 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -597,12 +597,11 @@ unittest } -ptrdiff_t format_float(double value, char[] buffer, const(char)[] format = null) // pure +ptrdiff_t format_float(double value, char[] buffer, const(char)[] format = null) pure { // TODO: this function should be oblitereated and implemented natively... // CRT call can't CTFE, which is a shame - import urt.internal.stdc; import urt.string.format : concat; char[16] fmt = void; @@ -687,6 +686,8 @@ template to(T) private: +extern(C) int snprintf(const char*, const size_t, const char*, ...) pure nothrow @nogc; + // valid result is 0 .. 35; result is garbage outside that bound uint get_digit(char c) pure { diff --git a/src/urt/hash.d b/src/urt/hash.d index 4fd7433..2a94fb8 100644 --- a/src/urt/hash.d +++ b/src/urt/hash.d @@ -16,7 +16,7 @@ template fnv1_initial(T) else static if (is(T == uint)) enum T fnv1_initial = 0x811C9DC5; else static if (is(T == ulong)) - enum T fnv1_initial = 0XCBF29CE484222325; + enum T fnv1_initial = 0xCBF29CE484222325; } T fnv1(T, bool alternate)(const ubyte[] s, T hash = fnv1_initial!T) pure nothrow @nogc diff --git a/src/urt/math.d b/src/urt/math.d index d510d7e..15d1930 100644 --- a/src/urt/math.d +++ b/src/urt/math.d @@ -154,31 +154,33 @@ auto pow(B, E)(B base, E exp) @trusted static if (isFloatB) { // Floating-point base - B result = cast(B) 1.0; + B result = B(1); B b = base; static if (isFloatE) { // F ^^ F — handle integer-valued exponents (covers 99% of // real-world `^^` uses: value^^2, 10.0^^e, etc.) - if (exp == 0) return cast(B) 1.0; - long iexp = cast(long) exp; - if (cast(E) iexp == exp) - return _powfi!(B)(b, iexp); + if (exp == 0) + return B(1); + long iexp = cast(long)exp; + if (cast(E)iexp == exp) + return _powfi!B(b, iexp); // True non-integer exponent: not supported without libm. assert(false, "Non-integer float exponent needs libm"); } else { // F ^^ I — binary exponentiation - return _powfi!(B)(b, cast(long) exp); + return _powfi!B(b, long(exp)); } } else { // I ^^ I — integer power - if (exp == 0) return cast(B) 1; - B result = cast(B) 1; + if (exp == 0) + return B(1); + B result = B(1); B b = base; auto e = cast(ulong) exp; while (e > 0) @@ -194,10 +196,11 @@ auto pow(B, E)(B base, E exp) @trusted // binary exponentiation: float base, integer exponent. private F _powfi(F)(F base, long exp) @trusted { - if (exp == 0) return cast(F) 1.0; + if (exp == 0) + return F(1); bool neg = exp < 0; ulong e = neg ? cast(ulong)(-exp) : cast(ulong) exp; - F result = cast(F) 1.0; + F result = F(1); while (e > 0) { if (e & 1) @@ -205,7 +208,7 @@ private F _powfi(F)(F base, long exp) @trusted base *= base; e >>= 1; } - return neg ? cast(F) 1.0 / result : result; + return neg ? F(1) / result : result; } pragma(inline, true) diff --git a/src/urt/mem/alloc.d b/src/urt/mem/alloc.d index d04e09b..4827bb8 100644 --- a/src/urt/mem/alloc.d +++ b/src/urt/mem/alloc.d @@ -1,29 +1,32 @@ module urt.mem.alloc; -import urt.internal.stdc; +import urt.mem; nothrow @nogc: -void[] alloc(size_t size) nothrow @nogc + +void[] alloc(size_t size) pure { + // TODO: pure malloc is meant to copy and restore errno around the call, because that's user-accessible state... + // TODO: we might pin the length to a debug table somewhere... return malloc(size)[0 .. size]; } -void[] realloc(void[] mem, size_t newSize) nothrow @nogc +void[] realloc(void[] mem, size_t newSize) pure { // TODO: we might pin the length to a debug table somewhere... return urt.mem.realloc(mem.ptr, newSize)[0 .. newSize]; } -void free(void[] mem) nothrow @nogc +void free(void[] mem) pure { // maybe check the length passed to free matches the alloc? // ... or you know, just don't do that. urt.mem.free(mem.ptr); } -void[] alloc_aligned(size_t size, size_t alignment) nothrow @nogc +void[] alloc_aligned(size_t size, size_t alignment) pure { import urt.util : align_down, is_power_of_2, max; @@ -60,7 +63,7 @@ void[] alloc_aligned(size_t size, size_t alignment) nothrow @nogc assert(false, "Unsupported platform"); } -void[] realloc_aligned(void[] mem, size_t newSize, size_t alignment) nothrow @nogc +void[] realloc_aligned(void[] mem, size_t newSize, size_t alignment) pure { import urt.util : is_power_of_2, min, max; @@ -77,7 +80,7 @@ void[] realloc_aligned(void[] mem, size_t newSize, size_t alignment) nothrow @no return newAlloc; } -void free_aligned(void[] mem) nothrow @nogc +void free_aligned(void[] mem) pure { if (mem.ptr is null) return; @@ -95,7 +98,7 @@ void free_aligned(void[] mem) nothrow @nogc } // NOTE: This function is only compatible with alloc_aligned! -void[] expand(void[] mem, size_t newSize) nothrow @nogc +void[] expand(void[] mem, size_t newSize) pure { if (mem.ptr is null) return null; @@ -105,7 +108,7 @@ void[] expand(void[] mem, size_t newSize) nothrow @nogc } // NOTE: This function is only compatible with alloc_aligned! -size_t memsize(void* ptr) nothrow @nogc +size_t memsize(void* ptr) pure { if (ptr is null) return 0; @@ -140,16 +143,16 @@ unittest version (Windows) { - extern(C) void* _aligned_malloc(size_t size, size_t alignment) nothrow @nogc; - extern(C) void _aligned_free(void* memblock) nothrow @nogc; - extern(C) size_t _aligned_msize(void* memblock) nothrow @nogc; + extern(C) void* _aligned_malloc(size_t size, size_t alignment) pure; + extern(C) void _aligned_free(void* memblock) pure; + extern(C) size_t _aligned_msize(void* memblock) pure; } version (Posix) { - extern(C) size_t malloc_usable_size(void *__ptr); + extern(C) size_t malloc_usable_size(void *__ptr) pure; } else version (FreeStanding) { - extern(C) size_t malloc_usable_size(void *__ptr); + extern(C) size_t malloc_usable_size(void *__ptr) pure; } diff --git a/src/urt/mem/allocator.d b/src/urt/mem/allocator.d index f6a54ca..7da1c58 100644 --- a/src/urt/mem/allocator.d +++ b/src/urt/mem/allocator.d @@ -2,28 +2,30 @@ module urt.mem.allocator; import urt.lifetime; +nothrow: + // TODO: this should be defined by platform/compiler/etc... enum DefaultAlign = size_t.sizeof; -NoGCAllocator defaultAllocator() nothrow @nogc +NoGCAllocator defaultAllocator() pure @nogc { - return Mallocator.instance; + return Mallocator.instance(); } -NoGCAllocator tempAllocator() nothrow @nogc +NoGCAllocator tempAllocator() pure @nogc { import urt.mem.temp; - - return TempAllocator.instance; + return TempAllocator.instance(); } class Allocator { - abstract void[] alloc(size_t bytes, size_t alignment = DefaultAlign) nothrow; +nothrow: + abstract void[] alloc(size_t bytes, size_t alignment = DefaultAlign) pure; - void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) nothrow + void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) pure { void[] newMem = alloc(newSize, alignment); if (newMem != null) @@ -37,16 +39,16 @@ class Allocator return newMem; } - void[] expand(void[] mem, size_t newSize) nothrow + void[] expand(void[] mem, size_t newSize) pure { return null; } - abstract void free(void[] mem) nothrow; + abstract void free(void[] mem) pure; // abstract size_t getUsableSize(void* p); // get the usable size for an allocation... - final T* allocT(T, Args...)(auto ref Args args) nothrow + final T* allocT(T, Args...)(auto ref Args args) if (!is(T == class)) { T* item = cast(T*)alloc(T.sizeof, T.alignof).ptr; @@ -59,7 +61,7 @@ class Allocator return item; } - final T allocT(T, Args...)(auto ref Args args) nothrow + final T allocT(T, Args...)(auto ref Args args) if (is(T == class)) { T item = cast(T)alloc(__traits(classInstanceSize, T), __traits(classInstanceAlignment, T)).ptr; @@ -72,7 +74,7 @@ class Allocator return item; } - final void freeT(T)(T* item) nothrow + final void freeT(T)(T* item) if (!is(T == class)) { try @@ -84,7 +86,7 @@ class Allocator free((cast(void*)item)[0..T.sizeof]); } - final void freeT(T)(T item) nothrow + final void freeT(T)(T item) if (is(T == class)) { try @@ -96,7 +98,7 @@ class Allocator free((cast(void*)item)[0..__traits(classInstanceSize, T)]); } - final T[] allocArray(T, Args...)(size_t count, auto ref Args args) nothrow + final T[] allocArray(T, Args...)(size_t count, auto ref Args args) if (!is(T == class)) { if (count == 0) @@ -114,7 +116,7 @@ class Allocator return items; } - final T[] reallocArray(T, Args...)(T[] arr, size_t newCount, auto ref Args args) nothrow + final T[] reallocArray(T, Args...)(T[] arr, size_t newCount, auto ref Args args) if (!is(T == class)) { if (newCount < arr.length) @@ -148,7 +150,7 @@ class Allocator return arr; } - final void freeArray(T)(T[] items) nothrow + final void freeArray(T)(T[] items) if (!is(T == class)) { try @@ -166,15 +168,21 @@ class Allocator class GCAllocator : Allocator { - static GCAllocator instance() nothrow @nogc => _instance; +nothrow: + static GCAllocator instance() pure @nogc + { + alias PureHack = GCAllocator function() pure nothrow @nogc; + static GCAllocator hack() nothrow @nogc => _instance; + return (cast(PureHack)&hack)(); + } - override void[] alloc(size_t bytes, size_t alignment = DefaultAlign) nothrow + override void[] alloc(size_t bytes, size_t alignment = DefaultAlign) pure { // TODO: can/should we enforce alignment? return new void[bytes]; } - override void free(void[] mem) nothrow + override void free(void[] mem) pure { // GC will take care of it... } @@ -185,9 +193,10 @@ private: class NoGCAllocator : Allocator { - abstract override void[] alloc(size_t bytes, size_t alignment = DefaultAlign) nothrow @nogc; +nothrow @nogc: + abstract override void[] alloc(size_t bytes, size_t alignment = DefaultAlign) pure; - override void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) nothrow @nogc + override void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) pure { void[] newMem = alloc(newSize, alignment); if (newMem != null) @@ -201,14 +210,14 @@ class NoGCAllocator : Allocator return newMem; } - override void[] expand(void[] mem, size_t newSize) nothrow @nogc + override void[] expand(void[] mem, size_t newSize) pure { return null; } - abstract override void free(void[] mem) nothrow @nogc; + abstract override void free(void[] mem) pure; - final T* allocT(T, Args...)(auto ref Args args) nothrow @nogc + final T* allocT(T, Args...)(auto ref Args args) if (!is(T == class)) { T* item = cast(T*)alloc(T.sizeof, T.alignof).ptr; @@ -221,7 +230,7 @@ class NoGCAllocator : Allocator return item; } - final T allocT(T, Args...)(auto ref Args args) nothrow @nogc + final T allocT(T, Args...)(auto ref Args args) if (is(T == class)) { T item = cast(T)alloc(__traits(classInstanceSize, T), __traits(classInstanceAlignment, T)).ptr; @@ -234,7 +243,7 @@ class NoGCAllocator : Allocator return item; } - final void freeT(T)(T* item) nothrow @nogc + final void freeT(T)(T* item) if (!is(T == class)) { try @@ -246,7 +255,7 @@ class NoGCAllocator : Allocator free((cast(void*)item)[0..T.sizeof]); } - final void freeT(T)(T item) nothrow @nogc + final void freeT(T)(T item) if (is(T == class)) { // HACK: since druntime can't actually destroy a @nogc class! @@ -262,7 +271,7 @@ class NoGCAllocator : Allocator free((cast(void*)item)[0..__traits(classInstanceSize, T)]); } - final T[] allocArray(T, Args...)(size_t count, auto ref Args args) nothrow @nogc + final T[] allocArray(T, Args...)(size_t count, auto ref Args args) if (!is(T == class)) { if (count == 0) @@ -279,7 +288,7 @@ class NoGCAllocator : Allocator return items; } - final T[] allocArray(T)(size_t count) nothrow @nogc + final T[] allocArray(T)(size_t count) if (is(T == class)) { if (count == 0) @@ -291,7 +300,7 @@ class NoGCAllocator : Allocator return items; } - final T[] reallocArray(T, Args...)(T[] arr, size_t newCount, auto ref Args args) nothrow @nogc + final T[] reallocArray(T, Args...)(T[] arr, size_t newCount, auto ref Args args) if (!is(T == class)) { if (newCount < arr.length) @@ -326,7 +335,7 @@ class NoGCAllocator : Allocator return arr; } - final void freeArray(T)(T[] items) nothrow @nogc + final void freeArray(T)(T[] items) if (!is(T == class)) { try @@ -341,7 +350,7 @@ class NoGCAllocator : Allocator free(cast(void[])items[]); } - final void freeArray(T)(T[] items) nothrow @nogc + final void freeArray(T)(T[] items) if (is(T == class)) { free(cast(void[])items[]); @@ -351,25 +360,31 @@ class NoGCAllocator : Allocator class Mallocator : NoGCAllocator { static import urt.mem.alloc; +nothrow @nogc: - static Mallocator instance() nothrow @nogc => _instance; + static Mallocator instance() pure + { + alias PureHack = Mallocator function() pure nothrow @nogc; + static Mallocator hack() nothrow @nogc => _instance; + return (cast(PureHack)&hack)(); + } - override void[] alloc(size_t size, size_t alignment = DefaultAlign) nothrow @nogc + override void[] alloc(size_t size, size_t alignment = DefaultAlign) pure { return urt.mem.alloc.alloc_aligned(size, alignment); } - override void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) nothrow @nogc + override void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) pure { return urt.mem.alloc.realloc_aligned(mem, newSize, alignment); } - override void[] expand(void[] mem, size_t newSize) nothrow + override void[] expand(void[] mem, size_t newSize) pure { return urt.mem.alloc.expand(mem, newSize); } - override void free(void[] mem) nothrow @nogc + override void free(void[] mem) pure { urt.mem.alloc.free_aligned(mem); } @@ -381,25 +396,25 @@ private: class RegionAllocator : NoGCAllocator { import urt.mem.region; +nothrow @nogc: - static RegionAllocator getRegionAllocator(void[] region) pure nothrow @nogc + static RegionAllocator get_region_allocator(void[] region) pure { Region* r = makeRegion(region); return r.alloc!RegionAllocator(r); } - - this(Region* region) pure nothrow @nogc + this(Region* region) pure { this.region = region; } - override void[] alloc(size_t bytes, size_t alignment = DefaultAlign) pure nothrow @nogc + override void[] alloc(size_t bytes, size_t alignment = DefaultAlign) pure { return region.alloc(bytes, alignment); } - override void free(void[] mem) pure nothrow @nogc + override void free(void[] mem) pure { } diff --git a/src/urt/mem/region.d b/src/urt/mem/region.d index a657ace..386e661 100644 --- a/src/urt/mem/region.d +++ b/src/urt/mem/region.d @@ -2,8 +2,10 @@ module urt.mem.region; import urt.util; +nothrow @nogc: -static Region* makeRegion(void[] mem) pure nothrow @nogc + +static Region* makeRegion(void[] mem) pure { assert(mem.length >= Region.sizeof, "Memory block too small"); Region* region = cast(Region*)mem.ptr.align_up(Region.alignof); diff --git a/src/urt/mem/string.d b/src/urt/mem/string.d index a068e51..15ede1d 100644 --- a/src/urt/mem/string.d +++ b/src/urt/mem/string.d @@ -171,15 +171,20 @@ void* allocWithStringCache(size_t bytes, String[] cachedStrings, const(char[])[] class StringAllocator : NoGCAllocator { - override void[] alloc(size_t bytes, size_t alignment = 1) nothrow @nogc + override void[] alloc(size_t bytes, size_t alignment = 1) pure nothrow @nogc { - assert(stringHeapCursor + bytes < stringHeap.length, "String heap overflow!"); + static void[] impl(size_t bytes, size_t alignment) nothrow @nogc + { + assert(stringHeapCursor + bytes < stringHeap.length, "String heap overflow!"); - char[] heap = cast(char[])stringHeap; - return heap[stringHeapCursor .. stringHeapCursor + bytes]; + char[] heap = cast(char[])stringHeap; + return heap[stringHeapCursor .. stringHeapCursor + bytes]; + } + alias PureHack = void[] function(size_t, size_t) pure nothrow @nogc; + return (cast(PureHack)&impl)(bytes, alignment); } - override void free(void[] mem) nothrow @nogc + override void free(void[] mem) pure nothrow @nogc { // you don't free cached strings! } diff --git a/src/urt/mem/temp.d b/src/urt/mem/temp.d index 5f4eb08..ada95d2 100644 --- a/src/urt/mem/temp.d +++ b/src/urt/mem/temp.d @@ -4,11 +4,12 @@ import urt.mem; version = DebugTempAlloc; +nothrow @nogc: enum size_t TempMemSize = 4096; -void[] talloc(size_t size) nothrow @nogc +void[] talloc(size_t size) pure { debug version (DebugTempAlloc) { @@ -18,21 +19,19 @@ void[] talloc(size_t size) nothrow @nogc assert(size <= TempMemSize / 2, "Requested temp memory size is too large"); - if (alloc_offset + size > TempMemSize) - alloc_offset = 0; - - void[] mem = tempMem[alloc_offset .. alloc_offset + size]; - alloc_offset += size; - - return mem; + void[] mem = tmem_tail(); + if (mem.length < size) + mem = tmem_reset(); + tmem_advance(size); + return mem[0 .. size]; } -void[] talloc_aligned(size_t size, size_t alignment) nothrow @nogc +void[] talloc_aligned(size_t size, size_t alignment) pure { assert(false); } -void[] trealloc(void[] mem, size_t newSize) nothrow @nogc +void[] trealloc(void[] mem, size_t newSize) pure { if (newSize <= mem.length) return mem[0 .. newSize]; @@ -46,35 +45,36 @@ void[] trealloc(void[] mem, size_t newSize) nothrow @nogc return r; } -void[] trealloc_aligned(void[] mem, size_t newSize, size_t alignment) nothrow @nogc +void[] trealloc_aligned(void[] mem, size_t newSize, size_t alignment) pure { assert(false); } -void[] texpand(void[] mem, size_t newSize) nothrow @nogc +void[] texpand(void[] mem, size_t newSize) pure { - if (mem.ptr + mem.length != tempMem.ptr + alloc_offset) + void[] tmem = tmem_tail(); + if (mem.ptr + mem.length != tmem.ptr) return null; ptrdiff_t grow = newSize - mem.length; - if (cast(size_t)(alloc_offset + grow) > TempMemSize) + if (grow > tmem.length) return null; - alloc_offset += grow; + tmem_advance(grow); return mem.ptr[0 .. newSize]; } -void tfree(void[] mem) nothrow @nogc +void tfree(void[] mem) pure { // maybe do some debug accounting...? } -char* tstringz(const(char)[] str) nothrow @nogc +char* tstringz(const(char)[] str) pure { char* r = cast(char*)talloc(str.length + 1).ptr; r[0 .. str.length] = str[]; r[str.length] = '\0'; return r; } -char* tstringz(const(wchar)[] str) nothrow @nogc +char* tstringz(const(wchar)[] str) pure { import urt.string.uni : uni_convert; char* r = cast(char*)talloc(str.length*3 + 1).ptr; @@ -83,7 +83,7 @@ char* tstringz(const(wchar)[] str) nothrow @nogc return r; } -wchar* twstringz(const(char)[] str) nothrow @nogc +wchar* twstringz(const(char)[] str) pure { import urt.string.uni : uni_convert; wchar* r = cast(wchar*)talloc(str.length*2 + 2).ptr; @@ -91,7 +91,7 @@ wchar* twstringz(const(char)[] str) nothrow @nogc r[len] = '\0'; return r; } -wchar* twstringz(const(wchar)[] str) nothrow @nogc +wchar* twstringz(const(wchar)[] str) pure { wchar* r = cast(wchar*)talloc(str.length*2 + 2).ptr; r[0 .. str.length] = str[]; @@ -110,24 +110,25 @@ const(char)[] tstring(T)(auto ref T value) else { import urt.string.format : toString; - ptrdiff_t r = toString(value, cast(char[])tempMem[alloc_offset..$]); + char[] tmem = cast(char[])tmem_tail(); + ptrdiff_t r = toString(value, tmem); if (r < 0) { - alloc_offset = 0; - r = toString(value, cast(char[])tempMem[0..TempMemSize / 2]); + tmem = cast(char[])tmem_reset(); + r = toString(value, tmem); if (r < 0) { // assert(false, "Formatted string is too large for the temp buffer!"); return null; } } - const(char)[] result = cast(char[])tempMem[alloc_offset .. alloc_offset + r]; - alloc_offset += r; + const(char)[] result = tmem[0 .. r]; + tmem_advance(r); return result; } } -const(dchar)[] tdstring(T)(auto ref T value) nothrow @nogc +const(dchar)[] tdstring(T)(auto ref T value) { static if (is(T : const(char)[]) || is(T : const(wchar)[]) || is(T : const(dchar)[])) alias s = value; @@ -178,15 +179,21 @@ char[] tformat(Args...)(const(char)[] fmt, ref Args args) class TempAllocator : NoGCAllocator { static import urt.mem.alloc; +nothrow @nogc: - static TempAllocator instance() nothrow @nogc => _instance; + static TempAllocator instance() pure + { + alias PureHack = TempAllocator function() pure nothrow @nogc; + static TempAllocator hack() nothrow @nogc => _instance; + return (cast(PureHack)&hack)(); + } - override void[] alloc(size_t bytes, size_t alignment = DefaultAlign) nothrow @nogc + override void[] alloc(size_t bytes, size_t alignment = DefaultAlign) pure { return talloc(bytes); } - override void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) nothrow @nogc + override void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) pure { return trealloc(mem, newSize); // TODO... @@ -194,12 +201,12 @@ class TempAllocator : NoGCAllocator return null; } - override void[] expand(void[] mem, size_t newSize) nothrow + override void[] expand(void[] mem, size_t newSize) pure { return texpand(mem, newSize); } - override void free(void[] mem) nothrow @nogc + override void free(void[] mem) pure { tfree(mem); } @@ -213,3 +220,32 @@ private: static void[TempMemSize] tempMem; static ushort alloc_offset = 0; + +void[] tmem_tail() pure +{ + static void[] impl() nothrow @nogc + => tempMem[alloc_offset..$]; + alias PureHack = void[] function() pure nothrow @nogc; + return (cast(PureHack)&impl)(); +} + +void[] tmem_reset() pure +{ + static void[] impl() nothrow @nogc + { + alloc_offset = 0; + return tempMem[0..TempMemSize / 2]; + } + alias PureHack = void[] function() pure nothrow @nogc; + return (cast(PureHack)&impl)(); +} + +void tmem_advance(size_t n) pure +{ + static void impl(ushort n) nothrow @nogc + { + alloc_offset += n; + } + alias PureHack = void function(ushort) pure nothrow @nogc; + return (cast(PureHack)&impl)(cast(ushort)n); +} diff --git a/src/urt/string/string.d b/src/urt/string/string.d index 96414a4..ac1d91c 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -28,8 +28,8 @@ enum StringAlloc : ubyte struct StringAllocator { - char* delegate(ushort bytes, void* userData) nothrow @nogc alloc; - void delegate(char* s) nothrow @nogc free; + char* delegate(ushort bytes, void* userData) pure nothrow @nogc alloc; + void delegate(char* s) pure nothrow @nogc free; } struct StringCacheBuilder @@ -194,7 +194,7 @@ char* writeString(char* buffer, const(char)[] str) pure nothrow @nogc return buffer; } -String as_string(const(char)* s) nothrow @nogc +String as_string(const(char)* s) pure nothrow @nogc => String(s, false); inout(char)[] as_dstring(inout(char)* s) pure nothrow @nogc @@ -237,7 +237,7 @@ nothrow @nogc: } } - this(size_t Embed)(MutableString!Embed str) inout //pure TODO: PUT THIS BACK!! + this(size_t Embed)(MutableString!Embed str) inout pure { if (!str.ptr) return; @@ -247,7 +247,7 @@ nothrow @nogc: if (Embed > 0 && str.ptr == str.embed.ptr + 2) { // clone the string - this(writeString(stringAllocators[0].alloc(cast(ushort)str.length, null), str[]), true); + this(writeString(get_string_allocator(0).alloc(cast(ushort)str.length, null), str[]), true); return; } } @@ -268,7 +268,7 @@ nothrow @nogc: ptr = cs.ptr; } - ~this() + ~this() pure { if (ptr) decRef(); @@ -401,12 +401,12 @@ private: } } - void decRef() + void decRef() pure { if (ushort* rc = refCounter()) { if ((*rc & 0x3FFF) == 0) - stringAllocators[*rc >> 14].free(cast(char*)ptr); + get_string_allocator(*rc >> 14).free(cast(char*)ptr); else --*rc; } @@ -604,7 +604,7 @@ nothrow @nogc: this.format(format, forward!args); } - ~this() + ~this() pure { freeStringBuffer(ptr); } @@ -934,7 +934,7 @@ private: return buffer + 4; } - void freeStringBuffer(char* buffer) + void freeStringBuffer(char* buffer) pure { if (!buffer) return; @@ -1111,32 +1111,42 @@ private: __gshared StringAllocator[4] stringAllocators; static assert(stringAllocators.length <= 4, "Only 2 bits reserved to store allocator index"); +ref StringAllocator get_string_allocator(uint i) pure nothrow @nogc +{ + alias PureHack = ref StringAllocator function(uint i) pure nothrow @nogc; + static ref StringAllocator get_allocator(uint i) nothrow @nogc { return stringAllocators[i]; }; + return (cast(PureHack)&get_allocator)(i); +} + package(urt) void initStringAllocators() nothrow @nogc { - stringAllocators[StringAlloc.Default].alloc = (ushort bytes, void* userData) { - char* buffer = cast(char*)defaultAllocator().alloc(bytes + 4, ushort.alignof).ptr; + alias PureAlloc = void[] delegate(size_t, size_t) pure nothrow @nogc; + alias PureFree = void delegate(void[]) pure nothrow @nogc; + + stringAllocators[StringAlloc.Default].alloc = (ushort bytes, void* userData) pure { + char* buffer = cast(char*)(cast(PureAlloc)&defaultAllocator().alloc)(bytes + 4, ushort.alignof).ptr; *cast(ushort*)buffer = StringAlloc.Default << 14; // allocator = default, rc = 0 return buffer + 4; }; - stringAllocators[StringAlloc.Default].free = (char* str) { + stringAllocators[StringAlloc.Default].free = (char* str) pure { ushort len = (cast(ushort*)str)[-1] & 0x7FFF; str -= 4; - defaultAllocator().free(str[0 .. 4 + len]); + (cast(PureFree)&defaultAllocator().free)(str[0 .. 4 + len]); }; - stringAllocators[StringAlloc.Explicit].alloc = (ushort bytes, void* userData) { + stringAllocators[StringAlloc.Explicit].alloc = (ushort bytes, void* userData) pure { NoGCAllocator a = cast(NoGCAllocator)userData; - char* buffer = cast(char*)a.alloc(size_t.sizeof*2 + bytes, size_t.alignof).ptr; + char* buffer = cast(char*)(cast(PureAlloc)&a.alloc)(size_t.sizeof*2 + bytes, size_t.alignof).ptr; *cast(NoGCAllocator*)buffer = a; buffer += size_t.sizeof*2; (cast(ushort*)buffer)[-2] = StringAlloc.Explicit << 14; // allocator = explicit, rc = 0 return buffer; }; - stringAllocators[StringAlloc.Explicit].free = (char* str) { + stringAllocators[StringAlloc.Explicit].free = (char* str) pure { NoGCAllocator a = *cast(NoGCAllocator*)(str - size_t.sizeof*2); ushort len = (cast(ushort*)str)[-1] & 0x7FFF; str -= size_t.sizeof*2; - a.free(str[0 .. size_t.sizeof*2 + len]); + (cast(PureFree)&a.free)(str[0 .. size_t.sizeof*2 + len]); }; } From 88a97572a7d86c2a2d31a80183d7f570594452c7 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 1 Apr 2026 17:16:07 +1000 Subject: [PATCH 112/138] Fix format_float to avoid varargs. --- src/urt/conv.d | 65 +++++++++++++++++++++++--- src/urt/internal/sys/windows/winbase.d | 10 ++-- src/urt/io.d | 21 +++++---- 3 files changed, 74 insertions(+), 22 deletions(-) diff --git a/src/urt/conv.d b/src/urt/conv.d index 97c994b..a12bffa 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -604,14 +604,27 @@ ptrdiff_t format_float(double value, char[] buffer, const(char)[] format = null) import urt.string.format : concat; - char[16] fmt = void; char[64] result = void; - assert(format.length <= fmt.sizeof - 3, "Format string buffer overflow"); - concat(fmt, "%", format, "g\0"); - int len = snprintf(result.ptr, result.length, fmt.ptr, value); - if (len < 0) - return -2; + // parse format; precision is '.10' => 10 digits + int digits = 6; + size_t dot = format.findFirst('.'); + if (dot < format.length) + digits = cast(int)parse_uint(format[dot + 1 .. $]); + version (Windows) + { + int err = _gcvt_s(result.ptr, result.length, value, digits); + if (err != 0) + return -2; + } + else + { + if (gcvt(value, digits, result.ptr) is null) + return -2; + } + size_t len = result.ptr.strlen(); + if (result[len - 1] == '.') + --len; // trim trailing '.' if no digits follow it if (buffer.ptr) { if (len > buffer.length) @@ -621,6 +634,41 @@ ptrdiff_t format_float(double value, char[] buffer, const(char)[] format = null) return len; } +unittest +{ + import urt.io; + char[64] buf; + auto len = format_float(0.0, buf); + writeln(buf[0..len]); + assert(buf[0..len] == "0"); + len = format_float(1.0, buf); + writeln(buf[0..len]); + assert(buf[0..len] == "1"); + len = format_float(-1.0, buf); + writeln(buf[0..len]); + assert(buf[0..len] == "-1"); + len = format_float(3.14159, buf); + writeln(buf[0..len]); + assert(buf[0..len] == "3.14159"); + len = format_float(3.14159, buf, ".3"); + writeln(buf[0..len]); + assert(buf[0..len] == "3.14"); + len = format_float(1.5, buf); + writeln(buf[0..len]); + assert(buf[0..len] == "1.5"); + len = format_float(1e6, buf); + writeln(buf[0..len]); + assert(buf[0..len] == "1.e+006"); + len = format_float(1e6, buf, ".7"); + writeln(buf[0..len]); + assert(buf[0..len] == "1000000"); + len = format_float(0.001, buf); + writeln(buf[0..len]); + assert(buf[0..len] == "0.001" || buf[0..len] == "1.e-003"); // i don't know why it emits e-3 :/ + len = format_float(-0.0, buf); + writeln(buf[0..len]); + assert(buf[0..len] == "-0"); // do we want to print -0? +} template to(T) @@ -686,7 +734,10 @@ template to(T) private: -extern(C) int snprintf(const char*, const size_t, const char*, ...) pure nothrow @nogc; +version (Windows) + extern(C) int _gcvt_s(const char* buffer, size_t size_in_bytes, double value, int digits) pure nothrow @nogc; +else + extern(C) char* gcvt(double value, int ndigit, char* buf) pure nothrow @nogc; // valid result is 0 .. 35; result is garbage outside that bound uint get_digit(char c) pure diff --git a/src/urt/internal/sys/windows/winbase.d b/src/urt/internal/sys/windows/winbase.d index c379452..59d208c 100644 --- a/src/urt/internal/sys/windows/winbase.d +++ b/src/urt/internal/sys/windows/winbase.d @@ -37,7 +37,7 @@ import urt.internal.sys.windows.basetyps, urt.internal.sys.windows.w32api, urt.i // FIXME: //alias void va_list; -import urt.internal.stdc : va_list; +//import urt.internal.stdc : va_list; import urt.mem : memset, memcpy, memmove; @@ -1862,8 +1862,8 @@ extern (Windows) nothrow @nogc { HRSRC FindResourceExW(HINSTANCE, LPCWSTR, LPCWSTR, WORD); BOOL FlushFileBuffers(HANDLE); BOOL FlushInstructionCache(HANDLE, PCVOID, SIZE_T); - DWORD FormatMessageA(DWORD, PCVOID, DWORD, DWORD, LPSTR, DWORD, va_list*); - DWORD FormatMessageW(DWORD, PCVOID, DWORD, DWORD, LPWSTR, DWORD, va_list*); +// DWORD FormatMessageA(DWORD, PCVOID, DWORD, DWORD, LPSTR, DWORD, va_list*); +// DWORD FormatMessageW(DWORD, PCVOID, DWORD, DWORD, LPWSTR, DWORD, va_list*); BOOL FreeEnvironmentStringsA(LPSTR); BOOL FreeEnvironmentStringsW(LPWSTR); BOOL FreeLibrary(HMODULE); @@ -2607,7 +2607,7 @@ version (Unicode) { alias FindNextFileW FindNextFile; alias FindResourceW FindResource; alias FindResourceExW FindResourceEx; - alias FormatMessageW FormatMessage; +// alias FormatMessageW FormatMessage; alias FreeEnvironmentStringsW FreeEnvironmentStrings; alias GetAtomNameW GetAtomName; alias GetCommandLineW GetCommandLine; @@ -2786,7 +2786,7 @@ version (Unicode) { alias FindNextFileA FindNextFile; alias FindResourceA FindResource; alias FindResourceExA FindResourceEx; - alias FormatMessageA FormatMessage; +// alias FormatMessageA FormatMessage; alias FreeEnvironmentStringsA FreeEnvironmentStrings; alias GetAtomNameA GetAtomName; alias GetCommandLineA GetCommandLine; diff --git a/src/urt/io.d b/src/urt/io.d index 9cca1aa..84cc844 100644 --- a/src/urt/io.d +++ b/src/urt/io.d @@ -25,11 +25,10 @@ template write_to(WriteTarget target, bool newline = false) } else { - import urt.internal.stdc; - static if (target == WriteTarget.stderr) - return fprintf(stderr, "%.*s" ~ (newline ? "\n" : ""), cast(int)str.length, str.ptr); - else - return printf("%.*s" ~ (newline ? "\n" : ""), cast(int)str.length, str.ptr); + fwrite(str.ptr, 1, str.length, target == WriteTarget.stdout ? stdout : stderr); + if (newline) + fwrite("\n".ptr, 1, "\n".length, target == WriteTarget.stdout ? stdout : stderr); + return cast(int)(str.length + "\n".length); } } else static if (target == WriteTarget.debugstring) @@ -90,11 +89,8 @@ void flush(WriteTarget target = WriteTarget.stdout)() nothrow @nogc } else { - import urt.internal.stdc : fflush, stdout, stderr; - static if (target == WriteTarget.stdout) - fflush(stdout); - else static if (target == WriteTarget.stderr) - fflush(stderr); + static if (target == WriteTarget.stdout || target == WriteTarget.stderr) + fflush(target == WriteTarget.stdout ? stdout : stderr); } } @@ -116,3 +112,8 @@ unittest write("mister ", "robot "); writef("how do {0} do?\n", "you"); } + + +private: + +import core.stdc.stdio : stdout, stderr, fwrite, fflush; From 00b236775d6462d8871adbb72ff23007cb4340a2 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 1 Apr 2026 22:41:17 +1000 Subject: [PATCH 113/138] Add minimal stdc. --- src/object.d | 16 +- src/urt/conv.d | 19 +- src/urt/exception.d | 2 +- src/urt/file.d | 53 +- src/urt/internal/aa.d | 4 +- src/urt/internal/exception.d | 12 +- src/urt/internal/os.c | 2 + src/urt/internal/stdc.c | 19 - src/urt/internal/stdc/errno.d | 2467 ++++++++++++++++++++++++++ src/urt/internal/stdc/stdio.d | 111 ++ src/urt/internal/stdc/stdlib.d | 12 + src/urt/internal/sys/posix/package.d | 335 ++++ src/urt/internal/sys/posix/termios.d | 107 ++ src/urt/io.d | 4 +- src/urt/mem/alloc.d | 2 +- src/urt/package.d | 2 +- src/urt/result.d | 9 +- src/urt/socket.d | 22 +- src/urt/system.d | 2 +- src/urt/time.d | 2 +- 20 files changed, 3111 insertions(+), 91 deletions(-) delete mode 100644 src/urt/internal/stdc.c create mode 100644 src/urt/internal/stdc/errno.d create mode 100644 src/urt/internal/stdc/stdio.d create mode 100644 src/urt/internal/stdc/stdlib.d create mode 100644 src/urt/internal/sys/posix/package.d create mode 100644 src/urt/internal/sys/posix/termios.d diff --git a/src/object.d b/src/object.d index 70cf9e0..b9e7081 100644 --- a/src/object.d +++ b/src/object.d @@ -27,16 +27,28 @@ else version (AArch64) else version = WithArgTypes; } - // ────────────────────────────────────────────────────────────────────── // Fundamental type aliases (compiler hardcodes references to these) // ────────────────────────────────────────────────────────────────────── alias size_t = typeof(int.sizeof); -alias ptrdiff_t = typeof(cast(void*) 0 - cast(void*) 0); +alias ptrdiff_t = typeof(cast(void*)0 - cast(void*)0); alias nullptr_t = typeof(null); alias noreturn = typeof(*null); +// needed so druntime's core.stdc.stdio compiles on AArch64 +version (AArch64) +{ + extern (C++, std) struct __va_list + { + void* __stack; + void* __gr_top; + void* __vr_top; + int __gr_offs; + int __vr_offs; + } +} + version (Windows) alias wchar wchar_t; else version (Posix) diff --git a/src/urt/conv.d b/src/urt/conv.d index a12bffa..1263bd2 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -613,12 +613,14 @@ ptrdiff_t format_float(double value, char[] buffer, const(char)[] format = null) digits = cast(int)parse_uint(format[dot + 1 .. $]); version (Windows) { + import urt.internal.stdc.stdlib : _gcvt_s; int err = _gcvt_s(result.ptr, result.length, value, digits); if (err != 0) return -2; } else { + import urt.internal.stdc.stdlib : gcvt; if (gcvt(value, digits, result.ptr) is null) return -2; } @@ -639,34 +641,24 @@ unittest import urt.io; char[64] buf; auto len = format_float(0.0, buf); - writeln(buf[0..len]); assert(buf[0..len] == "0"); len = format_float(1.0, buf); - writeln(buf[0..len]); assert(buf[0..len] == "1"); len = format_float(-1.0, buf); - writeln(buf[0..len]); assert(buf[0..len] == "-1"); len = format_float(3.14159, buf); - writeln(buf[0..len]); assert(buf[0..len] == "3.14159"); len = format_float(3.14159, buf, ".3"); - writeln(buf[0..len]); assert(buf[0..len] == "3.14"); len = format_float(1.5, buf); - writeln(buf[0..len]); assert(buf[0..len] == "1.5"); len = format_float(1e6, buf); - writeln(buf[0..len]); - assert(buf[0..len] == "1.e+006"); + assert(buf[0..len] == "1.e+006" || buf[0..len] == "1e+06"); len = format_float(1e6, buf, ".7"); - writeln(buf[0..len]); assert(buf[0..len] == "1000000"); len = format_float(0.001, buf); - writeln(buf[0..len]); assert(buf[0..len] == "0.001" || buf[0..len] == "1.e-003"); // i don't know why it emits e-3 :/ len = format_float(-0.0, buf); - writeln(buf[0..len]); assert(buf[0..len] == "-0"); // do we want to print -0? } @@ -734,11 +726,6 @@ template to(T) private: -version (Windows) - extern(C) int _gcvt_s(const char* buffer, size_t size_in_bytes, double value, int digits) pure nothrow @nogc; -else - extern(C) char* gcvt(double value, int ndigit, char* buf) pure nothrow @nogc; - // valid result is 0 .. 35; result is garbage outside that bound uint get_digit(char c) pure { diff --git a/src/urt/exception.d b/src/urt/exception.d index 21c5d10..1dc3467 100644 --- a/src/urt/exception.d +++ b/src/urt/exception.d @@ -53,7 +53,7 @@ void urt_assert(string file, size_t line, string msg) nothrow @nogc } else { - import urt.internal.stdc : exit; + import urt.internal.stdc.stdlib : exit; exit(-1); } } diff --git a/src/urt/file.d b/src/urt/file.d index c18728b..34199dc 100644 --- a/src/urt/file.d +++ b/src/urt/file.d @@ -26,12 +26,8 @@ version(Windows) } else version (Posix) { - import urt.internal.stdc; - import core.sys.posix.dirent; - import core.sys.posix.fcntl; - import core.sys.posix.stdlib; - import core.sys.posix.sys.stat; - import core.sys.posix.unistd; + import urt.internal.sys.posix; + import urt.internal.stdc.errno; import urt.mem.temp : tconcat; import urt.string : tstringz; @@ -43,6 +39,7 @@ else version (Posix) enum POSIX_FADV_RANDOM = 1; enum POSIX_FADV_SEQUENTIAL = 2; extern(C) int posix_fadvise(int fd, off_t offset, off_t len, int advice) nothrow @nogc; + extern(C) int rename(scope const char*, scope const char*) nothrow @nogc; } else version (FreeStanding) { @@ -126,7 +123,6 @@ bool file_exists(const(char)[] path) } else version (Posix) { - import core.sys.posix.sys.stat; stat_t st; return stat(path.tstringz, &st) == 0 && S_ISREG(st.st_mode); } @@ -161,8 +157,7 @@ Result rename_file(const(char)[] oldPath, const(char)[] newPath) } else version (Posix) { - import core.sys.posix.stdio; - if (int result = rename(oldPath.tstringz, newPath.tstringz)!= 0) + if (int result = rename(oldPath.tstringz, newPath.tstringz) != 0) return posix_result(result); } else @@ -367,7 +362,7 @@ Result create_directory(const(char)[] path) } else version (Posix) { - if (!core.sys.posix.sys.stat.mkdir(tconcat(path, "\0").ptr, 493 /* 0755 */) != 0) + if (!urt.internal.sys.posix.mkdir(tconcat(path, "\0").ptr, 493 /* 0755 */) != 0) return Result.success; r = errno_result(); } @@ -487,7 +482,7 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags flags |= O_DIRECT; } - int fd = core.sys.posix.fcntl.open(path.tstringz, flags, 0b110_110_110); + int fd = urt.internal.sys.posix.open(path.tstringz, flags, 0b110_110_110); if (fd < 0) return errno_result(); file.fd = fd; @@ -541,7 +536,7 @@ void close(ref File file) { if (file.fd == -1) return; - core.sys.posix.unistd.close(file.fd); + urt.internal.sys.posix.close(file.fd); file.fd = -1; } else @@ -670,7 +665,7 @@ Result read(ref File file, void[] buffer, out size_t bytesRead) } else version (Posix) { - ptrdiff_t n = core.sys.posix.unistd.read(file.fd, buffer.ptr, buffer.length); + ptrdiff_t n = urt.internal.sys.posix.read(file.fd, buffer.ptr, buffer.length); if (n < 0) return errno_result(); bytesRead = n; @@ -723,7 +718,7 @@ Result write(ref File file, const(void)[] data, out size_t bytesWritten) } else version (Posix) { - ptrdiff_t n = core.sys.posix.unistd.write(file.fd, data.ptr, data.length); + ptrdiff_t n = urt.internal.sys.posix.write(file.fd, data.ptr, data.length); if (n < 0) return errno_result(); bytesWritten = n; @@ -839,7 +834,7 @@ Result get_temp_filename(ref char[] buffer, const(char)[] dstDir, const(char)[] if (file.fd == -1) return errno_result(); Result r = get_path(file, buffer); - core.sys.posix.unistd.close(file.fd); + urt.internal.sys.posix.close(file.fd); return r; } else @@ -870,4 +865,32 @@ else unittest assert(!file.is_open); assert(filename.delete_file()); + + // create a temp file with known content + filename = buffer[]; + assert(get_temp_filename(filename, "", "stat_test")); + + assert(file.open(filename, FileOpenMode.WriteTruncate)); + + // write exactly 42 bytes + ubyte[42] data; + size_t written; + assert(file.write(data[], written)); + assert(written == 42); + + // get_size exercises fstat + st_size + assert(file.get_size() == 42); + + // set_size exercises ftruncate, then fstat again + assert(file.set_size(100)); + assert(file.get_size() == 100); + + file.close(); + + // file_exists exercises stat + S_ISREG + st_mode + assert(file_exists(filename)); + + // clean up and verify + assert(filename.delete_file()); + assert(!file_exists(filename)); } diff --git a/src/urt/internal/aa.d b/src/urt/internal/aa.d index e7ae5d6..b597a43 100644 --- a/src/urt/internal/aa.d +++ b/src/urt/internal/aa.d @@ -688,7 +688,7 @@ auto _aaValues(K, V)(inout V[K] a) { if (!b.filled) continue; - import core.lifetime; + import urt.lifetime; () @trusted { copyEmplace(b.entry.value, res[i++]); }(); } return res; @@ -716,7 +716,7 @@ auto _aaKeys(K, V)(inout V[K] a) if (!b.filled) continue; // res ~= b.entry.key; - import core.lifetime; + import urt.lifetime; () @trusted { copyEmplace(b.entry.key, res[i++]); }(); } return res; diff --git a/src/urt/internal/exception.d b/src/urt/internal/exception.d index 2fc112d..cf62a6c 100644 --- a/src/urt/internal/exception.d +++ b/src/urt/internal/exception.d @@ -460,11 +460,7 @@ version (Windows) {} else version (FreeStanding) {} else debug import urt.io : write_err, writeln_err, writef_to, WriteTarget; import urt.mem : strlen, memcpy; - import core.sys.posix.dlfcn : dladdr, Dl_info; - import core.sys.posix.fcntl : open, O_RDONLY; - import core.sys.posix.unistd : close, lseek, readlink, sysconf, _SC_PAGE_SIZE; - import core.sys.posix.sys.mman : mmap, munmap, PROT_READ, MAP_PRIVATE, MAP_FAILED; - import core.sys.posix.sys.types : off_t; + import urt.internal.sys.posix; private enum SEEK_END = 2; @@ -1583,7 +1579,7 @@ private void terminate() nothrow @nogc @trusted asm nothrow @nogc { hlt; } else { - import urt.internal.stdc : abort; + import urt.internal.stdc.stdlib : abort; abort(); } } @@ -3172,7 +3168,7 @@ _sleb128_t s_leb128(const(ubyte)** p) nothrow @nogc void dwarf_terminate(uint line) nothrow @nogc { import urt.io : writef_to, WriteTarget; - import urt.internal.stdc : abort; + import urt.internal.stdc.stdlib : abort; writef_to!(WriteTarget.stderr, true)("dwarfeh({0}) fatal error", line); abort(); } @@ -3406,7 +3402,7 @@ pragma(mangle, throw_mangle) extern(C) void dwarfeh_throw(Throwable o) { import urt.io : writeln_err, writef_to, WriteTarget; - import urt.internal.stdc : abort; + import urt.internal.stdc.stdlib : abort; ExceptionHeader* eh = ExceptionHeader.create(o); eh.push(); diff --git a/src/urt/internal/os.c b/src/urt/internal/os.c index 075965f..02bf6cb 100644 --- a/src/urt/internal/os.c +++ b/src/urt/internal/os.c @@ -11,6 +11,8 @@ # include # include # include +# include +# include // EWOULDBLOCK is #define EWOULDBLOCK EAGAIN on Linux — ImportC cannot resolve // chained macros, so re-define as a plain integer. diff --git a/src/urt/internal/stdc.c b/src/urt/internal/stdc.c deleted file mode 100644 index 26fecdb..0000000 --- a/src/urt/internal/stdc.c +++ /dev/null @@ -1,19 +0,0 @@ -#pragma attribute(push, nothrow, nogc) - -#include -#include -#include -#include -#include - -// The errno macro expands to (*__errno_location()) on Linux. ImportC would -// generate a function symbol `errno` that clashes with libc's TLS errno. -#undef errno - -// Some errno values are defined as chained macros (e.g. ENOTSUP → EOPNOTSUPP, -// EWOULDBLOCK → EAGAIN) that ImportC cannot resolve to integers. -// Re-define as plain integer literals so ImportC can export them. -#undef ENOTSUP -#define ENOTSUP 95 /* == EOPNOTSUPP on Linux */ -#undef EWOULDBLOCK -#define EWOULDBLOCK 11 /* == EAGAIN on Linux */ diff --git a/src/urt/internal/stdc/errno.d b/src/urt/internal/stdc/errno.d new file mode 100644 index 0000000..e25d5f2 --- /dev/null +++ b/src/urt/internal/stdc/errno.d @@ -0,0 +1,2467 @@ +/** + * D header file for C99. + * + * $(C_HEADER_DESCRIPTION pubs.opengroup.org/onlinepubs/009695399/basedefs/_errno.h.html, _errno.h) + * + * Copyright: Copyright Sean Kelly 2005 - 2009. + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Sean Kelly, Alex Rønne Petersen + * Source: $(DRUNTIMESRC core/stdc/_errno.d) + * Standards: ISO/IEC 9899:1999 (E) + */ + +module urt.internal.stdc.errno; + +version (OSX) + version = Darwin; +else version (iOS) + version = Darwin; +else version (TVOS) + version = Darwin; +else version (WatchOS) + version = Darwin; + +version (ARM) version = ARM_Any; +version (AArch64) version = ARM_Any; +version (HPPA) version = HPPA_Any; +version (MIPS32) version = MIPS_Any; +version (MIPS64) version = MIPS_Any; +version (PPC) version = PPC_Any; +version (PPC64) version = PPC_Any; +version (RISCV32) version = RISCV_Any; +version (RISCV64) version = RISCV_Any; +version (S390) version = IBMZ_Any; +version (SPARC) version = SPARC_Any; +version (SPARC64) version = SPARC_Any; +version (SystemZ) version = IBMZ_Any; +version (X86) version = X86_Any; +version (X86_64) version = X86_Any; + +@trusted: // Only manipulates errno. +nothrow: +@nogc: + +version (CRuntime_Microsoft) +{ + extern (C) + { + ref int _errno(); + alias errno = _errno; + } +} +else version (CRuntime_Glibc) +{ + extern (C) + { + ref int __errno_location(); + alias errno = __errno_location; + } +} +else version (CRuntime_Musl) +{ + extern (C) + { + ref int __errno_location(); + alias errno = __errno_location; + } +} +else version (CRuntime_Newlib) +{ + extern (C) + { + ref int __errno(); + alias errno = __errno; + } +} +else version (OpenBSD) +{ + // https://github.com/openbsd/src/blob/master/include/errno.h + extern (C) + { + ref int __errno(); + alias errno = __errno; + } +} +else version (NetBSD) +{ + // https://github.com/NetBSD/src/blob/trunk/include/errno.h + extern (C) + { + ref int __errno(); + alias errno = __errno; + } +} +else version (FreeBSD) +{ + extern (C) + { + ref int __error(); + alias errno = __error; + } +} +else version (DragonFlyBSD) +{ + extern (C) + { + pragma(mangle, "errno") int __errno; + ref int __error() { + return __errno; + } + alias errno = __error; + } +} +else version (CRuntime_Bionic) +{ + extern (C) + { + ref int __errno(); + alias errno = __errno; + } +} +else version (CRuntime_UClibc) +{ + extern (C) + { + ref int __errno_location(); + alias errno = __errno_location; + } +} +else version (Darwin) +{ + extern (C) + { + ref int __error(); + alias errno = __error; + } +} +else version (Solaris) +{ + extern (C) + { + ref int ___errno(); + alias errno = ___errno; + } +} +else version (Haiku) +{ + // https://github.com/haiku/haiku/blob/master/headers/posix/errno.h + extern (C) + { + ref int _errnop(); + alias errno = _errnop; + } +} +else +{ + /// + extern(C) pragma(mangle, "getErrno") @property int errno(); + /// + extern(C) pragma(mangle, "setErrno") @property int errno(int n); +} + +extern (C): + + +version (CRuntime_Microsoft) +{ + enum EPERM = 1; /// Operation not permitted + enum ENOENT = 2; /// No such file or directory + enum ESRCH = 3; /// No such process + enum EINTR = 4; /// Interrupted system call + enum EIO = 5; /// I/O error + enum ENXIO = 6; /// No such device or address + enum E2BIG = 7; /// Argument list too long + enum ENOEXEC = 8; /// Exec format error + enum EBADF = 9; /// Bad file number + enum ECHILD = 10; /// No child processes + enum EAGAIN = 11; /// Try again + enum ENOMEM = 12; /// Out of memory + enum EACCES = 13; /// Permission denied + enum EFAULT = 14; /// Bad address + enum EBUSY = 16; /// Device or resource busy + enum EEXIST = 17; /// File exists + enum EXDEV = 18; /// Cross-device link + enum ENODEV = 19; /// No such device + enum ENOTDIR = 20; /// Not a directory + enum EISDIR = 21; /// Is a directory + enum EINVAL = 22; /// Invalid argument + enum ENFILE = 23; /// File table overflow + enum EMFILE = 24; /// Too many open files + enum ENOTTY = 25; /// Not a typewriter + enum EFBIG = 27; /// File too large + enum ENOSPC = 28; /// No space left on device + enum ESPIPE = 29; /// Illegal seek + enum EROFS = 30; /// Read-only file system + enum EMLINK = 31; /// Too many links + enum EPIPE = 32; /// Broken pipe + enum EDOM = 33; /// Math argument out of domain of func + enum ERANGE = 34; /// Math result not representable + enum EDEADLK = 36; /// Resource deadlock would occur + enum ENAMETOOLONG = 38; /// File name too long + enum ENOLCK = 39; /// No record locks available + enum ENOSYS = 40; /// Function not implemented + enum ENOTEMPTY = 41; /// Directory not empty + enum EILSEQ = 42; /// Illegal byte sequence + enum EDEADLOCK = EDEADLK; /// Resource deadlock would occur + + // POSIX compatibility + // See_Also: https://docs.microsoft.com/en-us/cpp/c-runtime-library/errno-constants + enum EADDRINUSE = 100; + enum EADDRNOTAVAIL = 101; + enum EAFNOSUPPORT = 102; + enum EALREADY = 103; + enum EBADMSG = 104; + enum ECANCELED = 105; + enum ECONNABORTED = 106; + enum ECONNREFUSED = 107; + enum ECONNRESET = 108; + enum EDESTADDRREQ = 109; + enum EHOSTUNREACH = 110; + enum EIDRM = 111; + enum EINPROGRESS = 112; + enum EISCONN = 113; + enum ELOOP = 114; + enum EMSGSIZE = 115; + enum ENETDOWN = 116; + enum ENETRESET = 117; + enum ENETUNREACH = 118; + enum ENOBUFS = 119; + enum ENODATA = 120; + enum ENOLINK = 121; + enum ENOMSG = 122; + enum ENOPROTOOPT = 123; + enum ENOSR = 124; + enum ENOSTR = 125; + enum ENOTCONN = 126; + enum ENOTRECOVERABLE = 127; + enum ENOTSOCK = 128; + enum ENOTSUP = 129; + enum EOPNOTSUPP = 130; + enum EOTHER = 131; + enum EOVERFLOW = 132; + enum EOWNERDEAD = 133; + enum EPROTO = 134; + enum EPROTONOSUPPORT = 135; + enum EPROTOTYPE = 136; + enum ETIME = 137; + enum ETIMEDOUT = 138; + enum ETXTBSY = 139; + enum EWOULDBLOCK = 140; +} +else version (CRuntime_Newlib) +{ + enum EPERM = 1; + enum ENOENT = 2; + enum ESRCH = 3; + enum EINTR = 4; + enum EIO = 5; + enum ENXIO = 6; + enum E2BIG = 7; + enum ENOEXEC = 8; + enum EBADF = 9; + enum ECHILD = 10; + enum EAGAIN = 11; + enum ENOMEM = 12; + enum EACCES = 13; + enum EFAULT = 14; + enum EBUSY = 16; + enum EEXIST = 17; + enum EXDEV = 18; + enum ENODEV = 19; + enum ENOTDIR = 20; + enum EISDIR = 21; + enum EINVAL = 22; + enum ENFILE = 23; + enum EMFILE = 24; + enum ENOTTY = 25; + enum ETXTBSY = 26; + enum EFBIG = 27; + enum ENOSPC = 28; + enum ESPIPE = 29; + enum EROFS = 30; + enum EMLINK = 31; + enum EPIPE = 32; + enum EDOM = 33; + enum ERANGE = 34; + enum ENOMSG = 35; + enum EIDRM = 36; + enum EDEADLK = 45; + enum ENOLCK = 46; + enum ENOSTR = 60; + enum ENODATA = 61; + enum ETIME = 62; + enum ENOSR = 63; + enum ENOLINK = 67; + enum EPROTO = 71; + enum EMULTIHOP = 74; + enum EBADMSG = 77; + enum EFTYPE = 79; + enum ENOSYS = 88; + enum ENOTEMPTY = 90; + enum ENAMETOOLONG = 91; + enum ELOOP = 92; + enum EOPNOTSUPP = 95; + enum EPFNOSUPPORT = 96; + enum ECONNRESET = 104; + enum ENOBUFS = 105; + enum EAFNOSUPPORT = 106; + enum EPROTOTYPE = 107; + enum ENOTSOCK = 108; + enum ENOPROTOOPT = 109; + enum ECONNREFUSED = 111; + enum EADDRINUSE = 112; + enum ECONNABORTED = 113; + enum ENETUNREACH = 114; + enum ENETDOWN = 115; + enum ETIMEDOUT = 116; + enum EHOSTDOWN = 117; + enum EHOSTUNREACH = 118; + enum EINPROGRESS = 119; + enum EALREADY = 120; + enum EDESTADDRREQ = 121; + enum EMSGSIZE = 122; + enum EPROTONOSUPPORT = 123; + enum EADDRNOTAVAIL = 125; + enum ENETRESET = 126; + enum EISCONN = 127; + enum ENOTCONN = 128; + enum ETOOMANYREFS = 129; + enum EDQUOT = 132; + enum ESTALE = 133; + enum ENOTSUP = 134; + enum EILSEQ = 138; + enum EOVERFLOW = 139; + enum ECANCELED = 140; + enum ENOTRECOVERABLE = 141; + enum EOWNERDEAD = 142; + + enum EWOULDBLOCK = EAGAIN; + + enum __ELASTERROR = 2000; + + // Linux errno extensions + version (Cygwin) + { + enum ENOTBLK = 15; + enum ECHRNG = 37; + enum EL2NSYNC = 38; + enum EL3HLT = 39; + enum EL3RST = 40; + enum ELNRNG = 41; + enum EUNATCH = 42; + enum ENOCSI = 43; + enum EL2HLT = 44; + enum EBADE = 50; + enum EBADR = 51; + enum EXFULL = 52; + enum ENOANO = 53; + enum EBADRQC = 54; + enum EBADSLT = 55; + enum EDEADLOCK = 56; + enum EBFONT = 57; + enum ENONET = 64; + enum ENOPKG = 65; + enum EREMOTE = 66; + enum EADV = 68; + enum ESRMNT = 69; + enum ECOMM = 70; + enum ELBIN = 75; + enum EDOTDOT = 76; + enum ENOTUNIQ = 80; + enum EBADFD = 81; + enum EREMCHG = 82; + enum ELIBACC = 83; + enum ELIBBAD = 84; + enum ELIBSCN = 85; + enum ELIBMAX = 86; + enum ELIBEXEC = 87; + enum ENMFILE = 89; + enum ESHUTDOWN = 110; + enum ESOCKTNOSUPPORT = 124; + enum EPROCLIM = 130; + enum EUSERS = 131; + enum ENOMEDIUM = 135; + enum ENOSHARE = 136; + enum ECASECLASH = 137; + enum ESTRPIPE = 143; + } +} +else version (linux) +{ + enum EPERM = 1; /// + enum ENOENT = 2; /// + enum ESRCH = 3; /// + enum EINTR = 4; /// + enum EIO = 5; /// + enum ENXIO = 6; /// + enum E2BIG = 7; /// + enum ENOEXEC = 8; /// + enum EBADF = 9; /// + enum ECHILD = 10; /// + enum EAGAIN = 11; /// + enum ENOMEM = 12; /// + enum EACCES = 13; /// + enum EFAULT = 14; /// + enum ENOTBLK = 15; /// + enum EBUSY = 16; /// + enum EEXIST = 17; /// + enum EXDEV = 18; /// + enum ENODEV = 19; /// + enum ENOTDIR = 20; /// + enum EISDIR = 21; /// + enum EINVAL = 22; /// + enum ENFILE = 23; /// + enum EMFILE = 24; /// + enum ENOTTY = 25; /// + enum ETXTBSY = 26; /// + enum EFBIG = 27; /// + enum ENOSPC = 28; /// + enum ESPIPE = 29; /// + enum EROFS = 30; /// + enum EMLINK = 31; /// + enum EPIPE = 32; /// + enum EDOM = 33; /// + enum ERANGE = 34; /// + + version (X86_Any) + { + enum EDEADLK = 35; /// + enum ENAMETOOLONG = 36; /// + enum ENOLCK = 37; /// + enum ENOSYS = 38; /// + enum ENOTEMPTY = 39; /// + enum ELOOP = 40; /// + enum EWOULDBLOCK = EAGAIN; /// + enum ENOMSG = 42; /// + enum EIDRM = 43; /// + enum ECHRNG = 44; /// + enum EL2NSYNC = 45; /// + enum EL3HLT = 46; /// + enum EL3RST = 47; /// + enum ELNRNG = 48; /// + enum EUNATCH = 49; /// + enum ENOCSI = 50; /// + enum EL2HLT = 51; /// + enum EBADE = 52; /// + enum EBADR = 53; /// + enum EXFULL = 54; /// + enum ENOANO = 55; /// + enum EBADRQC = 56; /// + enum EBADSLT = 57; /// + enum EDEADLOCK = EDEADLK; /// + enum EBFONT = 59; /// + enum ENOSTR = 60; /// + enum ENODATA = 61; /// + enum ETIME = 62; /// + enum ENOSR = 63; /// + enum ENONET = 64; /// + enum ENOPKG = 65; /// + enum EREMOTE = 66; /// + enum ENOLINK = 67; /// + enum EADV = 68; /// + enum ESRMNT = 69; /// + enum ECOMM = 70; /// + enum EPROTO = 71; /// + enum EMULTIHOP = 72; /// + enum EDOTDOT = 73; /// + enum EBADMSG = 74; /// + enum EOVERFLOW = 75; /// + enum ENOTUNIQ = 76; /// + enum EBADFD = 77; /// + enum EREMCHG = 78; /// + enum ELIBACC = 79; /// + enum ELIBBAD = 80; /// + enum ELIBSCN = 81; /// + enum ELIBMAX = 82; /// + enum ELIBEXEC = 83; /// + enum EILSEQ = 84; /// + enum ERESTART = 85; /// + enum ESTRPIPE = 86; /// + enum EUSERS = 87; /// + enum ENOTSOCK = 88; /// + enum EDESTADDRREQ = 89; /// + enum EMSGSIZE = 90; /// + enum EPROTOTYPE = 91; /// + enum ENOPROTOOPT = 92; /// + enum EPROTONOSUPPORT = 93; /// + enum ESOCKTNOSUPPORT = 94; /// + enum EOPNOTSUPP = 95; /// + enum ENOTSUP = EOPNOTSUPP; /// + enum EPFNOSUPPORT = 96; /// + enum EAFNOSUPPORT = 97; /// + enum EADDRINUSE = 98; /// + enum EADDRNOTAVAIL = 99; /// + enum ENETDOWN = 100; /// + enum ENETUNREACH = 101; /// + enum ENETRESET = 102; /// + enum ECONNABORTED = 103; /// + enum ECONNRESET = 104; /// + enum ENOBUFS = 105; /// + enum EISCONN = 106; /// + enum ENOTCONN = 107; /// + enum ESHUTDOWN = 108; /// + enum ETOOMANYREFS = 109; /// + enum ETIMEDOUT = 110; /// + enum ECONNREFUSED = 111; /// + enum EHOSTDOWN = 112; /// + enum EHOSTUNREACH = 113; /// + enum EALREADY = 114; /// + enum EINPROGRESS = 115; /// + enum ESTALE = 116; /// + enum EUCLEAN = 117; /// + enum ENOTNAM = 118; /// + enum ENAVAIL = 119; /// + enum EISNAM = 120; /// + enum EREMOTEIO = 121; /// + enum EDQUOT = 122; /// + enum ENOMEDIUM = 123; /// + enum EMEDIUMTYPE = 124; /// + enum ECANCELED = 125; /// + enum ENOKEY = 126; /// + enum EKEYEXPIRED = 127; /// + enum EKEYREVOKED = 128; /// + enum EKEYREJECTED = 129; /// + enum EOWNERDEAD = 130; /// + enum ENOTRECOVERABLE = 131; /// + enum ERFKILL = 132; /// + enum EHWPOISON = 133; /// + } + else version (ARM_Any) + { + enum EDEADLK = 35; /// + enum ENAMETOOLONG = 36; /// + enum ENOLCK = 37; /// + enum ENOSYS = 38; /// + enum ENOTEMPTY = 39; /// + enum ELOOP = 40; /// + enum EWOULDBLOCK = EAGAIN; /// + enum ENOMSG = 42; /// + enum EIDRM = 43; /// + enum ECHRNG = 44; /// + enum EL2NSYNC = 45; /// + enum EL3HLT = 46; /// + enum EL3RST = 47; /// + enum ELNRNG = 48; /// + enum EUNATCH = 49; /// + enum ENOCSI = 50; /// + enum EL2HLT = 51; /// + enum EBADE = 52; /// + enum EBADR = 53; /// + enum EXFULL = 54; /// + enum ENOANO = 55; /// + enum EBADRQC = 56; /// + enum EBADSLT = 57; /// + enum EDEADLOCK = EDEADLK; /// + enum EBFONT = 59; /// + enum ENOSTR = 60; /// + enum ENODATA = 61; /// + enum ETIME = 62; /// + enum ENOSR = 63; /// + enum ENONET = 64; /// + enum ENOPKG = 65; /// + enum EREMOTE = 66; /// + enum ENOLINK = 67; /// + enum EADV = 68; /// + enum ESRMNT = 69; /// + enum ECOMM = 70; /// + enum EPROTO = 71; /// + enum EMULTIHOP = 72; /// + enum EDOTDOT = 73; /// + enum EBADMSG = 74; /// + enum EOVERFLOW = 75; /// + enum ENOTUNIQ = 76; /// + enum EBADFD = 77; /// + enum EREMCHG = 78; /// + enum ELIBACC = 79; /// + enum ELIBBAD = 80; /// + enum ELIBSCN = 81; /// + enum ELIBMAX = 82; /// + enum ELIBEXEC = 83; /// + enum EILSEQ = 84; /// + enum ERESTART = 85; /// + enum ESTRPIPE = 86; /// + enum EUSERS = 87; /// + enum ENOTSOCK = 88; /// + enum EDESTADDRREQ = 89; /// + enum EMSGSIZE = 90; /// + enum EPROTOTYPE = 91; /// + enum ENOPROTOOPT = 92; /// + enum EPROTONOSUPPORT = 93; /// + enum ESOCKTNOSUPPORT = 94; /// + enum EOPNOTSUPP = 95; /// + enum ENOTSUP = EOPNOTSUPP; /// + enum EPFNOSUPPORT = 96; /// + enum EAFNOSUPPORT = 97; /// + enum EADDRINUSE = 98; /// + enum EADDRNOTAVAIL = 99; /// + enum ENETDOWN = 100; /// + enum ENETUNREACH = 101; /// + enum ENETRESET = 102; /// + enum ECONNABORTED = 103; /// + enum ECONNRESET = 104; /// + enum ENOBUFS = 105; /// + enum EISCONN = 106; /// + enum ENOTCONN = 107; /// + enum ESHUTDOWN = 108; /// + enum ETOOMANYREFS = 109; /// + enum ETIMEDOUT = 110; /// + enum ECONNREFUSED = 111; /// + enum EHOSTDOWN = 112; /// + enum EHOSTUNREACH = 113; /// + enum EALREADY = 114; /// + enum EINPROGRESS = 115; /// + enum ESTALE = 116; /// + enum EUCLEAN = 117; /// + enum ENOTNAM = 118; /// + enum ENAVAIL = 119; /// + enum EISNAM = 120; /// + enum EREMOTEIO = 121; /// + enum EDQUOT = 122; /// + enum ENOMEDIUM = 123; /// + enum EMEDIUMTYPE = 124; /// + enum ECANCELED = 125; /// + enum ENOKEY = 126; /// + enum EKEYEXPIRED = 127; /// + enum EKEYREVOKED = 128; /// + enum EKEYREJECTED = 129; /// + enum EOWNERDEAD = 130; /// + enum ENOTRECOVERABLE = 131; /// + enum ERFKILL = 132; /// + enum EHWPOISON = 133; /// + } + else version (HPPA_Any) + { + enum ENOMSG = 35; /// + enum EIDRM = 36; /// + enum ECHRNG = 37; /// + enum EL2NSYNC = 38; /// + enum EL3HLT = 39; /// + enum EL3RST = 40; /// + enum ELNRNG = 41; /// + enum EUNATCH = 42; /// + enum ENOCSI = 43; /// + enum EL2HLT = 44; /// + enum EDEADLK = 45; /// + enum EDEADLOCK = EDEADLK; /// + enum ENOLCK = 46; /// + enum EILSEQ = 47; /// + enum ENONET = 50; /// + enum ENODATA = 51; /// + enum ETIME = 52; /// + enum ENOSR = 53; /// + enum ENOSTR = 54; /// + enum ENOPKG = 55; /// + enum ENOLINK = 57; /// + enum EADV = 58; /// + enum ESRMNT = 59; /// + enum ECOMM = 60; /// + enum EPROTO = 61; /// + enum EMULTIHOP = 64; /// + enum EDOTDOT = 66; /// + enum EBADMSG = 67; /// + enum EUSERS = 68; /// + enum EDQUOT = 69; /// + enum ESTALE = 70; /// + enum EREMOTE = 71; /// + enum EOVERFLOW = 72; /// + enum EBADE = 160; /// + enum EBADR = 161; /// + enum EXFULL = 162; /// + enum ENOANO = 163; /// + enum EBADRQC = 164; /// + enum EBADSLT = 165; /// + enum EBFONT = 166; /// + enum ENOTUNIQ = 167; /// + enum EBADFD = 168; /// + enum EREMCHG = 169; /// + enum ELIBACC = 170; /// + enum ELIBBAD = 171; /// + enum ELIBSCN = 172; /// + enum ELIBMAX = 173; /// + enum ELIBEXEC = 174; /// + enum ERESTART = 175; /// + enum ESTRPIPE = 176; /// + enum EUCLEAN = 177; /// + enum ENOTNAM = 178; /// + enum ENAVAIL = 179; /// + enum EISNAM = 180; /// + enum EREMOTEIO = 181; /// + enum ENOMEDIUM = 182; /// + enum EMEDIUMTYPE = 183; /// + enum ENOKEY = 184; /// + enum EKEYEXPIRED = 185; /// + enum EKEYREVOKED = 186; /// + enum EKEYREJECTED = 187; /// + enum ENOSYM = 215; /// + enum ENOTSOCK = 216; /// + enum EDESTADDRREQ = 217; /// + enum EMSGSIZE = 218; /// + enum EPROTOTYPE = 219; /// + enum ENOPROTOOPT = 220; /// + enum EPROTONOSUPPORT = 221; /// + enum ESOCKTNOSUPPORT = 221; /// + enum EOPNOTSUPP = 223; /// + enum EPFNOSUPPORT = 224; /// + enum EAFNOSUPPORT = 225; /// + enum EADDRINUSE = 226; /// + enum EADDRNOTAVAIL = 227; /// + enum ENETDOWN = 228; /// + enum ENETUNREACH = 229; /// + enum ENETRESET = 230; /// + enum ECONNABORTED = 231; /// + enum ECONNRESET = 232; /// + enum ENOBUFS = 233; /// + enum EISCONN = 234; /// + enum ENOTCONN = 235; /// + enum ESHUTDOWN = 236; /// + enum ETOOMANYREFS = 237; /// + enum ETIMEDOUT = 238; /// + enum ECONNREFUSED = 239; /// + enum EREFUSED = ECONNREFUSED; /// + enum EREMOTERELEASE = 240; /// + enum EHOSTDOWN = 241; /// + enum EHOSTUNREACH = 242; /// + enum EALREADY = 244; /// + enum EINPROGRESS = 245; /// + enum EWOULDBLOCK = EAGAIN; /// + enum ENOTEMPTY = 247; /// + enum ENAMETOOLONG = 248; /// + enum ELOOP = 249; /// + enum ENOSYS = 251; /// + enum ECANCELLED = 253; /// + enum ECANCELED = ECANCELLED; /// + enum EOWNERDEAD = 254; /// + enum ENOTRECOVERABLE = 255; /// + enum ERFKILL = 256; /// + enum EHWPOISON = 257; /// + } + else version (MIPS_Any) + { + enum ENOMSG = 35; /// + enum EIDRM = 36; /// + enum ECHRNG = 37; /// + enum EL2NSYNC = 38; /// + enum EL3HLT = 39; /// + enum EL3RST = 40; /// + enum ELNRNG = 41; /// + enum EUNATCH = 42; /// + enum ENOCSI = 43; /// + enum EL2HLT = 44; /// + enum EDEADLK = 45; /// + enum ENOLCK = 46; /// + enum EBADE = 50; /// + enum EBADR = 51; /// + enum EXFULL = 52; /// + enum ENOANO = 53; /// + enum EBADRQC = 54; /// + enum EBADSLT = 55; /// + enum EDEADLOCK = 56; /// + enum EBFONT = 59; /// + enum ENOSTR = 60; /// + enum ENODATA = 61; /// + enum ETIME = 62; /// + enum ENOSR = 63; /// + enum ENONET = 64; /// + enum ENOPKG = 65; /// + enum EREMOTE = 66; /// + enum ENOLINK = 67; /// + enum EADV = 68; /// + enum ESRMNT = 69; /// + enum ECOMM = 70; /// + enum EPROTO = 71; /// + enum EDOTDOT = 73; /// + enum EMULTIHOP = 74; /// + enum EBADMSG = 77; /// + enum ENAMETOOLONG = 78; /// + enum EOVERFLOW = 79; /// + enum ENOTUNIQ = 80; /// + enum EBADFD = 81; /// + enum EREMCHG = 82; /// + enum ELIBACC = 83; /// + enum ELIBBAD = 84; /// + enum ELIBSCN = 85; /// + enum ELIBMAX = 86; /// + enum ELIBEXEC = 87; /// + enum EILSEQ = 88; /// + enum ENOSYS = 89; /// + enum ELOOP = 90; /// + enum ERESTART = 91; /// + enum ESTRPIPE = 92; /// + enum ENOTEMPTY = 93; /// + enum EUSERS = 94; /// + enum ENOTSOCK = 95; /// + enum EDESTADDRREQ = 96; /// + enum EMSGSIZE = 97; /// + enum EPROTOTYPE = 98; /// + enum ENOPROTOOPT = 99; /// + enum EPROTONOSUPPORT = 120; /// + enum ESOCKTNOSUPPORT = 121; /// + enum EOPNOTSUPP = 122; /// + enum ENOTSUP = EOPNOTSUPP; /// + enum EPFNOSUPPORT = 123; /// + enum EAFNOSUPPORT = 124; /// + enum EADDRINUSE = 125; /// + enum EADDRNOTAVAIL = 126; /// + enum ENETDOWN = 127; /// + enum ENETUNREACH = 128; /// + enum ENETRESET = 129; /// + enum ECONNABORTED = 130; /// + enum ECONNRESET = 131; /// + enum ENOBUFS = 132; /// + enum EISCONN = 133; /// + enum ENOTCONN = 134; /// + enum EUCLEAN = 135; /// + enum ENOTNAM = 137; /// + enum ENAVAIL = 138; /// + enum EISNAM = 139; /// + enum EREMOTEIO = 140; /// + enum EINIT = 141; /// + enum EREMDEV = 142; /// + enum ESHUTDOWN = 143; /// + enum ETOOMANYREFS = 144; /// + enum ETIMEDOUT = 145; /// + enum ECONNREFUSED = 146; /// + enum EHOSTDOWN = 147; /// + enum EHOSTUNREACH = 148; /// + enum EWOULDBLOCK = EAGAIN; /// + enum EALREADY = 149; /// + enum EINPROGRESS = 150; /// + enum ESTALE = 151; /// + enum ECANCELED = 158; /// + enum ENOMEDIUM = 159; /// + enum EMEDIUMTYPE = 160; /// + enum ENOKEY = 161; /// + enum EKEYEXPIRED = 162; /// + enum EKEYREVOKED = 163; /// + enum EKEYREJECTED = 164; /// + enum EOWNERDEAD = 165; /// + enum ENOTRECOVERABLE = 166; /// + enum ERFKILL = 167; /// + enum EHWPOISON = 168; /// + enum EDQUOT = 1133; /// + } + else version (PPC_Any) + { + enum EDEADLK = 35; /// + enum ENAMETOOLONG = 36; /// + enum ENOLCK = 37; /// + enum ENOSYS = 38; /// + enum ENOTEMPTY = 39; /// + enum ELOOP = 40; /// + enum EWOULDBLOCK = EAGAIN; /// + enum ENOMSG = 42; /// + enum EIDRM = 43; /// + enum ECHRNG = 44; /// + enum EL2NSYNC = 45; /// + enum EL3HLT = 46; /// + enum EL3RST = 47; /// + enum ELNRNG = 48; /// + enum EUNATCH = 49; /// + enum ENOCSI = 50; /// + enum EL2HLT = 51; /// + enum EBADE = 52; /// + enum EBADR = 53; /// + enum EXFULL = 54; /// + enum ENOANO = 55; /// + enum EBADRQC = 56; /// + enum EBADSLT = 57; /// + enum EDEADLOCK = 58; /// + enum EBFONT = 59; /// + enum ENOSTR = 60; /// + enum ENODATA = 61; /// + enum ETIME = 62; /// + enum ENOSR = 63; /// + enum ENONET = 64; /// + enum ENOPKG = 65; /// + enum EREMOTE = 66; /// + enum ENOLINK = 67; /// + enum EADV = 68; /// + enum ESRMNT = 69; /// + enum ECOMM = 70; /// + enum EPROTO = 71; /// + enum EMULTIHOP = 72; /// + enum EDOTDOT = 73; /// + enum EBADMSG = 74; /// + enum EOVERFLOW = 75; /// + enum ENOTUNIQ = 76; /// + enum EBADFD = 77; /// + enum EREMCHG = 78; /// + enum ELIBACC = 79; /// + enum ELIBBAD = 80; /// + enum ELIBSCN = 81; /// + enum ELIBMAX = 82; /// + enum ELIBEXEC = 83; /// + enum EILSEQ = 84; /// + enum ERESTART = 85; /// + enum ESTRPIPE = 86; /// + enum EUSERS = 87; /// + enum ENOTSOCK = 88; /// + enum EDESTADDRREQ = 89; /// + enum EMSGSIZE = 90; /// + enum EPROTOTYPE = 91; /// + enum ENOPROTOOPT = 92; /// + enum EPROTONOSUPPORT = 93; /// + enum ESOCKTNOSUPPORT = 94; /// + enum EOPNOTSUPP = 95; /// + enum ENOTSUP = EOPNOTSUPP; /// + enum EPFNOSUPPORT = 96; /// + enum EAFNOSUPPORT = 97; /// + enum EADDRINUSE = 98; /// + enum EADDRNOTAVAIL = 99; /// + enum ENETDOWN = 100; /// + enum ENETUNREACH = 101; /// + enum ENETRESET = 102; /// + enum ECONNABORTED = 103; /// + enum ECONNRESET = 104; /// + enum ENOBUFS = 105; /// + enum EISCONN = 106; /// + enum ENOTCONN = 107; /// + enum ESHUTDOWN = 108; /// + enum ETOOMANYREFS = 109; /// + enum ETIMEDOUT = 110; /// + enum ECONNREFUSED = 111; /// + enum EHOSTDOWN = 112; /// + enum EHOSTUNREACH = 113; /// + enum EALREADY = 114; /// + enum EINPROGRESS = 115; /// + enum ESTALE = 116; /// + enum EUCLEAN = 117; /// + enum ENOTNAM = 118; /// + enum ENAVAIL = 119; /// + enum EISNAM = 120; /// + enum EREMOTEIO = 121; /// + enum EDQUOT = 122; /// + enum ENOMEDIUM = 123; /// + enum EMEDIUMTYPE = 124; /// + enum ECANCELED = 125; /// + enum ENOKEY = 126; /// + enum EKEYEXPIRED = 127; /// + enum EKEYREVOKED = 128; /// + enum EKEYREJECTED = 129; /// + enum EOWNERDEAD = 130; /// + enum ENOTRECOVERABLE = 131; /// + enum ERFKILL = 132; /// + enum EHWPOISON = 133; /// + } + else version (RISCV_Any) + { + enum EDEADLK = 35; /// + enum ENAMETOOLONG = 36; /// + enum ENOLCK = 37; /// + enum ENOSYS = 38; /// + enum ENOTEMPTY = 39; /// + enum ELOOP = 40; /// + enum EWOULDBLOCK = EAGAIN; /// + enum ENOMSG = 42; /// + enum EIDRM = 43; /// + enum ECHRNG = 44; /// + enum EL2NSYNC = 45; /// + enum EL3HLT = 46; /// + enum EL3RST = 47; /// + enum ELNRNG = 48; /// + enum EUNATCH = 49; /// + enum ENOCSI = 50; /// + enum EL2HLT = 51; /// + enum EBADE = 52; /// + enum EBADR = 53; /// + enum EXFULL = 54; /// + enum ENOANO = 55; /// + enum EBADRQC = 56; /// + enum EBADSLT = 57; /// + enum EDEADLOCK = EDEADLK; /// + enum EBFONT = 59; /// + enum ENOSTR = 60; /// + enum ENODATA = 61; /// + enum ETIME = 62; /// + enum ENOSR = 63; /// + enum ENONET = 64; /// + enum ENOPKG = 65; /// + enum EREMOTE = 66; /// + enum ENOLINK = 67; /// + enum EADV = 68; /// + enum ESRMNT = 69; /// + enum ECOMM = 70; /// + enum EPROTO = 71; /// + enum EMULTIHOP = 72; /// + enum EDOTDOT = 73; /// + enum EBADMSG = 74; /// + enum EOVERFLOW = 75; /// + enum ENOTUNIQ = 76; /// + enum EBADFD = 77; /// + enum EREMCHG = 78; /// + enum ELIBACC = 79; /// + enum ELIBBAD = 80; /// + enum ELIBSCN = 81; /// + enum ELIBMAX = 82; /// + enum ELIBEXEC = 83; /// + enum EILSEQ = 84; /// + enum ERESTART = 85; /// + enum ESTRPIPE = 86; /// + enum EUSERS = 87; /// + enum ENOTSOCK = 88; /// + enum EDESTADDRREQ = 89; /// + enum EMSGSIZE = 90; /// + enum EPROTOTYPE = 91; /// + enum ENOPROTOOPT = 92; /// + enum EPROTONOSUPPORT = 93; /// + enum ESOCKTNOSUPPORT = 94; /// + enum EOPNOTSUPP = 95; /// + enum EPFNOSUPPORT = 96; /// + enum EAFNOSUPPORT = 97; /// + enum EADDRINUSE = 98; /// + enum EADDRNOTAVAIL = 99; /// + enum ENETDOWN = 100; /// + enum ENETUNREACH = 101; /// + enum ENETRESET = 102; /// + enum ECONNABORTED = 103; /// + enum ECONNRESET = 104; /// + enum ENOBUFS = 105; /// + enum EISCONN = 106; /// + enum ENOTCONN = 107; /// + enum ESHUTDOWN = 108; /// + enum ETOOMANYREFS = 109; /// + enum ETIMEDOUT = 110; /// + enum ECONNREFUSED = 111; /// + enum EHOSTDOWN = 112; /// + enum EHOSTUNREACH = 113; /// + enum EALREADY = 114; /// + enum EINPROGRESS = 115; /// + enum ESTALE = 116; /// + enum EUCLEAN = 117; /// + enum ENOTNAM = 118; /// + enum ENAVAIL = 119; /// + enum EISNAM = 120; /// + enum EREMOTEIO = 121; /// + enum EDQUOT = 122; /// + enum ENOMEDIUM = 123; /// + enum EMEDIUMTYPE = 124; /// + enum ECANCELED = 125; /// + enum ENOKEY = 126; /// + enum EKEYEXPIRED = 127; /// + enum EKEYREVOKED = 128; /// + enum EKEYREJECTED = 129; /// + enum EOWNERDEAD = 130; /// + enum ENOTRECOVERABLE = 131; /// + enum ERFKILL = 132; /// + enum EHWPOISON = 133; /// + } + else version (SPARC_Any) + { + enum EWOULDBLOCK = EAGAIN; /// + enum EINPROGRESS = 36; /// + enum EALREADY = 37; /// + enum ENOTSOCK = 38; /// + enum EDESTADDRREQ = 39; /// + enum EMSGSIZE = 40; /// + enum EPROTOTYPE = 41; /// + enum ENOPROTOOPT = 42; /// + enum EPROTONOSUPPORT = 43; /// + enum ESOCKTNOSUPPORT = 44; /// + enum EOPNOTSUPP = 45; /// + enum ENOTSUP = EOPNOTSUPP; /// + enum EPFNOSUPPORT = 46; /// + enum EAFNOSUPPORT = 47; /// + enum EADDRINUSE = 48; /// + enum EADDRNOTAVAIL = 49; /// + enum ENETDOWN = 50; /// + enum ENETUNREACH = 51; /// + enum ENETRESET = 52; /// + enum ECONNABORTED = 53; /// + enum ECONNRESET = 54; /// + enum ENOBUFS = 55; /// + enum EISCONN = 56; /// + enum ENOTCONN = 57; /// + enum ESHUTDOWN = 58; /// + enum ETOOMANYREFS = 59; /// + enum ETIMEDOUT = 60; /// + enum ECONNREFUSED = 61; /// + enum ELOOP = 62; /// + enum ENAMETOOLONG = 63; /// + enum EHOSTDOWN = 64; /// + enum EHOSTUNREACH = 65; /// + enum ENOTEMPTY = 66; /// + enum EPROCLIM = 67; /// + enum EUSERS = 68; /// + enum EDQUOT = 69; /// + enum ESTALE = 70; /// + enum EREMOTE = 71; /// + enum ENOSTR = 72; /// + enum ETIME = 73; /// + enum ENOSR = 74; /// + enum ENOMSG = 75; /// + enum EBADMSG = 76; /// + enum EIDRM = 77; /// + enum EDEADLK = 78; /// + enum ENOLCK = 79; /// + enum ENONET = 80; /// + enum ERREMOTE = 81; /// + enum ENOLINK = 82; /// + enum EADV = 83; /// + enum ESRMNT = 84; /// + enum ECOMM = 85; /// + enum EPROTO = 86; /// + enum EMULTIHOP = 87; /// + enum EDOTDOT = 88; /// + enum EREMCHG = 89; /// + enum ENOSYS = 90; /// + enum ESTRPIPE = 91; /// + enum EOVERFLOW = 92; /// + enum EBADFD = 93; /// + enum ECHRNG = 94; /// + enum EL2NSYNC = 95; /// + enum EL3HLT = 96; /// + enum EL3RST = 97; /// + enum ELNRNG = 98; /// + enum EUNATCH = 99; /// + enum ENOCSI = 100; /// + enum EL2HLT = 101; /// + enum EBADE = 102; /// + enum EBADR = 103; /// + enum EXFULL = 104; /// + enum ENOANO = 105; /// + enum EBADRQC = 106; /// + enum EBADSLT = 107; /// + enum EDEADLOCK = 108; /// + enum EBFONT = 109; /// + enum ELIBEXEC = 110; /// + enum ENODATA = 111; /// + enum ELIBBAD = 112; /// + enum ENOPKG = 113; /// + enum ELIBACC = 114; /// + enum ENOTUNIQ = 115; /// + enum ERESTART = 116; /// + enum EUCLEAN = 117; /// + enum ENOTNAM = 118; /// + enum ENAVAIL = 119; /// + enum EISNAM = 120; /// + enum EREMOTEIO = 121; /// + enum EILSEQ = 122; /// + enum ELIBMAX = 123; /// + enum ELIBSCN = 124; /// + enum ENOMEDIUM = 125; /// + enum EMEDIUMTYPE = 126; /// + enum ECANCELED = 127; /// + enum ENOKEY = 128; /// + enum EKEYEXPIRED = 129; /// + enum EKEYREVOKED = 130; /// + enum EKEYREJECTED = 131; /// + enum EOWNERDEAD = 132; /// + enum ENOTRECOVERABLE = 133; /// + enum ERFKILL = 134; /// + enum EHWPOISON = 135; /// + } + else version (IBMZ_Any) + { + enum EDEADLK = 35; /// + enum ENAMETOOLONG = 36; /// + enum ENOLCK = 37; /// + enum ENOSYS = 38; /// + enum ENOTEMPTY = 39; /// + enum ELOOP = 40; /// + enum EWOULDBLOCK = EAGAIN; /// + enum ENOMSG = 42; /// + enum EIDRM = 43; /// + enum ECHRNG = 44; /// + enum EL2NSYNC = 45; /// + enum EL3HLT = 46; /// + enum EL3RST = 47; /// + enum ELNRNG = 48; /// + enum EUNATCH = 49; /// + enum ENOCSI = 50; /// + enum EL2HLT = 51; /// + enum EBADE = 52; /// + enum EBADR = 53; /// + enum EXFULL = 54; /// + enum ENOANO = 55; /// + enum EBADRQC = 56; /// + enum EBADSLT = 57; /// + enum EDEADLOCK = EDEADLK; /// + enum EBFONT = 59; /// + enum ENOSTR = 60; /// + enum ENODATA = 61; /// + enum ETIME = 62; /// + enum ENOSR = 63; /// + enum ENONET = 64; /// + enum ENOPKG = 65; /// + enum EREMOTE = 66; /// + enum ENOLINK = 67; /// + enum EADV = 68; /// + enum ESRMNT = 69; /// + enum ECOMM = 70; /// + enum EPROTO = 71; /// + enum EMULTIHOP = 72; /// + enum EDOTDOT = 73; /// + enum EBADMSG = 74; /// + enum EOVERFLOW = 75; /// + enum ENOTUNIQ = 76; /// + enum EBADFD = 77; /// + enum EREMCHG = 78; /// + enum ELIBACC = 79; /// + enum ELIBBAD = 80; /// + enum ELIBSCN = 81; /// + enum ELIBMAX = 82; /// + enum ELIBEXEC = 83; /// + enum EILSEQ = 84; /// + enum ERESTART = 85; /// + enum ESTRPIPE = 86; /// + enum EUSERS = 87; /// + enum ENOTSOCK = 88; /// + enum EDESTADDRREQ = 89; /// + enum EMSGSIZE = 90; /// + enum EPROTOTYPE = 91; /// + enum ENOPROTOOPT = 92; /// + enum EPROTONOSUPPORT = 93; /// + enum ESOCKTNOSUPPORT = 94; /// + enum EOPNOTSUPP = 95; /// + enum ENOTSUP = EOPNOTSUPP; /// + enum EPFNOSUPPORT = 96; /// + enum EAFNOSUPPORT = 97; /// + enum EADDRINUSE = 98; /// + enum EADDRNOTAVAIL = 99; /// + enum ENETDOWN = 100; /// + enum ENETUNREACH = 101; /// + enum ENETRESET = 102; /// + enum ECONNABORTED = 103; /// + enum ECONNRESET = 104; /// + enum ENOBUFS = 105; /// + enum EISCONN = 106; /// + enum ENOTCONN = 107; /// + enum ESHUTDOWN = 108; /// + enum ETOOMANYREFS = 109; /// + enum ETIMEDOUT = 110; /// + enum ECONNREFUSED = 111; /// + enum EHOSTDOWN = 112; /// + enum EHOSTUNREACH = 113; /// + enum EALREADY = 114; /// + enum EINPROGRESS = 115; /// + enum ESTALE = 116; /// + enum EUCLEAN = 117; /// + enum ENOTNAM = 118; /// + enum ENAVAIL = 119; /// + enum EISNAM = 120; /// + enum EREMOTEIO = 121; /// + enum EDQUOT = 122; /// + enum ENOMEDIUM = 123; /// + enum EMEDIUMTYPE = 124; /// + enum ECANCELED = 125; /// + enum ENOKEY = 126; /// + enum EKEYEXPIRED = 127; /// + enum EKEYREVOKED = 128; /// + enum EKEYREJECTED = 129; /// + enum EOWNERDEAD = 130; /// + enum ENOTRECOVERABLE = 131; /// + enum ERFKILL = 132; /// + enum EHWPOISON = 133; /// + } + else version (LoongArch64) + { + enum EDEADLK = 35; /// + enum ENAMETOOLONG = 36; /// + enum ENOLCK = 37; /// + enum ENOSYS = 38; /// + enum ENOTEMPTY = 39; /// + enum ELOOP = 40; /// + enum EWOULDBLOCK = EAGAIN; /// + enum ENOMSG = 42; /// + enum EIDRM = 43; /// + enum ECHRNG = 44; /// + enum EL2NSYNC = 45; /// + enum EL3HLT = 46; /// + enum EL3RST = 47; /// + enum ELNRNG = 48; /// + enum EUNATCH = 49; /// + enum ENOCSI = 50; /// + enum EL2HLT = 51; /// + enum EBADE = 52; /// + enum EBADR = 53; /// + enum EXFULL = 54; /// + enum ENOANO = 55; /// + enum EBADRQC = 56; /// + enum EBADSLT = 57; /// + enum EDEADLOCK = EDEADLK; /// + enum EBFONT = 59; /// + enum ENOSTR = 60; /// + enum ENODATA = 61; /// + enum ETIME = 62; /// + enum ENOSR = 63; /// + enum ENONET = 64; /// + enum ENOPKG = 65; /// + enum EREMOTE = 66; /// + enum ENOLINK = 67; /// + enum EADV = 68; /// + enum ESRMNT = 69; /// + enum ECOMM = 70; /// + enum EPROTO = 71; /// + enum EMULTIHOP = 72; /// + enum EDOTDOT = 73; /// + enum EBADMSG = 74; /// + enum EOVERFLOW = 75; /// + enum ENOTUNIQ = 76; /// + enum EBADFD = 77; /// + enum EREMCHG = 78; /// + enum ELIBACC = 79; /// + enum ELIBBAD = 80; /// + enum ELIBSCN = 81; /// + enum ELIBMAX = 82; /// + enum ELIBEXEC = 83; /// + enum EILSEQ = 84; /// + enum ERESTART = 85; /// + enum ESTRPIPE = 86; /// + enum EUSERS = 87; /// + enum ENOTSOCK = 88; /// + enum EDESTADDRREQ = 89; /// + enum EMSGSIZE = 90; /// + enum EPROTOTYPE = 91; /// + enum ENOPROTOOPT = 92; /// + enum EPROTONOSUPPORT = 93; /// + enum ESOCKTNOSUPPORT = 94; /// + enum EOPNOTSUPP = 95; /// + enum ENOTSUP = EOPNOTSUPP; /// + enum EPFNOSUPPORT = 96; /// + enum EAFNOSUPPORT = 97; /// + enum EADDRINUSE = 98; /// + enum EADDRNOTAVAIL = 99; /// + enum ENETDOWN = 100; /// + enum ENETUNREACH = 101; /// + enum ENETRESET = 102; /// + enum ECONNABORTED = 103; /// + enum ECONNRESET = 104; /// + enum ENOBUFS = 105; /// + enum EISCONN = 106; /// + enum ENOTCONN = 107; /// + enum ESHUTDOWN = 108; /// + enum ETOOMANYREFS = 109; /// + enum ETIMEDOUT = 110; /// + enum ECONNREFUSED = 111; /// + enum EHOSTDOWN = 112; /// + enum EHOSTUNREACH = 113; /// + enum EALREADY = 114; /// + enum EINPROGRESS = 115; /// + enum ESTALE = 116; /// + enum EUCLEAN = 117; /// + enum ENOTNAM = 118; /// + enum ENAVAIL = 119; /// + enum EISNAM = 120; /// + enum EREMOTEIO = 121; /// + enum EDQUOT = 122; /// + enum ENOMEDIUM = 123; /// + enum EMEDIUMTYPE = 124; /// + enum ECANCELED = 125; /// + enum ENOKEY = 126; /// + enum EKEYEXPIRED = 127; /// + enum EKEYREVOKED = 128; /// + enum EKEYREJECTED = 129; /// + enum EOWNERDEAD = 130; /// + enum ENOTRECOVERABLE = 131; /// + enum ERFKILL = 132; /// + enum EHWPOISON = 133; /// + } + else + { + static assert(false, "Architecture not supported."); + } +} +else version (Darwin) +{ + enum EPERM = 1; /// Operation not permitted + enum ENOENT = 2; /// No such file or directory + enum ESRCH = 3; /// No such process + enum EINTR = 4; /// Interrupted system call + enum EIO = 5; /// Input/output error + enum ENXIO = 6; /// Device not configured + enum E2BIG = 7; /// Argument list too long + enum ENOEXEC = 8; /// Exec format error + enum EBADF = 9; /// Bad file descriptor + enum ECHILD = 10; /// No child processes + enum EDEADLK = 11; /// Resource deadlock avoided + enum ENOMEM = 12; /// Cannot allocate memory + enum EACCES = 13; /// Permission denied + enum EFAULT = 14; /// Bad address + enum EBUSY = 16; /// Device busy + enum EEXIST = 17; /// File exists + enum EXDEV = 18; /// Cross-device link + enum ENODEV = 19; /// Operation not supported by device + enum ENOTDIR = 20; /// Not a directory + enum EISDIR = 21; /// Is a directory + enum EINVAL = 22; /// Invalid argument + enum ENFILE = 23; /// Too many open files in system + enum EMFILE = 24; /// Too many open files + enum ENOTTY = 25; /// Inappropriate ioctl for device + enum ETXTBSY = 26; /// Text file busy + enum EFBIG = 27; /// File too large + enum ENOSPC = 28; /// No space left on device + enum ESPIPE = 29; /// Illegal seek + enum EROFS = 30; /// Read-only file system + enum EMLINK = 31; /// Too many links + enum EPIPE = 32; /// Broken pipe + enum EDOM = 33; /// Numerical argument out of domain + enum ERANGE = 34; /// Result too large + enum EAGAIN = 35; /// Resource temporarily unavailable + enum EWOULDBLOCK = EAGAIN; /// Operation would block + enum EINPROGRESS = 36; /// Operation now in progress + enum EALREADY = 37; /// Operation already in progress + enum ENOTSOCK = 38; /// Socket operation on non-socket + enum EDESTADDRREQ = 39; /// Destination address required + enum EMSGSIZE = 40; /// Message too long + enum EPROTOTYPE = 41; /// Protocol wrong type for socket + enum ENOPROTOOPT = 42; /// Protocol not available + enum EPROTONOSUPPORT = 43; /// Protocol not supported + enum ENOTSUP = 45; /// Operation not supported + enum EOPNOTSUPP = ENOTSUP; /// Operation not supported on socket + enum EAFNOSUPPORT = 47; /// Address family not supported by protocol family + enum EADDRINUSE = 48; /// Address already in use + enum EADDRNOTAVAIL = 49; /// Can't assign requested address + enum ENETDOWN = 50; /// Network is down + enum ENETUNREACH = 51; /// Network is unreachable + enum ENETRESET = 52; /// Network dropped connection on reset + enum ECONNABORTED = 53; /// Software caused connection abort + enum ECONNRESET = 54; /// Connection reset by peer + enum ENOBUFS = 55; /// No buffer space available + enum EISCONN = 56; /// Socket is already connected + enum ENOTCONN = 57; /// Socket is not connected + enum ETIMEDOUT = 60; /// Operation timed out + enum ECONNREFUSED = 61; /// Connection refused + enum ELOOP = 62; /// Too many levels of symbolic links + enum ENAMETOOLONG = 63; /// File name too long + enum EHOSTUNREACH = 65; /// No route to host + enum ENOTEMPTY = 66; /// Directory not empty + enum EDQUOT = 69; /// Disc quota exceeded + enum ESTALE = 70; /// Stale NFS file handle + enum ENOLCK = 77; /// No locks available + enum ENOSYS = 78; /// Function not implemented + enum EOVERFLOW = 84; /// Value too large to be stored in data type + enum ECANCELED = 89; /// Operation canceled + enum EIDRM = 90; /// Identifier removed + enum ENOMSG = 91; /// No message of desired type + enum EILSEQ = 92; /// Illegal byte sequence + enum EBADMSG = 94; /// Bad message + enum EMULTIHOP = 95; /// Reserved + enum ENODATA = 96; /// No message available on STREAM + enum ENOLINK = 97; /// Reserved + enum ENOSR = 98; /// No STREAM resources + enum ENOSTR = 99; /// Not a STREAM + enum EPROTO = 100; /// Protocol error + enum ETIME = 101; /// STREAM ioctl timeout + enum ELAST = 101; /// Must be equal largest errno +} +else version (FreeBSD) +{ + enum EPERM = 1; /// Operation not permitted + enum ENOENT = 2; /// No such file or directory + enum ESRCH = 3; /// No such process + enum EINTR = 4; /// Interrupted system call + enum EIO = 5; /// Input/output error + enum ENXIO = 6; /// Device not configured + enum E2BIG = 7; /// Argument list too long + enum ENOEXEC = 8; /// Exec format error + enum EBADF = 9; /// Bad file descriptor + enum ECHILD = 10; /// No child processes + enum EDEADLK = 11; /// Resource deadlock avoided + enum ENOMEM = 12; /// Cannot allocate memory + enum EACCES = 13; /// Permission denied + enum EFAULT = 14; /// Bad address + enum ENOTBLK = 15; /// Block device required + enum EBUSY = 16; /// Device busy + enum EEXIST = 17; /// File exists + enum EXDEV = 18; /// Cross-device link + enum ENODEV = 19; /// Operation not supported by device + enum ENOTDIR = 20; /// Not a directory + enum EISDIR = 21; /// Is a directory + enum EINVAL = 22; /// Invalid argument + enum ENFILE = 23; /// Too many open files in system + enum EMFILE = 24; /// Too many open files + enum ENOTTY = 25; /// Inappropriate ioctl for device + enum ETXTBSY = 26; /// Text file busy + enum EFBIG = 27; /// File too large + enum ENOSPC = 28; /// No space left on device + enum ESPIPE = 29; /// Illegal seek + enum EROFS = 30; /// Read-only file system + enum EMLINK = 31; /// Too many links + enum EPIPE = 32; /// Broken pipe + enum EDOM = 33; /// Numerical argument out of domain + enum ERANGE = 34; /// Result too large + enum EAGAIN = 35; /// Resource temporarily unavailable + enum EWOULDBLOCK = EAGAIN; /// Operation would block + enum EINPROGRESS = 36; /// Operation now in progress + enum EALREADY = 37; /// Operation already in progress + enum ENOTSOCK = 38; /// Socket operation on non-socket + enum EDESTADDRREQ = 39; /// Destination address required + enum EMSGSIZE = 40; /// Message too long + enum EPROTOTYPE = 41; /// Protocol wrong type for socket + enum ENOPROTOOPT = 42; /// Protocol not available + enum EPROTONOSUPPORT = 43; /// Protocol not supported + enum ENOTSUP = 45; /// Operation not supported + enum EOPNOTSUPP = ENOTSUP; /// Operation not supported on socket + enum EAFNOSUPPORT = 47; /// Address family not supported by protocol family + enum EADDRINUSE = 48; /// Address already in use + enum EADDRNOTAVAIL = 49; /// Can't assign requested address + enum ENETDOWN = 50; /// Network is down + enum ENETUNREACH = 51; /// Network is unreachable + enum ENETRESET = 52; /// Network dropped connection on reset + enum ECONNABORTED = 53; /// Software caused connection abort + enum ECONNRESET = 54; /// Connection reset by peer + enum ENOBUFS = 55; /// No buffer space available + enum EISCONN = 56; /// Socket is already connected + enum ENOTCONN = 57; /// Socket is not connected + enum ESHUTDOWN = 58; /// Can't send after socket shutdown + enum ETOOMANYREFS = 59; /// Too many refrences; can't splice + enum ETIMEDOUT = 60; /// Operation timed out + enum ECONNREFUSED = 61; /// Connection refused + enum ELOOP = 62; /// Too many levels of symbolic links + enum ENAMETOOLONG = 63; /// File name too long + enum EHOSTUNREACH = 65; /// No route to host + enum ENOTEMPTY = 66; /// Directory not empty + enum EPROCLIM = 67; /// Too many processes + enum EUSERS = 68; /// Too many users + enum EDQUOT = 69; /// Disc quota exceeded + enum ESTALE = 70; /// Stale NFS file handle + enum EREMOTE = 71; /// Too many levels of remote in path + enum EBADRPC = 72; /// RPC struct is bad + enum ERPCMISMATCH = 73; /// RPC version wrong + enum EPROGUNAVAIL = 74; /// RPC prog. not avail + enum EPROGMISMATCH = 75; /// Program version wrong + enum EPROCUNAVAIL = 76; /// Bad procedure for program + enum ENOLCK = 77; /// No locks available + enum ENOSYS = 78; /// Function not implemented + enum EFTYPE = 79; /// Inappropriate file type or format + enum EAUTH = 80; /// Authentication error + enum ENEEDAUTH = 81; /// Need authenticator + enum EIDRM = 82; /// Itendifier removed + enum ENOMSG = 83; /// No message of desired type + enum EOVERFLOW = 84; /// Value too large to be stored in data type + enum ECANCELED = 85; /// Operation canceled + enum EILSEQ = 86; /// Illegal byte sequence + enum ENOATTR = 87; /// Attribute not found + enum EDOOFUS = 88; /// Programming error + enum EBADMSG = 89; /// Bad message + enum EMULTIHOP = 90; /// Multihop attempted + enum ENOLINK = 91; /// Link has been severed + enum EPROTO = 92; /// Protocol error + enum ELAST = 92; /// Must be equal largest errno +} +else version (NetBSD) +{ + // http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/sys/sys/errno.h + enum EPERM = 1; + enum ENOENT = 2; + enum ESRCH = 3; + enum EINTR = 4; + enum EIO = 5; + enum ENXIO = 6; + enum E2BIG = 7; + enum ENOEXEC = 8; + enum EBADF = 9; + enum ECHILD = 10; + enum EDEADLK = 11; + /// + enum ENOMEM = 12; + enum EACCES = 13; + enum EFAULT = 14; + enum ENOTBLK = 15; + enum EBUSY = 16; + enum EEXIST = 17; + enum EXDEV = 18; + enum ENODEV = 19; + enum ENOTDIR = 20; + enum EISDIR = 21; + enum EINVAL = 22; + enum ENFILE = 23; + enum EMFILE = 24; + enum ENOTTY = 25; + enum ETXTBSY = 26; + enum EFBIG = 27; + enum ENOSPC = 28; + enum ESPIPE = 29; + enum EROFS = 30; + enum EMLINK = 31; + enum EPIPE = 32; + /// + enum EDOM = 33; + enum ERANGE = 34; + + /// + enum EAGAIN = 35; + enum EWOULDBLOCK = EAGAIN; + enum EINPROGRESS = 36; + enum EALREADY = 37; + + /// + enum ENOTSOCK = 38; + enum EDESTADDRREQ = 39; + enum EMSGSIZE = 40; + enum EPROTOTYPE = 41; + enum ENOPROTOOPT = 42; + enum EPROTONOSUPPORT = 43; + enum ESOCKTNOSUPPORT = 44; + enum EOPNOTSUPP = 45; + enum EPFNOSUPPORT = 46; + enum EAFNOSUPPORT = 47; + enum EADDRINUSE = 48; + enum EADDRNOTAVAIL = 49; + + /// + enum ENETDOWN = 50; + enum ENETUNREACH = 51; + enum ENETRESET = 52; + enum ECONNABORTED = 53; + enum ECONNRESET = 54; + enum ENOBUFS = 55; + enum EISCONN = 56; + enum ENOTCONN = 57; + enum ESHUTDOWN = 58; + enum ETOOMANYREFS = 59; + enum ETIMEDOUT = 60; + enum ECONNREFUSED = 61; + enum ELOOP = 62; + enum ENAMETOOLONG = 63; + + /// + enum EHOSTDOWN = 64; + enum EHOSTUNREACH = 65; + enum ENOTEMPTY = 66; + + /// + enum EPROCLIM = 67; + enum EUSERS = 68; + enum EDQUOT = 69; + + /// + enum ESTALE = 70; + enum EREMOTE = 71; + enum EBADRPC = 72; + enum ERPCMISMATCH = 73; + enum EPROGUNAVAIL = 74; + enum EPROGMISMATCH = 75; + enum EPROCUNAVAIL = 76; + + enum ENOLCK = 77; + enum ENOSYS = 78; + + enum EFTYPE = 79; + enum EAUTH = 80; + enum ENEEDAUTH = 81; + + /// + enum EIDRM = 82; + enum ENOMSG = 83; + enum EOVERFLOW = 84; + /// + enum EILSEQ = 85; + + /// + enum ENOTSUP = 86; + + /// + enum ECANCELED = 87; + + /// + enum EBADMSG = 88; + + /// + enum ENODATA = 89; + enum ENOSR = 90; + enum ENOSTR = 91; + enum ETIME = 92; + + /// + enum ENOATTR = 93; + + /// + enum EMULTIHOP = 94; + enum ENOLINK = 95; + enum EPROTO = 96; +} +else version (OpenBSD) +{ + enum EPERM = 1; /// Operation not permitted + enum ENOENT = 2; /// No such file or directory + enum ESRCH = 3; /// No such process + enum EINTR = 4; /// Interrupted system call + enum EIO = 5; /// Input/output error + enum ENXIO = 6; /// Device not configured + enum E2BIG = 7; /// Argument list too long + enum ENOEXEC = 8; /// Exec format error + enum EBADF = 9; /// Bad file descriptor + enum ECHILD = 10; /// No child processes + enum EDEADLK = 11; /// Resource deadlock avoided + enum ENOMEM = 12; /// Cannot allocate memory + enum EACCES = 13; /// Permission denied + enum EFAULT = 14; /// Bad address + enum ENOTBLK = 15; /// Block device required + enum EBUSY = 16; /// Device busy + enum EEXIST = 17; /// File exists + enum EXDEV = 18; /// Cross-device link + enum ENODEV = 19; /// Operation not supported by device + enum ENOTDIR = 20; /// Not a directory + enum EISDIR = 21; /// Is a directory + enum EINVAL = 22; /// Invalid argument + enum ENFILE = 23; /// Too many open files in system + enum EMFILE = 24; /// Too many open files + enum ENOTTY = 25; /// Inappropriate ioctl for device + enum ETXTBSY = 26; /// Text file busy + enum EFBIG = 27; /// File too large + enum ENOSPC = 28; /// No space left on device + enum ESPIPE = 29; /// Illegal seek + enum EROFS = 30; /// Read-only file system + enum EMLINK = 31; /// Too many links + enum EPIPE = 32; /// Broken pipe + enum EDOM = 33; /// Numerical argument out of domain + enum ERANGE = 34; /// Result too large + enum EAGAIN = 35; /// Resource temporarily unavailable + enum EWOULDBLOCK = EAGAIN; /// Operation would block + enum EINPROGRESS = 36; /// Operation now in progress + enum EALREADY = 37; /// Operation already in progress + enum ENOTSOCK = 38; /// Socket operation on non-socket + enum EDESTADDRREQ = 39; /// Destination address required + enum EMSGSIZE = 40; /// Message too long + enum EPROTOTYPE = 41; /// Protocol wrong type for socket + enum ENOPROTOOPT = 42; /// Protocol not available + enum EPROTONOSUPPORT = 43; /// Protocol not supported + enum ESOCKTNOSUPPORT = 44; /// Socket type not supported + enum EOPNOTSUPP = 45; /// Operation not supported + enum EPFNOSUPPORT = 46; /// Protocol family not supported + enum EAFNOSUPPORT = 47; /// Address family not supported by protocol family + enum EADDRINUSE = 48; /// Address already in use + enum EADDRNOTAVAIL = 49; /// Can't assign requested address + enum ENETDOWN = 50; /// Network is down + enum ENETUNREACH = 51; /// Network is unreachable + enum ENETRESET = 52; /// Network dropped connection on reset + enum ECONNABORTED = 53; /// Software caused connection abort + enum ECONNRESET = 54; /// Connection reset by peer + enum ENOBUFS = 55; /// No buffer space available + enum EISCONN = 56; /// Socket is already connected + enum ENOTCONN = 57; /// Socket is not connected + enum ESHUTDOWN = 58; /// Can't send after socket shutdown + enum ETOOMANYREFS = 59; /// Too many references: can't splice + enum ETIMEDOUT = 60; /// Operation timed out + enum ECONNREFUSED = 61; /// Connection refused + enum ELOOP = 62; /// Too many levels of symbolic links + enum ENAMETOOLONG = 63; /// File name too long + enum EHOSTDOWN = 64; /// Host is down + enum EHOSTUNREACH = 65; /// No route to host + enum ENOTEMPTY = 66; /// Directory not empty + enum EPROCLIM = 67; /// Too many processes + enum EUSERS = 68; /// Too many users + enum EDQUOT = 69; /// Disk quota exceeded + enum ESTALE = 70; /// Stale NFS file handle + enum EREMOTE = 71; /// Too many levels of remote in path + enum EBADRPC = 72; /// RPC struct is bad + enum ERPCMISMATCH = 73; /// RPC version wrong + enum EPROGUNAVAIL = 74; /// RPC program not available + enum EPROGMISMATCH = 75; /// Program version wrong + enum EPROCUNAVAIL = 76; /// Bad procedure for program + enum ENOLCK = 77; /// No locks available + enum ENOSYS = 78; /// Function not implemented + enum EFTYPE = 79; /// Inappropriate file type or format + enum EAUTH = 80; /// Authentication error + enum ENEEDAUTH = 81; /// Need authenticator + enum EIPSEC = 82; /// IPsec processing failure + enum ENOATTR = 83; /// Attribute not found + enum EILSEQ = 84; /// Illegal byte sequence + enum ENOMEDIUM = 85; /// No medium found + enum EMEDIUMTYPE = 86; /// Wrong medium type + enum EOVERFLOW = 87; /// Value too large to be stored in data type + enum ECANCELED = 88; /// Operation canceled + enum EIDRM = 89; /// Identifier removed + enum ENOMSG = 90; /// No message of desired type + enum ENOTSUP = 91; /// Not supported + enum EBADMSG = 92; /// Bad message + enum ENOTRECOVERABLE = 93; /// State not recoverable + enum EOWNERDEAD = 94; /// Previous owner died + enum EPROTO = 95; /// Protocol error + enum ELAST = 95; /// Must be equal largest errno +} +else version (DragonFlyBSD) +{ + enum EPERM = 1; + enum ENOENT = 2; + enum ESRCH = 3; + enum EINTR = 4; + enum EIO = 5; + enum ENXIO = 6; + enum E2BIG = 7; + enum ENOEXEC = 8; + enum EBADF = 9; + enum ECHILD = 10; + enum EDEADLK = 11; + enum ENOMEM = 12; + enum EACCES = 13; + enum EFAULT = 14; + enum ENOTBLK = 15; + enum EBUSY = 16; + enum EEXIST = 17; + enum EXDEV = 18; + enum ENODEV = 19; + enum ENOTDIR = 20; + enum EISDIR = 21; + enum EINVAL = 22; + enum ENFILE = 23; + enum EMFILE = 24; + enum ENOTTY = 25; + enum ETXTBSY = 26; + enum EFBIG = 27; + enum ENOSPC = 28; + enum ESPIPE = 29; + enum EROFS = 30; + enum EMLINK = 31; + enum EPIPE = 32; + enum EDOM = 33; + enum ERANGE = 34; + enum EAGAIN = 35; + enum EWOULDBLOCK = EAGAIN; + enum EINPROGRESS = 36; + enum EALREADY = 37; + enum ENOTSOCK = 38; + enum EDESTADDRREQ = 39; + enum EMSGSIZE = 40; + enum EPROTOTYPE = 41; + enum ENOPROTOOPT = 42; + enum EPROTONOSUPPORT = 43; + enum ENOTSUP = 45; + enum EOPNOTSUPP = ENOTSUP; + enum EPFNOSUPPORT = 46; + enum EAFNOSUPPORT = 47; + enum EADDRINUSE = 48; + enum EADDRNOTAVAIL = 49; + enum ENETDOWN = 50; + enum ENETUNREACH = 51; + enum ENETRESET = 52; + enum ECONNABORTED = 53; + enum ECONNRESET = 54; + enum ENOBUFS = 55; + enum EISCONN = 56; + enum ENOTCONN = 57; + enum ESHUTDOWN = 58; + enum ETOOMANYREFS = 59; + enum ETIMEDOUT = 60; + enum ECONNREFUSED = 61; + enum ELOOP = 62; + enum ENAMETOOLONG = 63; + enum EHOSTUNREACH = 65; + enum ENOTEMPTY = 66; + enum EPROCLIM = 67; + enum EUSERS = 68; + enum EDQUOT = 69; + enum ESTALE = 70; + enum EREMOTE = 71; + enum EBADRPC = 72; + enum ERPCMISMATCH = 73; + enum EPROGUNAVAIL = 74; + enum EPROGMISMATCH = 75; + enum EPROCUNAVAIL = 76; + enum ENOLCK = 77; + enum ENOSYS = 78; + enum EFTYPE = 79; + enum EAUTH = 80; + enum ENEEDAUTH = 81; + enum EIDRM = 82; + enum ENOMSG = 83; + enum EOVERFLOW = 84; + enum ECANCELED = 85; + enum EILSEQ = 86; + enum ENOATTR = 87; + enum EDOOFUS = 88; + enum EBADMSG = 89; + enum EMULTIHOP = 90; + enum ENOLINK = 91; + enum EPROTO = 92; + enum ENOMEDIUM = 93; + enum EUNUSED94 = 94; + enum EUNUSED95 = 95; + enum EUNUSED96 = 96; + enum EUNUSED97 = 97; + enum EUNUSED98 = 98; + enum EASYNC = 99; + enum ELAST = 99; +} +else version (Solaris) +{ + enum EPERM = 1; /// Not super-user + enum ENOENT = 2; /// No such file or directory + enum ESRCH = 3; /// No such process + enum EINTR = 4; /// interrupted system call + enum EIO = 5; /// I/O error + enum ENXIO = 6; /// No such device or address + enum E2BIG = 7; /// Arg list too long + enum ENOEXEC = 8; /// Exec format error + enum EBADF = 9; /// Bad file number + enum ECHILD = 10; /// No children + enum EAGAIN = 11; /// Resource temporarily unavailable + enum ENOMEM = 12; /// Not enough core + enum EACCES = 13; /// Permission denied + enum EFAULT = 14; /// Bad address + enum ENOTBLK = 15; /// Block device required + enum EBUSY = 16; /// Mount device busy + enum EEXIST = 17; /// File exists + enum EXDEV = 18; /// Cross-device link + enum ENODEV = 19; /// No such device + enum ENOTDIR = 20; /// Not a directory + enum EISDIR = 21; /// Is a directory + enum EINVAL = 22; /// Invalid argument + enum ENFILE = 23; /// File table overflow + enum EMFILE = 24; /// Too many open files + enum ENOTTY = 25; /// Inappropriate ioctl for device + enum ETXTBSY = 26; /// Text file busy + enum EFBIG = 27; /// File too large + enum ENOSPC = 28; /// No space left on device + enum ESPIPE = 29; /// Illegal seek + enum EROFS = 30; /// Read only file system + enum EMLINK = 31; /// Too many links + enum EPIPE = 32; /// Broken pipe + enum EDOM = 33; /// Math arg out of domain of func + enum ERANGE = 34; /// Math result not representable + enum ENOMSG = 35; /// No message of desired type + enum EIDRM = 36; /// Identifier removed + enum ECHRNG = 37; /// Channel number out of range + enum EL2NSYNC = 38; /// Level 2 not synchronized + enum EL3HLT = 39; /// Level 3 halted + enum EL3RST = 40; /// Level 3 reset + enum ELNRNG = 41; /// Link number out of range + enum EUNATCH = 42; /// Protocol driver not attached + enum ENOCSI = 43; /// No CSI structure available + enum EL2HLT = 44; /// Level 2 halted + enum EDEADLK = 45; /// Deadlock condition. + enum ENOLCK = 46; /// No record locks available. + enum ECANCELED = 47; /// Operation canceled + enum ENOTSUP = 48; /// Operation not supported + enum EDQUOT = 49; /// Disc quota exceeded + enum EBADE = 50; /// invalid exchange + enum EBADR = 51; /// invalid request descriptor + enum EXFULL = 52; /// exchange full + enum ENOANO = 53; /// no anode + enum EBADRQC = 54; /// invalid request code + enum EBADSLT = 55; /// invalid slot + enum EDEADLOCK = 56; /// file locking deadlock error + enum EBFONT = 57; /// bad font file fmt + enum EOWNERDEAD = 58; /// process died with the lock + enum ENOTRECOVERABLE = 59; /// lock is not recoverable + enum ENOSTR = 60; /// Device not a stream + enum ENODATA = 61; /// no data (for no delay io) + enum ETIME = 62; /// timer expired + enum ENOSR = 63; /// out of streams resources + enum ENONET = 64; /// Machine is not on the network + enum ENOPKG = 65; /// Package not installed + enum EREMOTE = 66; /// The object is remote + enum ENOLINK = 67; /// the link has been severed + enum EADV = 68; /// advertise error + enum ESRMNT = 69; /// srmount error + enum ECOMM = 70; /// Communication error on send + enum EPROTO = 71; /// Protocol error + enum ELOCKUNMAPPED = 72; /// locked lock was unmapped + enum ENOTACTIVE = 73; /// Facility is not active + enum EMULTIHOP = 74; /// multihop attempted + enum EBADMSG = 77; /// trying to read unreadable message + enum ENAMETOOLONG = 78; /// path name is too long + enum EOVERFLOW = 79; /// value too large to be stored in data type + enum ENOTUNIQ = 80; /// given log. name not unique + enum EBADFD = 81; /// f.d. invalid for this operation + enum EREMCHG = 82; /// Remote address changed + enum ELIBACC = 83; /// Can't access a needed shared lib. + enum ELIBBAD = 84; /// Accessing a corrupted shared lib. + enum ELIBSCN = 85; /// .lib section in a.out corrupted. + enum ELIBMAX = 86; /// Attempting to link in too many libs. + enum ELIBEXEC = 87; /// Attempting to exec a shared library. + enum EILSEQ = 88; /// Illegal byte sequence. + enum ENOSYS = 89; /// Unsupported file system operation + enum ELOOP = 90; /// Symbolic link loop + enum ERESTART = 91; /// Restartable system call + enum ESTRPIPE = 92; /// if pipe/FIFO, don't sleep in stream head + enum ENOTEMPTY = 93; /// directory not empty + enum EUSERS = 94; /// Too many users (for UFS) + enum ENOTSOCK = 95; /// Socket operation on non-socket + enum EDESTADDRREQ = 96; /// Destination address required + enum EMSGSIZE = 97; /// Message too long + enum EPROTOTYPE = 98; /// Protocol wrong type for socket + enum ENOPROTOOPT = 99; /// Protocol not available + enum EPROTONOSUPPORT = 120; /// Protocol not supported + enum ESOCKTNOSUPPORT = 121; /// Socket type not supported + enum EOPNOTSUPP = 122; /// Operation not supported on socket + enum EPFNOSUPPORT = 123; /// Protocol family not supported + enum EAFNOSUPPORT = 124; /// Address family not supported by the protocol family + enum EADDRINUSE = 125; /// Address already in use + enum EADDRNOTAVAIL = 126; /// Can't assign requested address + enum ENETDOWN = 127; /// Network is down + enum ENETUNREACH = 128; /// Network is unreachable + enum ENETRESET = 129; /// Network dropped connection because of reset + enum ECONNABORTED = 130; /// Software caused connection abort + enum ECONNRESET = 131; /// Connection reset by peer + enum ENOBUFS = 132; /// No buffer space available + enum EISCONN = 133; /// Socket is already connected + enum ENOTCONN = 134; /// Socket is not connected + enum ESHUTDOWN = 143; /// Can't send after socket shutdown + enum ETOOMANYREFS = 144; /// Too many references: can't splice + enum ETIMEDOUT = 145; /// Connection timed out + enum ECONNREFUSED = 146; /// Connection refused + enum EHOSTDOWN = 147; /// Host is down + enum EHOSTUNREACH = 148; /// No route to host + enum EWOULDBLOCK = EAGAIN; /// Resource temporarily unavailable + enum EALREADY = 149; /// operation already in progress + enum EINPROGRESS = 150; /// operation now in progress + enum ESTALE = 151; /// Stale NFS file handle +} +else version (Haiku) +{ + // https://github.com/haiku/haiku/blob/master/headers/os/support/Errors.h + // https://github.com/haiku/haiku/blob/master/headers/build/os/support/Errors.h + import core.stdc.limits : INT_MIN; + enum B_GENERAL_ERROR_BASE = INT_MIN; + enum B_OS_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x1000); + enum B_APP_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x2000); + enum B_INTERFACE_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x3000); + enum B_MEDIA_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x4000); + /* - 0x41ff */ + enum B_TRANSLATION_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x4800); + /* - 0x48ff */ + enum B_MIDI_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x5000); + enum B_STORAGE_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x6000); + enum B_POSIX_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x7000); + enum B_MAIL_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x8000); + enum B_PRINT_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x9000); + enum B_DEVICE_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0xa000); + + /* General Errors */ + enum B_NO_MEMORY = (B_GENERAL_ERROR_BASE + 0); + enum B_IO_ERROR = (B_GENERAL_ERROR_BASE + 1); + enum B_PERMISSION_DENIED = (B_GENERAL_ERROR_BASE + 2); + enum B_BAD_INDEX = (B_GENERAL_ERROR_BASE + 3); + enum B_BAD_TYPE = (B_GENERAL_ERROR_BASE + 4); + enum B_BAD_VALUE = (B_GENERAL_ERROR_BASE + 5); + enum B_MISMATCHED_VALUES = (B_GENERAL_ERROR_BASE + 6); + enum B_NAME_NOT_FOUND = (B_GENERAL_ERROR_BASE + 7); + enum B_NAME_IN_USE = (B_GENERAL_ERROR_BASE + 8); + enum B_TIMED_OUT = (B_GENERAL_ERROR_BASE + 9); + enum B_INTERRUPTED = (B_GENERAL_ERROR_BASE + 10); + enum B_WOULD_BLOCK = (B_GENERAL_ERROR_BASE + 11); + enum B_CANCELED = (B_GENERAL_ERROR_BASE + 12); + enum B_NO_INIT = (B_GENERAL_ERROR_BASE + 13); + enum B_NOT_INITIALIZED = (B_GENERAL_ERROR_BASE + 13); + enum B_BUSY = (B_GENERAL_ERROR_BASE + 14); + enum B_NOT_ALLOWED = (B_GENERAL_ERROR_BASE + 15); + enum B_BAD_DATA = (B_GENERAL_ERROR_BASE + 16); + enum B_DONT_DO_THAT = (B_GENERAL_ERROR_BASE + 17); + + enum B_ERROR = (-1); + enum B_OK = (int(0)); + enum B_NO_ERROR = (int(0)); + + /* Kernel Kit Errors */ + enum B_BAD_SEM_ID = (B_OS_ERROR_BASE + 0); + enum B_NO_MORE_SEMS = (B_OS_ERROR_BASE + 1); + + enum B_BAD_THREAD_ID = (B_OS_ERROR_BASE + 0x100); + enum B_NO_MORE_THREADS = (B_OS_ERROR_BASE + 0x101); + enum B_BAD_THREAD_STATE = (B_OS_ERROR_BASE + 0x102); + enum B_BAD_TEAM_ID = (B_OS_ERROR_BASE + 0x103); + enum B_NO_MORE_TEAMS = (B_OS_ERROR_BASE + 0x104); + + enum B_BAD_PORT_ID = (B_OS_ERROR_BASE + 0x200); + enum B_NO_MORE_PORTS = (B_OS_ERROR_BASE + 0x201); + + enum B_BAD_IMAGE_ID = (B_OS_ERROR_BASE + 0x300); + enum B_BAD_ADDRESS = (B_OS_ERROR_BASE + 0x301); + enum B_NOT_AN_EXECUTABLE = (B_OS_ERROR_BASE + 0x302); + enum B_MISSING_LIBRARY = (B_OS_ERROR_BASE + 0x303); + enum B_MISSING_SYMBOL = (B_OS_ERROR_BASE + 0x304); + enum B_UNKNOWN_EXECUTABLE = (B_OS_ERROR_BASE + 0x305); + enum B_LEGACY_EXECUTABLE = (B_OS_ERROR_BASE + 0x306); + + enum B_DEBUGGER_ALREADY_INSTALLED = (B_OS_ERROR_BASE + 0x400); + + /* Application Kit Errors */ + enum B_BAD_REPLY = (B_APP_ERROR_BASE + 0); + enum B_DUPLICATE_REPLY = (B_APP_ERROR_BASE + 1); + enum B_MESSAGE_TO_SELF = (B_APP_ERROR_BASE + 2); + enum B_BAD_HANDLER = (B_APP_ERROR_BASE + 3); + enum B_ALREADY_RUNNING = (B_APP_ERROR_BASE + 4); + enum B_LAUNCH_FAILED = (B_APP_ERROR_BASE + 5); + enum B_AMBIGUOUS_APP_LAUNCH = (B_APP_ERROR_BASE + 6); + enum B_UNKNOWN_MIME_TYPE = (B_APP_ERROR_BASE + 7); + enum B_BAD_SCRIPT_SYNTAX = (B_APP_ERROR_BASE + 8); + enum B_LAUNCH_FAILED_NO_RESOLVE_LINK = (B_APP_ERROR_BASE + 9); + enum B_LAUNCH_FAILED_EXECUTABLE = (B_APP_ERROR_BASE + 10); + enum B_LAUNCH_FAILED_APP_NOT_FOUND = (B_APP_ERROR_BASE + 11); + enum B_LAUNCH_FAILED_APP_IN_TRASH = (B_APP_ERROR_BASE + 12); + enum B_LAUNCH_FAILED_NO_PREFERRED_APP = (B_APP_ERROR_BASE + 13); + enum B_LAUNCH_FAILED_FILES_APP_NOT_FOUND = (B_APP_ERROR_BASE + 14); + enum B_BAD_MIME_SNIFFER_RULE = (B_APP_ERROR_BASE + 15); + enum B_NOT_A_MESSAGE = (B_APP_ERROR_BASE + 16); + enum B_SHUTDOWN_CANCELLED = (B_APP_ERROR_BASE + 17); + enum B_SHUTTING_DOWN = (B_APP_ERROR_BASE + 18); + + /* Storage Kit/File System Errors */ + enum B_FILE_ERROR = (B_STORAGE_ERROR_BASE + 0); + enum B_FILE_NOT_FOUND = (B_STORAGE_ERROR_BASE + 1); + /* deprecated: use B_ENTRY_NOT_FOUND instead */ + enum B_FILE_EXISTS = (B_STORAGE_ERROR_BASE + 2); + enum B_ENTRY_NOT_FOUND = (B_STORAGE_ERROR_BASE + 3); + enum B_NAME_TOO_LONG = (B_STORAGE_ERROR_BASE + 4); + enum B_NOT_A_DIRECTORY = (B_STORAGE_ERROR_BASE + 5); + enum B_DIRECTORY_NOT_EMPTY = (B_STORAGE_ERROR_BASE + 6); + enum B_DEVICE_FULL = (B_STORAGE_ERROR_BASE + 7); + enum B_READ_ONLY_DEVICE = (B_STORAGE_ERROR_BASE + 8); + enum B_IS_A_DIRECTORY = (B_STORAGE_ERROR_BASE + 9); + enum B_NO_MORE_FDS = (B_STORAGE_ERROR_BASE + 10); + enum B_CROSS_DEVICE_LINK = (B_STORAGE_ERROR_BASE + 11); + enum B_LINK_LIMIT = (B_STORAGE_ERROR_BASE + 12); + enum B_BUSTED_PIPE = (B_STORAGE_ERROR_BASE + 13); + enum B_UNSUPPORTED = (B_STORAGE_ERROR_BASE + 14); + enum B_PARTITION_TOO_SMALL = (B_STORAGE_ERROR_BASE + 15); + enum B_PARTIAL_READ = (B_STORAGE_ERROR_BASE + 16); + enum B_PARTIAL_WRITE = (B_STORAGE_ERROR_BASE + 17); + + /* POSIX Errors */ + enum B_USE_POSITIVE_POSIX_ERRORS = false; + + static if (B_USE_POSITIVE_POSIX_ERRORS) + { + enum B_TO_POSIX_ERROR(int code) = -code; + } + else + { + enum B_TO_POSIX_ERROR(int code) = code; + } + alias B_FROM_POSIX_ERROR = B_TO_POSIX_ERROR; + + enum B_POSIX_ENOMEM = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 0); + enum E2BIG = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 1); + enum ECHILD = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 2); + enum EDEADLK = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 3); + enum EFBIG = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 4); + enum EMLINK = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 5); + enum ENFILE = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 6); + enum ENODEV = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 7); + enum ENOLCK = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 8); + enum ENOSYS = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 9); + enum ENOTTY = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 10); + enum ENXIO = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 11); + enum ESPIPE = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 12); + enum ESRCH = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 13); + enum EFPOS = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 14); + enum ESIGPARM = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 15); + enum EDOM = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 16); + enum ERANGE = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 17); + enum EPROTOTYPE = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 18); + enum EPROTONOSUPPORT = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 19); + enum EPFNOSUPPORT = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 20); + enum EAFNOSUPPORT = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 21); + enum EADDRINUSE = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 22); + enum EADDRNOTAVAIL = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 23); + enum ENETDOWN = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 24); + enum ENETUNREACH = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 25); + enum ENETRESET = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 26); + enum ECONNABORTED = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 27); + enum ECONNRESET = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 28); + enum EISCONN = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 29); + enum ENOTCONN = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 30); + enum ESHUTDOWN = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 31); + enum ECONNREFUSED = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 32); + enum EHOSTUNREACH = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 33); + enum ENOPROTOOPT = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 34); + enum ENOBUFS = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 35); + enum EINPROGRESS = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 36); + enum EALREADY = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 37); + enum EILSEQ = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 38); + enum ENOMSG = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 39); + enum ESTALE = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 40); + enum EOVERFLOW = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 41); + enum EMSGSIZE = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 42); + enum EOPNOTSUPP = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 43); + enum ENOTSOCK = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 44); + enum EHOSTDOWN = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 45); + enum EBADMSG = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 46); + enum ECANCELED = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 47); + enum EDESTADDRREQ = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 48); + enum EDQUOT = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 49); + enum EIDRM = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 50); + enum EMULTIHOP = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 51); + enum ENODATA = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 52); + enum ENOLINK = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 53); + enum ENOSR = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 54); + enum ENOSTR = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 55); + enum ENOTSUP = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 56); + enum EPROTO = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 57); + enum ETIME = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 58); + enum ETXTBSY = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 59); + enum ENOATTR = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 60); + + /* B_NO_MEMORY (0x80000000) can't be negated, so it needs special handling */ + static if (B_USE_POSITIVE_POSIX_ERRORS) + enum ENOMEM = B_POSIX_ENOMEM; + else + enum ENOMEM = B_NO_MEMORY; + + /* POSIX errors that can be mapped to BeOS error codes */ + enum EACCES = B_TO_POSIX_ERROR!(B_PERMISSION_DENIED); + enum EINTR = B_TO_POSIX_ERROR!(B_INTERRUPTED); + enum EIO = B_TO_POSIX_ERROR!(B_IO_ERROR); + enum EBUSY = B_TO_POSIX_ERROR!(B_BUSY); + enum EFAULT = B_TO_POSIX_ERROR!(B_BAD_ADDRESS); + enum ETIMEDOUT = B_TO_POSIX_ERROR!(B_TIMED_OUT); + enum EAGAIN = B_TO_POSIX_ERROR!(B_WOULD_BLOCK) /* SysV compatibility */; + enum EWOULDBLOCK = B_TO_POSIX_ERROR!(B_WOULD_BLOCK) /* BSD compatibility */; + enum EBADF = B_TO_POSIX_ERROR!(B_FILE_ERROR); + enum EEXIST = B_TO_POSIX_ERROR!(B_FILE_EXISTS); + enum EINVAL = B_TO_POSIX_ERROR!(B_BAD_VALUE); + enum ENAMETOOLONG = B_TO_POSIX_ERROR!(B_NAME_TOO_LONG); + enum ENOENT = B_TO_POSIX_ERROR!(B_ENTRY_NOT_FOUND); + enum EPERM = B_TO_POSIX_ERROR!(B_NOT_ALLOWED); + enum ENOTDIR = B_TO_POSIX_ERROR!(B_NOT_A_DIRECTORY); + enum EISDIR = B_TO_POSIX_ERROR!(B_IS_A_DIRECTORY); + enum ENOTEMPTY = B_TO_POSIX_ERROR!(B_DIRECTORY_NOT_EMPTY); + enum ENOSPC = B_TO_POSIX_ERROR!(B_DEVICE_FULL); + enum EROFS = B_TO_POSIX_ERROR!(B_READ_ONLY_DEVICE); + enum EMFILE = B_TO_POSIX_ERROR!(B_NO_MORE_FDS); + enum EXDEV = B_TO_POSIX_ERROR!(B_CROSS_DEVICE_LINK); + enum ELOOP = B_TO_POSIX_ERROR!(B_LINK_LIMIT); + enum ENOEXEC = B_TO_POSIX_ERROR!(B_NOT_AN_EXECUTABLE); + enum EPIPE = B_TO_POSIX_ERROR!(B_BUSTED_PIPE); + + /* new error codes that can be mapped to POSIX errors */ + enum B_BUFFER_OVERFLOW = B_FROM_POSIX_ERROR!(EOVERFLOW); + enum B_TOO_MANY_ARGS = B_FROM_POSIX_ERROR!(E2BIG); + enum B_FILE_TOO_LARGE = B_FROM_POSIX_ERROR!(EFBIG); + enum B_RESULT_NOT_REPRESENTABLE = B_FROM_POSIX_ERROR!(ERANGE); + enum B_DEVICE_NOT_FOUND = B_FROM_POSIX_ERROR!(ENODEV); + enum B_NOT_SUPPORTED = B_FROM_POSIX_ERROR!(EOPNOTSUPP); + + /* Media Kit Errors */ + enum B_STREAM_NOT_FOUND = (B_MEDIA_ERROR_BASE + 0); + enum B_SERVER_NOT_FOUND = (B_MEDIA_ERROR_BASE + 1); + enum B_RESOURCE_NOT_FOUND = (B_MEDIA_ERROR_BASE + 2); + enum B_RESOURCE_UNAVAILABLE = (B_MEDIA_ERROR_BASE + 3); + enum B_BAD_SUBSCRIBER = (B_MEDIA_ERROR_BASE + 4); + enum B_SUBSCRIBER_NOT_ENTERED = (B_MEDIA_ERROR_BASE + 5); + enum B_BUFFER_NOT_AVAILABLE = (B_MEDIA_ERROR_BASE + 6); + enum B_LAST_BUFFER_ERROR = (B_MEDIA_ERROR_BASE + 7); + + enum B_MEDIA_SYSTEM_FAILURE = (B_MEDIA_ERROR_BASE + 100); + enum B_MEDIA_BAD_NODE = (B_MEDIA_ERROR_BASE + 101); + enum B_MEDIA_NODE_BUSY = (B_MEDIA_ERROR_BASE + 102); + enum B_MEDIA_BAD_FORMAT = (B_MEDIA_ERROR_BASE + 103); + enum B_MEDIA_BAD_BUFFER = (B_MEDIA_ERROR_BASE + 104); + enum B_MEDIA_TOO_MANY_NODES = (B_MEDIA_ERROR_BASE + 105); + enum B_MEDIA_TOO_MANY_BUFFERS = (B_MEDIA_ERROR_BASE + 106); + enum B_MEDIA_NODE_ALREADY_EXISTS = (B_MEDIA_ERROR_BASE + 107); + enum B_MEDIA_BUFFER_ALREADY_EXISTS = (B_MEDIA_ERROR_BASE + 108); + enum B_MEDIA_CANNOT_SEEK = (B_MEDIA_ERROR_BASE + 109); + enum B_MEDIA_CANNOT_CHANGE_RUN_MODE = (B_MEDIA_ERROR_BASE + 110); + enum B_MEDIA_APP_ALREADY_REGISTERED = (B_MEDIA_ERROR_BASE + 111); + enum B_MEDIA_APP_NOT_REGISTERED = (B_MEDIA_ERROR_BASE + 112); + enum B_MEDIA_CANNOT_RECLAIM_BUFFERS = (B_MEDIA_ERROR_BASE + 113); + enum B_MEDIA_BUFFERS_NOT_RECLAIMED = (B_MEDIA_ERROR_BASE + 114); + enum B_MEDIA_TIME_SOURCE_STOPPED = (B_MEDIA_ERROR_BASE + 115); + enum B_MEDIA_TIME_SOURCE_BUSY = (B_MEDIA_ERROR_BASE + 116); + enum B_MEDIA_BAD_SOURCE = (B_MEDIA_ERROR_BASE + 117); + enum B_MEDIA_BAD_DESTINATION = (B_MEDIA_ERROR_BASE + 118); + enum B_MEDIA_ALREADY_CONNECTED = (B_MEDIA_ERROR_BASE + 119); + enum B_MEDIA_NOT_CONNECTED = (B_MEDIA_ERROR_BASE + 120); + enum B_MEDIA_BAD_CLIP_FORMAT = (B_MEDIA_ERROR_BASE + 121); + enum B_MEDIA_ADDON_FAILED = (B_MEDIA_ERROR_BASE + 122); + enum B_MEDIA_ADDON_DISABLED = (B_MEDIA_ERROR_BASE + 123); + enum B_MEDIA_CHANGE_IN_PROGRESS = (B_MEDIA_ERROR_BASE + 124); + enum B_MEDIA_STALE_CHANGE_COUNT = (B_MEDIA_ERROR_BASE + 125); + enum B_MEDIA_ADDON_RESTRICTED = (B_MEDIA_ERROR_BASE + 126); + enum B_MEDIA_NO_HANDLER = (B_MEDIA_ERROR_BASE + 127); + enum B_MEDIA_DUPLICATE_FORMAT = (B_MEDIA_ERROR_BASE + 128); + enum B_MEDIA_REALTIME_DISABLED = (B_MEDIA_ERROR_BASE + 129); + enum B_MEDIA_REALTIME_UNAVAILABLE = (B_MEDIA_ERROR_BASE + 130); + + /* Mail Kit Errors */ + enum B_MAIL_NO_DAEMON = (B_MAIL_ERROR_BASE + 0); + enum B_MAIL_UNKNOWN_USER = (B_MAIL_ERROR_BASE + 1); + enum B_MAIL_WRONG_PASSWORD = (B_MAIL_ERROR_BASE + 2); + enum B_MAIL_UNKNOWN_HOST = (B_MAIL_ERROR_BASE + 3); + enum B_MAIL_ACCESS_ERROR = (B_MAIL_ERROR_BASE + 4); + enum B_MAIL_UNKNOWN_FIELD = (B_MAIL_ERROR_BASE + 5); + enum B_MAIL_NO_RECIPIENT = (B_MAIL_ERROR_BASE + 6); + enum B_MAIL_INVALID_MAIL = (B_MAIL_ERROR_BASE + 7); + + /* Printing Errors */ + enum B_NO_PRINT_SERVER = (B_PRINT_ERROR_BASE + 0); + + /* Device Kit Errors */ + enum B_DEV_INVALID_IOCTL = (B_DEVICE_ERROR_BASE + 0); + enum B_DEV_NO_MEMORY = (B_DEVICE_ERROR_BASE + 1); + enum B_DEV_BAD_DRIVE_NUM = (B_DEVICE_ERROR_BASE + 2); + enum B_DEV_NO_MEDIA = (B_DEVICE_ERROR_BASE + 3); + enum B_DEV_UNREADABLE = (B_DEVICE_ERROR_BASE + 4); + enum B_DEV_FORMAT_ERROR = (B_DEVICE_ERROR_BASE + 5); + enum B_DEV_TIMEOUT = (B_DEVICE_ERROR_BASE + 6); + enum B_DEV_RECALIBRATE_ERROR = (B_DEVICE_ERROR_BASE + 7); + enum B_DEV_SEEK_ERROR = (B_DEVICE_ERROR_BASE + 8); + enum B_DEV_ID_ERROR = (B_DEVICE_ERROR_BASE + 9); + enum B_DEV_READ_ERROR = (B_DEVICE_ERROR_BASE + 10); + enum B_DEV_WRITE_ERROR = (B_DEVICE_ERROR_BASE + 11); + enum B_DEV_NOT_READY = (B_DEVICE_ERROR_BASE + 12); + enum B_DEV_MEDIA_CHANGED = (B_DEVICE_ERROR_BASE + 13); + enum B_DEV_MEDIA_CHANGE_REQUESTED = (B_DEVICE_ERROR_BASE + 14); + enum B_DEV_RESOURCE_CONFLICT = (B_DEVICE_ERROR_BASE + 15); + enum B_DEV_CONFIGURATION_ERROR = (B_DEVICE_ERROR_BASE + 16); + enum B_DEV_DISABLED_BY_USER = (B_DEVICE_ERROR_BASE + 17); + enum B_DEV_DOOR_OPEN = (B_DEVICE_ERROR_BASE + 18); + + enum B_DEV_INVALID_PIPE = (B_DEVICE_ERROR_BASE + 19); + enum B_DEV_CRC_ERROR = (B_DEVICE_ERROR_BASE + 20); + enum B_DEV_STALLED = (B_DEVICE_ERROR_BASE + 21); + enum B_DEV_BAD_PID = (B_DEVICE_ERROR_BASE + 22); + enum B_DEV_UNEXPECTED_PID = (B_DEVICE_ERROR_BASE + 23); + enum B_DEV_DATA_OVERRUN = (B_DEVICE_ERROR_BASE + 24); + enum B_DEV_DATA_UNDERRUN = (B_DEVICE_ERROR_BASE + 25); + enum B_DEV_FIFO_OVERRUN = (B_DEVICE_ERROR_BASE + 26); + enum B_DEV_FIFO_UNDERRUN = (B_DEVICE_ERROR_BASE + 27); + enum B_DEV_PENDING = (B_DEVICE_ERROR_BASE + 28); + enum B_DEV_MULTIPLE_ERRORS = (B_DEVICE_ERROR_BASE + 29); + enum B_DEV_TOO_LATE = (B_DEVICE_ERROR_BASE + 30); + + /* Translation Kit Errors */ + enum B_TRANSLATION_BASE_ERROR = (B_TRANSLATION_ERROR_BASE + 0); + enum B_NO_TRANSLATOR = (B_TRANSLATION_ERROR_BASE + 1); + enum B_ILLEGAL_DATA = (B_TRANSLATION_ERROR_BASE + 2); +} +else version (WASI) +{ + enum EPERM = 1; + enum ENOENT = 2; + enum ESRCH = 3; + enum EINTR = 4; + enum EIO = 5; + enum ENXIO = 6; + enum E2BIG = 7; + enum ENOEXEC = 8; + enum EBADF = 9; + enum ECHILD = 10; + enum EAGAIN = 11; + enum ENOMEM = 12; + enum EACCES = 13; + enum EFAULT = 14; + enum ENOTBLK = 15; + enum EBUSY = 16; + enum EEXIST = 17; + enum EXDEV = 18; + enum ENODEV = 19; + enum ENOTDIR = 20; + enum EISDIR = 21; + enum EINVAL = 22; + enum ENFILE = 23; + enum EMFILE = 24; + enum ENOTTY = 25; + enum ETXTBSY = 26; + enum EFBIG = 27; + enum ENOSPC = 28; + enum ESPIPE = 29; + enum EROFS = 30; + enum EMLINK = 31; + enum EPIPE = 32; + enum EDOM = 33; + enum ERANGE = 34; + enum EDEADLK = 35; + enum ENAMETOOLONG = 36; + enum ENOLCK = 37; + enum ENOSYS = 38; + enum ENOTEMPTY = 39; + enum ELOOP = 40; + enum EWOULDBLOCK = EAGAIN; + enum ENOMSG = 42; + enum EIDRM = 43; + enum ECHRNG = 44; + enum EL2NSYNC = 45; + enum EL3HLT = 46; + enum EL3RST = 47; + enum ELNRNG = 48; + enum EUNATCH = 49; + enum ENOCSI = 50; + enum EL2HLT = 51; + enum EBADE = 52; + enum EBADR = 53; + enum EXFULL = 54; + enum ENOANO = 55; + enum EBADRQC = 56; + enum EBADSLT = 57; + enum EDEADLOCK = EDEADLK; + enum EBFONT = 59; + enum ENOSTR = 60; + enum ENODATA = 61; + enum ETIME = 62; + enum ENOSR = 63; + enum ENONET = 64; + enum ENOPKG = 65; + enum EREMOTE = 66; + enum ENOLINK = 67; + enum EADV = 68; + enum ESRMNT = 69; + enum ECOMM = 70; + enum EPROTO = 71; + enum EMULTIHOP = 72; + enum EDOTDOT = 73; + enum EBADMSG = 74; + enum EOVERFLOW = 75; + enum ENOTUNIQ = 76; + enum EBADFD = 77; + enum EREMCHG = 78; + enum ELIBACC = 79; + enum ELIBBAD = 80; + enum ELIBSCN = 81; + enum ELIBMAX = 82; + enum ELIBEXEC = 83; + enum EILSEQ = 84; + enum ERESTART = 85; + enum ESTRPIPE = 86; + enum EUSERS = 87; + enum ENOTSOCK = 88; + enum EDESTADDRREQ = 89; + enum EMSGSIZE = 90; + enum EPROTOTYPE = 91; + enum ENOPROTOOPT = 92; + enum EPROTONOSUPPORT = 93; + enum ESOCKTNOSUPPORT = 94; + enum EOPNOTSUPP = 95; + enum ENOTSUP = EOPNOTSUPP; + enum EPFNOSUPPORT = 96; + enum EAFNOSUPPORT = 97; + enum EADDRINUSE = 98; + enum EADDRNOTAVAIL = 99; + enum ENETDOWN = 100; + enum ENETUNREACH = 101; + enum ENETRESET = 102; + enum ECONNABORTED = 103; + enum ECONNRESET = 104; + enum ENOBUFS = 105; + enum EISCONN = 106; + enum ENOTCONN = 107; + enum ESHUTDOWN = 108; + enum ETOOMANYREFS = 109; + enum ETIMEDOUT = 110; + enum ECONNREFUSED = 111; + enum EHOSTDOWN = 112; + enum EHOSTUNREACH = 113; + enum EALREADY = 114; + enum EINPROGRESS = 115; + enum ESTALE = 116; + enum EUCLEAN = 117; + enum ENOTNAM = 118; + enum ENAVAIL = 119; + enum EISNAM = 120; + enum EREMOTEIO = 121; + enum EDQUOT = 122; + enum ENOMEDIUM = 123; + enum EMEDIUMTYPE = 124; + enum ECANCELED = 125; + enum ENOKEY = 126; + enum EKEYEXPIRED = 127; + enum EKEYREVOKED = 128; + enum EKEYREJECTED = 129; + enum EOWNERDEAD = 130; + enum ENOTRECOVERABLE = 131; + enum ERFKILL = 132; + enum EHWPOISON = 133; +} +else version (FreeStanding) +{ + // no errno on bare metal +} +else +{ + static assert(false, "Unsupported platform"); +} diff --git a/src/urt/internal/stdc/stdio.d b/src/urt/internal/stdc/stdio.d new file mode 100644 index 0000000..5442939 --- /dev/null +++ b/src/urt/internal/stdc/stdio.d @@ -0,0 +1,111 @@ +// Minimal C stdio bindings — only what URT actually uses. +// FILE is opaque; we never dereference its fields. + +module urt.internal.stdc.stdio; + +struct FILE; + +extern(C) nothrow @nogc: + +size_t fread(scope void* ptr, size_t size, size_t nmemb, FILE* stream); +size_t fwrite(scope const void* ptr, size_t size, size_t nmemb, FILE* stream); +int fgetc(FILE* stream); +char* fgets(char* s, int n, FILE* stream); +int feof(FILE* stream); +int ferror(FILE* stream); +int fflush(FILE* stream); + +version (CRuntime_Microsoft) +{ + FILE* __acrt_iob_func(int hnd); + + FILE* stdin()() { return __acrt_iob_func(0); } + FILE* stdout()() { return __acrt_iob_func(1); } + FILE* stderr()() { return __acrt_iob_func(2); } +} +else version (CRuntime_Glibc) +{ + extern __gshared FILE* stdin; + extern __gshared FILE* stdout; + extern __gshared FILE* stderr; +} +else version (Darwin) +{ + private extern __gshared FILE* __stdinp; + private extern __gshared FILE* __stdoutp; + private extern __gshared FILE* __stderrp; + + alias stdin = __stdinp; + alias stdout = __stdoutp; + alias stderr = __stderrp; +} +else version (FreeBSD) +{ + private extern __gshared FILE* __stdinp; + private extern __gshared FILE* __stdoutp; + private extern __gshared FILE* __stderrp; + + alias stdin = __stdinp; + alias stdout = __stdoutp; + alias stderr = __stderrp; +} +else version (DragonFlyBSD) +{ + private extern __gshared FILE* __stdinp; + private extern __gshared FILE* __stdoutp; + private extern __gshared FILE* __stderrp; + + alias stdin = __stdinp; + alias stdout = __stdoutp; + alias stderr = __stderrp; +} +else version (CRuntime_Musl) +{ + extern __gshared FILE* stdin; + extern __gshared FILE* stdout; + extern __gshared FILE* stderr; +} +else version (CRuntime_UClibc) +{ + extern __gshared FILE* stdin; + extern __gshared FILE* stdout; + extern __gshared FILE* stderr; +} +else version (CRuntime_Bionic) +{ + private extern __gshared FILE[3] __sF; + + @property FILE* stdin()() { return &__sF[0]; } + @property FILE* stdout()() { return &__sF[1]; } + @property FILE* stderr()() { return &__sF[2]; } +} +else version (CRuntime_Newlib) +{ + private struct _reent + { + int _errno; + FILE* _stdin; + FILE* _stdout; + FILE* _stderr; + } + + private extern(C) _reent* __getreent(); + + @property FILE* stdin()() { return __getreent()._stdin; } + @property FILE* stdout()() { return __getreent()._stdout; } + @property FILE* stderr()() { return __getreent()._stderr; } +} +else version (WASI) +{ + extern __gshared FILE* stdin; + extern __gshared FILE* stdout; + extern __gshared FILE* stderr; +} +else version (FreeStanding) +{ + // no stdio streams — io.d uses UART directly +} +else +{ + static assert(false, "Unsupported platform"); +} diff --git a/src/urt/internal/stdc/stdlib.d b/src/urt/internal/stdc/stdlib.d new file mode 100644 index 0000000..7bc03c9 --- /dev/null +++ b/src/urt/internal/stdc/stdlib.d @@ -0,0 +1,12 @@ +// Minimal C stdlib bindings — only what URT actually uses. + +module urt.internal.stdc.stdlib; + +extern (C) nothrow @nogc: + +noreturn abort() @safe; +noreturn exit(int status); + +pure char* gcvt(double value, int ndigit, char* buf); +version (Windows) + pure int _gcvt_s(const char* buffer, size_t size_in_bytes, double value, int digits); diff --git a/src/urt/internal/sys/posix/package.d b/src/urt/internal/sys/posix/package.d new file mode 100644 index 0000000..e12cda2 --- /dev/null +++ b/src/urt/internal/sys/posix/package.d @@ -0,0 +1,335 @@ +// Minimal POSIX bindings — only what URT actually uses. +// Replaces imports of core.sys.posix.* to avoid druntime's transitive +// core.stdc.stdio dependency (stdint → wchar_ → stdio). + +module urt.internal.sys.posix; + +version (Posix): +extern(C) nothrow @nogc: + +// ── types ── + +alias off_t = long; +alias mode_t = uint; +alias ssize_t = ptrdiff_t; +alias time_t = long; +alias clockid_t = int; +alias blkcnt_t = long; +alias dev_t = ulong; +alias ino_t = ulong; +alias uid_t = uint; +alias gid_t = uint; + +version (X86) +{ + alias blksize_t = int; + alias nlink_t = uint; +} +else version (X86_64) +{ + alias blksize_t = long; + alias nlink_t = ulong; +} +else version (AArch64) +{ + alias blksize_t = int; + alias nlink_t = uint; +} +else version (ARM) +{ + alias blksize_t = int; + alias nlink_t = uint; +} +else version (RISCV32) +{ + alias blksize_t = int; + alias nlink_t = uint; +} +else version (RISCV64) +{ + alias blksize_t = int; + alias nlink_t = uint; +} +else + static assert(false, "POSIX type aliases not defined for this arch"); + +// ── fcntl ── + +enum O_RDONLY = 0x0; +enum O_WRONLY = 0x1; +enum O_RDWR = 0x2; +enum O_CREAT = 0x40; +enum O_TRUNC = 0x200; +enum O_APPEND = 0x400; +enum O_NOCTTY = 0x100; +enum O_NDELAY = 0x800; // same as O_NONBLOCK +enum O_CLOEXEC = 0x80000; + +version (linux) + enum O_DIRECT = 0x4000; + +version (X86) +{ + // glibc x86: largefile64 redirects + int open64(scope const char* pathname, int flags, ...); + alias open = open64; +} +else + int open(scope const char* pathname, int flags, ...); +int fcntl(int fd, int cmd, ...); + +version (Darwin) +{ + enum F_NOCACHE = 48; + enum F_GETPATH = 50; +} + +enum F_GETFL = 3; +enum F_SETFL = 4; +enum O_NONBLOCK = 0x800; + +// ── unistd ── + +int close(int fd); +int unlink(scope const char* pathname); +ssize_t read(int fd, void* buf, size_t count); +ssize_t write(int fd, const void* buf, size_t count); +version (X86) +{ + off_t lseek64(int fd, off_t offset, int whence); + alias lseek = lseek64; + int ftruncate64(int fd, off_t length); + alias ftruncate = ftruncate64; +} +else +{ + off_t lseek(int fd, off_t offset, int whence); + int ftruncate(int fd, off_t length); +} +ssize_t readlink(scope const char* path, char* buf, size_t bufsiz); +version (X86) +{ + ssize_t pread64(int fd, void* buf, size_t count, off_t offset); + alias pread = pread64; + ssize_t pwrite64(int fd, const void* buf, size_t count, off_t offset); + alias pwrite = pwrite64; +} +else +{ + ssize_t pread(int fd, void* buf, size_t count, off_t offset); + ssize_t pwrite(int fd, const void* buf, size_t count, off_t offset); +} +int fsync(int fd); +int usleep(uint usec); +long sysconf(int name); +int gethostname(char* name, size_t len); + +enum _SC_PAGE_SIZE = 30; +enum _SC_PHYS_PAGES = 85; + +enum STDIN_FILENO = 0; +enum STDOUT_FILENO = 1; +enum STDERR_FILENO = 2; +int isatty(int fd); + +// ── sys/stat ── + +version (X86_64) +{ + // x86_64 is unique: nlink before mode, long blksize/blocks, long[3] tail + struct stat_t + { + dev_t st_dev; + ino_t st_ino; + nlink_t st_nlink; + mode_t st_mode; + uid_t st_uid; + gid_t st_gid; + uint __pad0; + dev_t st_rdev; + off_t st_size; + long st_blksize; + long st_blocks; + timespec st_atim; + timespec st_mtim; + timespec st_ctim; + long[3] __unused; + } + static assert(stat_t.sizeof == 144); +} +else version (X86) version = OldStatLayout; +else version (ARM) version = OldStatLayout; +else version (AArch64) version = NewStatLayout; +else version (RISCV32) version = NewStatLayout; +else version (RISCV64) version = NewStatLayout; +else + static assert(false, "stat_t not defined for this arch"); + +version (NewStatLayout) +{ + // AArch64, RISCV32, RISCV64: new kernel stat layout + struct stat_t + { + dev_t st_dev; + ino_t st_ino; + mode_t st_mode; + nlink_t st_nlink; + uid_t st_uid; + gid_t st_gid; + dev_t st_rdev; + ulong __pad1; + off_t st_size; + blksize_t st_blksize; + int __pad2; + blkcnt_t st_blocks; + timespec st_atim; + timespec st_mtim; + timespec st_ctim; + int[2] __unused; + } + static assert(stat_t.sizeof == 128); +} + +version (OldStatLayout) +{ + // X86, ARM: old glibc stat layout with __USE_FILE_OFFSET64 + private struct __timespec32 { int tv_sec; int tv_nsec; } + struct stat_t + { + dev_t st_dev; + ushort __pad1; + uint __st_ino; + mode_t st_mode; + nlink_t st_nlink; + uid_t st_uid; + gid_t st_gid; + dev_t st_rdev; + ushort __pad2; + off_t st_size; + blksize_t st_blksize; + blkcnt_t st_blocks; + version (CRuntime_Musl) + { + __timespec32 __st_atim32; + __timespec32 __st_mtim32; + __timespec32 __st_ctim32; + } + ino_t st_ino; + timespec st_atim; + timespec st_mtim; + timespec st_ctim; + } +} + +bool S_ISREG(mode_t mode) { return (mode & 0xF000) == 0x8000; } + +version (X86) +{ + int stat64(scope const char* pathname, stat_t* buf); + alias stat = stat64; + int fstat64(int fd, stat_t* buf); + alias fstat = fstat64; + int mkstemp64(char* tmpl); + alias mkstemp = mkstemp64; +} +else +{ + int stat(scope const char* pathname, stat_t* buf); + int fstat(int fd, stat_t* buf); + int mkstemp(char* tmpl); +} +int mkdir(scope const char* pathname, mode_t mode); +pure int posix_memalign(void** memptr, size_t alignment, size_t size); + +// ── time ── + +struct timespec +{ + time_t tv_sec; + long tv_nsec; +} + +struct tm +{ + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; + long tm_gmtoff; + const(char)* tm_zone; +} + +enum CLOCK_REALTIME = 0; +enum CLOCK_MONOTONIC = 1; + +int clock_gettime(clockid_t clk_id, timespec* tp); +int clock_settime(clockid_t clk_id, const timespec* tp); +tm* gmtime_r(scope const time_t* timep, tm* result); +time_t mktime(tm* tp); + +// ── sys/mman ── + +enum PROT_READ = 0x1; +enum MAP_PRIVATE = 0x02; +enum MAP_FAILED = cast(void*)-1; + +void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset); +int munmap(void* addr, size_t length); + +// ── dlfcn ── + +struct Dl_info +{ + const(char)* dli_fname; + void* dli_fbase; + const(char)* dli_sname; + void* dli_saddr; +} + +int dladdr(scope const void* addr, Dl_info* info); + +// ── netinet/in ── +// in6_addr and sockaddr_in6 are provided by ImportC (urt.internal.os) +// on platforms that use it. Defined here as fallback for others. + +version (none) +{ + struct in6_addr + { + ubyte[16] s6_addr; + } + + struct sockaddr_in6 + { + ushort sin6_family; + ushort sin6_port; + uint sin6_flowinfo; + in6_addr sin6_addr; + uint sin6_scope_id; + } +} + +// ── poll ── + +struct pollfd +{ + int fd; + short events; + short revents; +} + +enum POLLIN = 0x001; +enum POLLPRI = 0x002; +enum POLLOUT = 0x004; +enum POLLERR = 0x008; +enum POLLHUP = 0x010; +enum POLLNVAL = 0x020; +enum POLLRDNORM = 0x040; +enum POLLWRNORM = 0x100; + +int poll(pollfd* fds, size_t nfds, int timeout); diff --git a/src/urt/internal/sys/posix/termios.d b/src/urt/internal/sys/posix/termios.d new file mode 100644 index 0000000..c322114 --- /dev/null +++ b/src/urt/internal/sys/posix/termios.d @@ -0,0 +1,107 @@ +// Minimal termios bindings for Linux. + +module urt.internal.sys.posix.termios; + +version (linux): +extern(C) nothrow @nogc: + +alias cc_t = ubyte; +alias speed_t = uint; +alias tcflag_t = uint; + +enum NCCS = 32; + +struct termios +{ + tcflag_t c_iflag; + tcflag_t c_oflag; + tcflag_t c_lflag; + tcflag_t c_cflag; + cc_t c_line; + cc_t[NCCS] c_cc; + speed_t c_ispeed; + speed_t c_ospeed; +} + +// c_iflag +enum IGNBRK = 0x01; +enum BRKINT = 0x02; +enum PARMRK = 0x08; +enum ISTRIP = 0x20; +enum INLCR = 0x40; +enum IGNCR = 0x80; +enum ICRNL = 0x100; +enum IXON = 0x400; +enum IXOFF = 0x1000; +enum IXANY = 0x800; + +// c_oflag +enum OPOST = 0x01; +enum ONLCR = 0x04; + +// c_cflag +enum CSIZE = 0x30; +enum CS5 = 0x00; +enum CS6 = 0x10; +enum CS7 = 0x20; +enum CS8 = 0x30; +enum CSTOPB = 0x40; +enum CREAD = 0x80; +enum PARENB = 0x100; +enum PARODD = 0x200; +enum CLOCAL = 0x800; +enum CRTSCTS = 0x80000000; + +// c_lflag +enum ISIG = 0x01; +enum ICANON = 0x02; +enum ECHO = 0x08; +enum ECHOE = 0x10; +enum ECHONL = 0x40; +enum IEXTEN = 0x8000; + +// c_cc indices +enum VMIN = 6; +enum VTIME = 5; + +// tcsetattr actions +enum TCSANOW = 0; +enum TCSAFLUSH = 2; + +// tcflush queue selectors +enum TCIFLUSH = 0; +enum TCOFLUSH = 1; +enum TCIOFLUSH = 2; + +// baud rates +enum B0 = 0x0; +enum B50 = 0x1; +enum B75 = 0x2; +enum B110 = 0x3; +enum B134 = 0x4; +enum B150 = 0x5; +enum B200 = 0x6; +enum B300 = 0x7; +enum B600 = 0x8; +enum B1200 = 0x9; +enum B1800 = 0xA; +enum B2400 = 0xB; +enum B4800 = 0xC; +enum B9600 = 0xD; +enum B19200 = 0xE; +enum B38400 = 0xF; +enum B57600 = 0x1001; +enum B115200 = 0x1002; +enum B230400 = 0x1003; +enum B460800 = 0x1004; +enum B500000 = 0x1005; +enum B576000 = 0x1006; +enum B921600 = 0x1007; + +int tcgetattr(int fd, termios* t); +int tcsetattr(int fd, int action, const termios* t); +int tcflush(int fd, int queue); +speed_t cfgetispeed(const termios* t); +speed_t cfgetospeed(const termios* t); +int cfsetispeed(termios* t, speed_t speed); +int cfsetospeed(termios* t, speed_t speed); diff --git a/src/urt/io.d b/src/urt/io.d index 84cc844..dc8b3a3 100644 --- a/src/urt/io.d +++ b/src/urt/io.d @@ -116,4 +116,6 @@ unittest private: -import core.stdc.stdio : stdout, stderr, fwrite, fflush; +version (FreeStanding) {} +else + import urt.internal.stdc.stdio : stdout, stderr, fwrite, fflush; diff --git a/src/urt/mem/alloc.d b/src/urt/mem/alloc.d index 4827bb8..955e8a4 100644 --- a/src/urt/mem/alloc.d +++ b/src/urt/mem/alloc.d @@ -40,7 +40,7 @@ void[] alloc_aligned(size_t size, size_t alignment) pure } else version (Posix) { - import core.sys.posix.stdlib; + import urt.internal.sys.posix; void* mem; return posix_memalign(&mem, alignment, size) ? null : mem[0 .. size]; } diff --git a/src/urt/package.d b/src/urt/package.d index 5989f46..d0f2037 100644 --- a/src/urt/package.d +++ b/src/urt/package.d @@ -62,7 +62,7 @@ extern(C) int main(int argc, char** argv) nothrow @nogc @trusted version (unittest) { - import urt.internal.stdc : exit; + import urt.internal.stdc.stdlib : exit; size_t executed, passed; foreach (m; modules) diff --git a/src/urt/result.d b/src/urt/result.d index aa69c10..d18c37b 100644 --- a/src/urt/result.d +++ b/src/urt/result.d @@ -93,14 +93,7 @@ version (Windows) } else version (Posix) { - import urt.internal.stdc; - - extern (C) private int* __errno_location() nothrow @nogc; - - @property int errno() nothrow @nogc @trusted - { - return *__errno_location(); - } + import urt.internal.stdc.errno; enum InternalResult : Result { diff --git a/src/urt/socket.d b/src/urt/socket.d index 89c847a..8560ee9 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -27,15 +27,11 @@ version (Windows) else version (Posix) { import urt.internal.os; // use ImportC to import system C headers... - import core.sys.posix.fcntl; - import core.sys.posix.poll; - import core.sys.posix.unistd : close, gethostname; - import core.sys.posix.netinet.in_ : in6_addr, sockaddr_in6; alias _bind = urt.internal.os.bind, _listen = urt.internal.os.listen, _connect = urt.internal.os.connect, _accept = urt.internal.os.accept, _send = urt.internal.os.send, _sendto = urt.internal.os.sendto, _sendmsg = urt.internal.os.sendmsg, - _recv = urt.internal.os.recv, _recvfrom = urt.internal.os.recvfrom, _shutdown = urt.internal.os.shutdown; - alias _poll = core.sys.posix.poll.poll; + _recv = urt.internal.os.recv, _recvfrom = urt.internal.os.recvfrom, _shutdown = urt.internal.os.shutdown, + _close = urt.internal.os.close, _poll = urt.internal.os.poll; version = HasUnixSocket; version = HasIPv6; @@ -288,16 +284,12 @@ Result close(Socket socket) int result; version (Windows) result = closesocket(socket.handle); + else version (Posix) + result = _close(socket.handle); else - result = close(socket.handle); + assert(false, "Not implemented!"); if (result < 0) return socket_getlasterror(); - -// { -// LockGuard lock(s_noSignalMut); -// s_noSignal.Erase(socket); -// } - return Result.success; } @@ -1296,7 +1288,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ version (Windows) storeBigEndian(&ain6.sin6_addr.in6_u.u6_addr16[a], address._a.ipv6.addr.s[a]); else version (Posix) - storeBigEndian(cast(ushort*)ain6.sin6_addr.s6_addr + a, address._a.ipv6.addr.s[a]); + storeBigEndian(&ain6.sin6_addr.__in6_u.__u6_addr16[a], address._a.ipv6.addr.s[a]); else assert(false, "Not implemented!"); } @@ -1412,7 +1404,7 @@ IPv6Addr make_IPv6Addr(ref const in6_addr in6) version (Windows) addr.s[a] = loadBigEndian(&in6.in6_u.u6_addr16[a]); else version (Posix) - addr.s[a] = loadBigEndian(cast(const(ushort)*)in6.s6_addr + a); + addr.s[a] = loadBigEndian(&in6.__in6_u.__u6_addr16[a]); else assert(false, "Not implemented!"); } diff --git a/src/urt/system.d b/src/urt/system.d index aee7291..d953784 100644 --- a/src/urt/system.d +++ b/src/urt/system.d @@ -104,7 +104,7 @@ SystemInfo get_sysinfo() } else version (Posix) { - import core.sys.posix.unistd; + import urt.internal.sys.posix; int pages = sysconf(_SC_PHYS_PAGES); int avail = sysconf(_SC_AVPHYS_PAGES); diff --git a/src/urt/time.d b/src/urt/time.d index b0107fb..fe1e11c 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -10,7 +10,7 @@ version (Windows) } else version (Posix) { - import core.sys.posix.time; + import urt.internal.sys.posix; } else version (BL808) { From d6536a0d5e28cb612b5e7a29f45d41094924b38d Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 3 Apr 2026 23:24:54 +1000 Subject: [PATCH 114/138] Got ESP32-S3 running! --- src/core/atomic.d | 15 + src/sys/bl808/syscalls.d | 3 - src/sys/bl808/uart.d | 60 ++-- src/sys/esp32/main.c | 19 ++ src/sys/esp32/ow_shim.c | 518 ++++++++++++++++++++++++++++++++++ src/urt/exception.d | 13 +- src/urt/fibre.d | 97 ++++++- src/urt/file.d | 54 ++-- src/urt/internal/stdc/errno.d | 22 +- src/urt/io.d | 23 +- src/urt/mem/alloc.d | 19 +- src/urt/result.d | 19 +- src/urt/socket.d | 200 +++++++++---- src/urt/system.d | 19 ++ src/urt/time.d | 31 ++ 15 files changed, 966 insertions(+), 146 deletions(-) create mode 100644 src/core/atomic.d create mode 100644 src/sys/esp32/main.c create mode 100644 src/sys/esp32/ow_shim.c diff --git a/src/core/atomic.d b/src/core/atomic.d new file mode 100644 index 0000000..5d5eb04 --- /dev/null +++ b/src/core/atomic.d @@ -0,0 +1,15 @@ +// Shadows LDC's core.atomic with non-intrinsic implementations. +// LDC's version uses LLVM atomic intrinsics (llvm_atomic_rmw_add etc.) which +// require hardware atomic support. Targets without native atomics (e.g. Xtensa) +// crash in LLVM instruction selection. This module provides plain load/store +// equivalents that are correct for single-core / cooperative-multitasking targets. +// +// Only included when uRT's import path (-I) precedes LDC's (post-switches). + +module core.atomic; + +nothrow @nogc @safe: + +public import urt.atomic : MemoryOrder, cas, + atomicLoad, atomicStore, atomicOp, atomicExchange, + atomicFetchAdd, atomicFetchSub, TailShared; diff --git a/src/sys/bl808/syscalls.d b/src/sys/bl808/syscalls.d index 003893d..c69ab3b 100644 --- a/src/sys/bl808/syscalls.d +++ b/src/sys/bl808/syscalls.d @@ -13,8 +13,6 @@ extern(C) extern __gshared { pragma(mangle, "__heap_end") void* _heap_end_ptr; } -__gshared int errno_val = 0; - public: // ================================================================ @@ -56,7 +54,6 @@ extern(C) int _getpid() @nogc nothrow { return 1; } // POSIX networking stubs (replaced by XRAM WiFi IPC when ready) // ================================================================ -extern(C) int* __errno_location() @nogc nothrow { return &errno_val; } extern(C) int socket(int domain, int type, int protocol) @nogc nothrow { return -1; } extern(C) int close(int fd) @nogc nothrow { return -1; } extern(C) int poll(void* fds, uint nfds, int timeout) @nogc nothrow { return -1; } diff --git a/src/sys/bl808/uart.d b/src/sys/bl808/uart.d index f0f0e1b..9d39f84 100644 --- a/src/sys/bl808/uart.d +++ b/src/sys/bl808/uart.d @@ -31,9 +31,9 @@ nothrow @nogc: // Register definitions // ══════════════════════════════════════════════════════════════════════════════ -enum UartId : uint { uart0 = 0, uart1 = 1, uart2 = 2, uart3 = 3 } +enum NUM_UARTS = 4; -private immutable uint[4] uart_base = [ +private immutable uint[NUM_UARTS] uart_base = [ 0x2000_A000, // UART0 0x2000_A100, // UART1 0x2000_AA00, // UART2 (shared with ISO11898 CAN) @@ -173,18 +173,18 @@ struct UartConfig } // Per-UART state -private __gshared Ring[4] rx_ring; -private __gshared Ring[4] tx_ring; -private __gshared bool[4] uart_open_flag; +private __gshared Ring[NUM_UARTS] rx_ring; +private __gshared Ring[NUM_UARTS] tx_ring; +private __gshared bool[NUM_UARTS] uart_open_flag; private __gshared IrqHandler prev_irq_handler; private __gshared bool irq_handler_installed; // Open a UART: configure baud rate, frame format, clear FIFOs, enable TX+RX. // UART3 gets interrupt-driven I/O. UART0/1/2 require uart_poll(). // Returns false if id is out of range. -bool uart_open(UartId id, UartConfig cfg) +bool uart_open(uint id, UartConfig cfg) { - if (id > UartId.max) + if (id >= NUM_UARTS) return false; immutable base = uart_base[id]; @@ -253,7 +253,7 @@ bool uart_open(UartId id, UartConfig cfg) uart_open_flag[id] = true; // UART3: set up interrupt-driven I/O - if (id == UartId.uart3) + if (id == 3) { // Install our IRQ handler (chain with previous) if (!irq_handler_installed) @@ -279,9 +279,9 @@ bool uart_open(UartId id, UartConfig cfg) } // Disable TX and RX, mask interrupts. -void uart_close(UartId id) +void uart_close(uint id) { - if (id > UartId.max) + if (id >= NUM_UARTS) return; immutable base = uart_base[id]; @@ -289,7 +289,7 @@ void uart_close(UartId id) // Mask all interrupts reg_write(base, INT_MASK, INT_MASK_ALL); - if (id == UartId.uart3) + if (id == 3) disable_irq(UART3_PLIC_IRQ); auto tx_cfg = reg_read(base, UTX_CONFIG); @@ -304,18 +304,18 @@ void uart_close(UartId id) // Poll hardware FIFOs and transfer to/from ring buffers. // Required for UART0/1/2 (no D0 interrupt). Harmless for UART3. -void uart_poll(UartId id) +void uart_poll(uint id) { - if (id > UartId.max || !uart_open_flag[id]) + if (id >= NUM_UARTS || !uart_open_flag[id]) return; drain_rx_fifo(id); fill_tx_fifo(id); } // Non-blocking read: pull from RX ring buffer, return bytes read. -ptrdiff_t uart_read(UartId id, void[] buffer) +ptrdiff_t uart_read(uint id, void[] buffer) { - if (id > UartId.max) + if (id >= NUM_UARTS) return -1; immutable prev = disable_interrupts(); @@ -326,9 +326,9 @@ ptrdiff_t uart_read(UartId id, void[] buffer) // Non-blocking write: push into TX ring buffer, kick TX if needed. // Returns bytes accepted (may be less than data.length if ring is full). -ptrdiff_t uart_write(UartId id, const(void)[] data) +ptrdiff_t uart_write(uint id, const(void)[] data) { - if (id > UartId.max) + if (id >= NUM_UARTS) return -1; immutable prev = disable_interrupts(); @@ -339,7 +339,7 @@ ptrdiff_t uart_write(UartId id, const(void)[] data) fill_tx_fifo(id); // For UART3, unmask TX FIFO interrupt so ISR continues draining - if (id == UartId.uart3 && !tx_ring[id].empty) + if (id == 3 && !tx_ring[id].empty) { auto mask = reg_read(uart_base[id], INT_MASK); mask &= ~INT_UTX_FIFO; @@ -351,9 +351,9 @@ ptrdiff_t uart_write(UartId id, const(void)[] data) } // Return number of bytes available to read from RX ring. -ptrdiff_t uart_rx_pending(UartId id) +ptrdiff_t uart_rx_pending(uint id) { - if (id > UartId.max) + if (id >= NUM_UARTS) return -1; immutable prev = disable_interrupts(); @@ -363,9 +363,9 @@ ptrdiff_t uart_rx_pending(UartId id) } // Clear RX ring buffer and hardware FIFO. Returns bytes discarded. -ptrdiff_t uart_flush(UartId id) +ptrdiff_t uart_flush(uint id) { - if (id > UartId.max) + if (id >= NUM_UARTS) return -1; immutable prev = disable_interrupts(); @@ -384,9 +384,9 @@ ptrdiff_t uart_flush(UartId id) // Check and clear FIFO error flags (overflow/underflow). // Returns true if any error was detected. -bool uart_check_errors(UartId id) +bool uart_check_errors(uint id) { - if (id > UartId.max) + if (id >= NUM_UARTS) return true; immutable base = uart_base[id]; @@ -411,7 +411,7 @@ bool uart_check_errors(UartId id) private: // Drain hardware RX FIFO into software ring buffer. -void drain_rx_fifo(UartId id) +void drain_rx_fifo(uint id) { immutable base = uart_base[id]; ubyte[1] b = void; @@ -427,7 +427,7 @@ void drain_rx_fifo(UartId id) } // Fill hardware TX FIFO from software ring buffer. -void fill_tx_fifo(UartId id) +void fill_tx_fifo(uint id) { immutable base = uart_base[id]; ubyte[1] b = void; @@ -448,7 +448,7 @@ void uart_irq_handler(uint irq) { if (irq == UART3_PLIC_IRQ) { - immutable base = uart_base[UartId.uart3]; + immutable base = uart_base[3]; immutable sts = reg_read(base, INT_STS); immutable mask = reg_read(base, INT_MASK); immutable active = sts & ~mask; @@ -456,7 +456,7 @@ void uart_irq_handler(uint irq) // RX FIFO threshold or RX timeout — drain into ring if (active & (INT_URX_FIFO | INT_URX_RTO)) { - drain_rx_fifo(UartId.uart3); + drain_rx_fifo(3); if (active & INT_URX_RTO) reg_write(base, INT_CLEAR, INT_URX_RTO); } @@ -464,9 +464,9 @@ void uart_irq_handler(uint irq) // TX FIFO has space — refill from ring if (active & INT_UTX_FIFO) { - fill_tx_fifo(UartId.uart3); + fill_tx_fifo(3); // If ring is drained, mask TX interrupt until more data arrives - if (tx_ring[UartId.uart3].empty) + if (tx_ring[3].empty) { auto m = reg_read(base, INT_MASK); m |= INT_UTX_FIFO; diff --git a/src/sys/esp32/main.c b/src/sys/esp32/main.c new file mode 100644 index 0000000..7573964 --- /dev/null +++ b/src/sys/esp32/main.c @@ -0,0 +1,19 @@ +// OpenWatt ESP-IDF entry point. +// ESP-IDF calls app_main() after FreeRTOS scheduler starts. +// .init_array (D module constructors) are called automatically by +// ESP-IDF startup before app_main, so we just call D's main(). + +#include "esp_netif.h" +#include "esp_event.h" + +extern int main(int argc, char **argv); + +void app_main(void) +{ + // Initialize lwIP and event loop early -- D code may open sockets + // during startup before any WiFi/Ethernet interface is created. + esp_netif_init(); + esp_event_loop_create_default(); + + main(0, (char **)0); +} diff --git a/src/sys/esp32/ow_shim.c b/src/sys/esp32/ow_shim.c new file mode 100644 index 0000000..7445417 --- /dev/null +++ b/src/sys/esp32/ow_shim.c @@ -0,0 +1,518 @@ +// OpenWatt ESP-IDF shim -- the single C bridge between D and ESP-IDF. +// Wraps FreeRTOS macros, UART HAL inlines, and anything else that +// needs C headers or static-inline access. + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "hal/uart_hal.h" +#include "hal/uart_types.h" +#include "hal/uart_periph.h" +#include "esp_rom_gpio.h" +#include "lwip/netdb.h" + +// -- errno accessor (picolibc uses _Thread_local errno, incompatible with emulated-TLS) -- + +#include + +int *ow_errno_location(void) +{ + return &errno; +} + +// -- FreeRTOS task wrappers -- + +typedef void (*ow_task_func_t)(void *); + +int ow_task_create(ow_task_func_t func, const char *name, uint32_t stack_bytes, + void *param, uint32_t priority, void **out_handle) +{ + return xTaskCreate(func, name, stack_bytes, param, priority, + (TaskHandle_t *)out_handle) == pdPASS ? 1 : 0; +} + +void ow_task_delete(void *handle) +{ + vTaskDelete((TaskHandle_t)handle); +} + +void *ow_task_current(void) +{ + return (void *)xTaskGetCurrentTaskHandle(); +} + +void ow_task_notify_give(void *handle) +{ + xTaskNotifyGive((TaskHandle_t)handle); +} + +uint32_t ow_task_notify_take(uint32_t ticks_to_wait) +{ + return ulTaskNotifyTake(pdTRUE, ticks_to_wait); +} + +uint32_t ow_task_priority_get(void *handle) +{ + return uxTaskPriorityGet((TaskHandle_t)handle); +} + +// -- UART HAL wrappers -- + +#define NUM_UARTS 3 + +static uart_hal_context_t uart_ctx[NUM_UARTS]; +static bool uart_initialized[NUM_UARTS]; + +// D enums: StopBits { one=0, one_point_five=1, two=2 } +// Parity { none=0, even=1, odd=2, mark=3, space=4 } +// HAL enums: UART_STOP_BITS_1=1, _1_5=2, _2=3 +// UART_PARITY_DISABLE=0, _EVEN=2, _ODD=3 +static const uart_stop_bits_t stop_bits_map[] = { + UART_STOP_BITS_1, UART_STOP_BITS_1_5, UART_STOP_BITS_2 +}; +static const uart_parity_t parity_map[] = { + UART_PARITY_DISABLE, UART_PARITY_EVEN, UART_PARITY_ODD, + UART_PARITY_DISABLE, UART_PARITY_DISABLE +}; + +int ow_uart_open(int port, uint32_t baud_rate, uint8_t data_bits, + uint8_t stop_bits, uint8_t parity, + int8_t tx_gpio, int8_t rx_gpio) +{ + if (port < 0 || port >= NUM_UARTS) + return 0; + + uart_hal_init(&uart_ctx[port], port); + + uart_ll_set_sclk(uart_ctx[port].dev, UART_SCLK_APB); + uart_ll_set_baudrate(uart_ctx[port].dev, baud_rate, 80000000); + uart_ll_set_data_bit_num(uart_ctx[port].dev, data_bits - 5); + uart_ll_set_stop_bits(uart_ctx[port].dev, stop_bits < sizeof(stop_bits_map) ? stop_bits_map[stop_bits] : 1); + uart_ll_set_parity(uart_ctx[port].dev, parity < sizeof(parity_map)/sizeof(parity_map[0]) ? parity_map[parity] : UART_PARITY_DISABLE); + + uart_hal_rxfifo_rst(&uart_ctx[port]); + uart_hal_txfifo_rst(&uart_ctx[port]); + + // GPIO pin routing -- UART0 defaults (TX=43, RX=44) set by bootloader. + // For non-default pins or UART1/2, route via GPIO matrix. + if (tx_gpio >= 0) + { + esp_rom_gpio_pad_select_gpio(tx_gpio); + esp_rom_gpio_connect_out_signal(tx_gpio, + uart_periph_signal[port].pins[SOC_UART_PERIPH_SIGNAL_TX].signal, false, false); + } + if (rx_gpio >= 0) + { + esp_rom_gpio_pad_select_gpio(rx_gpio); + esp_rom_gpio_pad_pullup_only(rx_gpio); + esp_rom_gpio_connect_in_signal(rx_gpio, + uart_periph_signal[port].pins[SOC_UART_PERIPH_SIGNAL_RX].signal, false); + } + + uart_initialized[port] = true; + return 1; +} + +void ow_uart_close(int port) +{ + if (port < 0 || port >= NUM_UARTS || !uart_initialized[port]) + return; + uart_hal_txfifo_rst(&uart_ctx[port]); + uart_hal_rxfifo_rst(&uart_ctx[port]); + uart_initialized[port] = false; +} + +int32_t ow_uart_read(int port, uint8_t *buf, int32_t len) +{ + if (port < 0 || port >= NUM_UARTS || !uart_initialized[port]) + return 0; + int rd_len = (int)len; + uart_hal_read_rxfifo(&uart_ctx[port], buf, &rd_len); + return rd_len; +} + +int32_t ow_uart_write(int port, const uint8_t *buf, int32_t len) +{ + if (port < 0 || port >= NUM_UARTS || !uart_initialized[port]) + return 0; + uint32_t written = 0; + uart_hal_write_txfifo(&uart_ctx[port], buf, (uint32_t)len, &written); + return (int32_t)written; +} + +int32_t ow_uart_rx_pending(int port) +{ + if (port < 0 || port >= NUM_UARTS || !uart_initialized[port]) + return 0; + return (int32_t)uart_ll_get_rxfifo_len(uart_ctx[port].dev); +} + +int ow_uart_tx_idle(int port) +{ + if (port < 0 || port >= NUM_UARTS || !uart_initialized[port]) + return 1; + return uart_ll_is_tx_idle(uart_ctx[port].dev) ? 1 : 0; +} + +int32_t ow_uart_flush(int port) +{ + if (port < 0 || port >= NUM_UARTS || !uart_initialized[port]) + return 0; + while (!uart_ll_is_tx_idle(uart_ctx[port].dev)) + ; + return 0; +} + +// -- WiFi wrappers -- + +#include "esp_wifi.h" +#include "esp_private/wifi.h" +#include "esp_netif.h" +#include "esp_event.h" +#include "esp_mac.h" + +static esp_netif_t *ow_wifi_netif_sta; +static esp_netif_t *ow_wifi_netif_ap; +static bool ow_wifi_initialized; + +typedef void (*ow_wifi_event_cb_t)(int event_id, void *data, int data_len); + +static ow_wifi_event_cb_t ow_wifi_sta_cb; +static ow_wifi_event_cb_t ow_wifi_ap_cb; + +static void ow_wifi_event_handler(void *arg, esp_event_base_t base, + int32_t event_id, void *event_data) +{ + if (base == WIFI_EVENT) + { + switch (event_id) + { + case WIFI_EVENT_STA_CONNECTED: + case WIFI_EVENT_STA_DISCONNECTED: + case WIFI_EVENT_STA_START: + case WIFI_EVENT_STA_STOP: + if (ow_wifi_sta_cb) + ow_wifi_sta_cb(event_id, event_data, 0); + break; + + case WIFI_EVENT_AP_START: + case WIFI_EVENT_AP_STOP: + case WIFI_EVENT_AP_STACONNECTED: + case WIFI_EVENT_AP_STADISCONNECTED: + if (ow_wifi_ap_cb) + ow_wifi_ap_cb(event_id, event_data, 0); + break; + } + } + else if (base == IP_EVENT) + { + if (event_id == IP_EVENT_STA_GOT_IP && ow_wifi_sta_cb) + ow_wifi_sta_cb(event_id, event_data, 0); + } +} + +int ow_wifi_init(void) +{ + if (ow_wifi_initialized) + return 1; + + esp_err_t err = esp_netif_init(); + if (err != ESP_OK) + return 0; + + err = esp_event_loop_create_default(); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) + return 0; + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + err = esp_wifi_init(&cfg); + if (err != ESP_OK) + return 0; + + esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, + &ow_wifi_event_handler, NULL); + esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, + &ow_wifi_event_handler, NULL); + + ow_wifi_initialized = true; + return 1; +} + +void ow_wifi_deinit(void) +{ + if (!ow_wifi_initialized) + return; + + esp_wifi_stop(); + esp_wifi_deinit(); + + if (ow_wifi_netif_sta) + { + esp_netif_destroy(ow_wifi_netif_sta); + ow_wifi_netif_sta = NULL; + } + if (ow_wifi_netif_ap) + { + esp_netif_destroy(ow_wifi_netif_ap); + ow_wifi_netif_ap = NULL; + } + + ow_wifi_initialized = false; +} + +int ow_wifi_set_mode(int mode) +{ + // mode: 0=none, 1=sta, 2=ap, 3=apsta + return esp_wifi_set_mode((wifi_mode_t)mode) == ESP_OK ? 1 : 0; +} + +int ow_wifi_start(void) +{ + return esp_wifi_start() == ESP_OK ? 1 : 0; +} + +int ow_wifi_stop(void) +{ + return esp_wifi_stop() == ESP_OK ? 1 : 0; +} + +int ow_wifi_sta_config(const char *ssid, const char *password, const uint8_t *bssid) +{ + if (!ow_wifi_netif_sta) + ow_wifi_netif_sta = esp_netif_create_default_wifi_sta(); + + wifi_config_t cfg = {0}; + if (ssid) + { + size_t len = strlen(ssid); + if (len > sizeof(cfg.sta.ssid) - 1) len = sizeof(cfg.sta.ssid) - 1; + memcpy(cfg.sta.ssid, ssid, len); + } + if (password) + { + size_t len = strlen(password); + if (len > sizeof(cfg.sta.password) - 1) len = sizeof(cfg.sta.password) - 1; + memcpy(cfg.sta.password, password, len); + } + if (bssid) + { + memcpy(cfg.sta.bssid, bssid, 6); + cfg.sta.bssid_set = true; + } + + return esp_wifi_set_config(WIFI_IF_STA, &cfg) == ESP_OK ? 1 : 0; +} + +int ow_wifi_sta_connect(void) +{ + return esp_wifi_connect() == ESP_OK ? 1 : 0; +} + +int ow_wifi_sta_disconnect(void) +{ + return esp_wifi_disconnect() == ESP_OK ? 1 : 0; +} + +int ow_wifi_ap_config(const char *ssid, const char *password, + uint8_t channel, uint8_t max_conn, uint8_t hidden) +{ + if (!ow_wifi_netif_ap) + ow_wifi_netif_ap = esp_netif_create_default_wifi_ap(); + + wifi_config_t cfg = {0}; + if (ssid) + { + size_t len = strlen(ssid); + if (len > sizeof(cfg.ap.ssid) - 1) len = sizeof(cfg.ap.ssid) - 1; + memcpy(cfg.ap.ssid, ssid, len); + cfg.ap.ssid_len = (uint8_t)len; + } + if (password) + { + size_t len = strlen(password); + if (len > sizeof(cfg.ap.password) - 1) len = sizeof(cfg.ap.password) - 1; + memcpy(cfg.ap.password, password, len); + cfg.ap.authmode = (len > 0) ? WIFI_AUTH_WPA2_PSK : WIFI_AUTH_OPEN; + } + else + cfg.ap.authmode = WIFI_AUTH_OPEN; + + cfg.ap.channel = channel; + cfg.ap.max_connection = max_conn > 0 ? max_conn : 4; + cfg.ap.ssid_hidden = hidden; + + return esp_wifi_set_config(WIFI_IF_AP, &cfg) == ESP_OK ? 1 : 0; +} + +int ow_wifi_set_tx_power(int8_t power) +{ + return esp_wifi_set_max_tx_power(power) == ESP_OK ? 1 : 0; +} + +int ow_wifi_get_channel(uint8_t *channel) +{ + uint8_t primary; + wifi_second_chan_t second; + if (esp_wifi_get_channel(&primary, &second) != ESP_OK) + return 0; + *channel = primary; + return 1; +} + +int ow_wifi_get_mac(int iface, uint8_t *mac) +{ + // iface: 0=sta, 1=ap + return esp_read_mac(mac, iface == 0 ? ESP_MAC_WIFI_STA : ESP_MAC_WIFI_SOFTAP) == ESP_OK ? 1 : 0; +} + +// Ethernet frame RX callbacks -- one per netif (STA=0, AP=1). +// esp_wifi_internal_reg_rxcb gives us raw Ethernet frames before lwIP, +// so the bridge/routing layer sees all traffic. + +typedef void (*ow_wifi_rx_cb_t)(const uint8_t *data, int len, int iface); + +static ow_wifi_rx_cb_t ow_wifi_rx_callback; + +static esp_err_t ow_wifi_sta_rx(void *buffer, uint16_t len, void *eb) +{ + if (ow_wifi_rx_callback) + ow_wifi_rx_callback((const uint8_t *)buffer, len, 0); + return esp_netif_receive(ow_wifi_netif_sta, buffer, len, eb); +} + +static esp_err_t ow_wifi_ap_rx(void *buffer, uint16_t len, void *eb) +{ + if (ow_wifi_rx_callback) + ow_wifi_rx_callback((const uint8_t *)buffer, len, 1); + return esp_netif_receive(ow_wifi_netif_ap, buffer, len, eb); +} + +int ow_wifi_set_rx_callback(ow_wifi_rx_cb_t cb) +{ + ow_wifi_rx_callback = cb; + esp_err_t err; + err = esp_wifi_internal_reg_rxcb(WIFI_IF_STA, cb ? &ow_wifi_sta_rx : NULL); + if (err != ESP_OK) + return 0; + err = esp_wifi_internal_reg_rxcb(WIFI_IF_AP, cb ? &ow_wifi_ap_rx : NULL); + return err == ESP_OK ? 1 : 0; +} + +int ow_wifi_tx(int iface, const uint8_t *data, int len) +{ + return esp_wifi_internal_tx((wifi_interface_t)iface, (void *)data, (uint16_t)len) == ESP_OK ? 1 : 0; +} + +void ow_wifi_set_sta_callback(ow_wifi_event_cb_t cb) +{ + ow_wifi_sta_cb = cb; +} + +void ow_wifi_set_ap_callback(ow_wifi_event_cb_t cb) +{ + ow_wifi_ap_cb = cb; +} + +// -- TWAI (CAN) wrappers -- + +// Legacy TWAI driver -- TODO: port to esp_twai.h node-handle API +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcpp" +#include "driver/twai.h" +#pragma GCC diagnostic pop + +static bool ow_twai_initialized; + +int ow_twai_init(uint32_t baud_rate, int tx_io, int rx_io) +{ + if (ow_twai_initialized) + return 1; + + twai_timing_config_t timing; + switch (baud_rate) + { + case 25000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_25KBITS(); break; + case 50000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_50KBITS(); break; + case 100000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_100KBITS(); break; + case 125000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_125KBITS(); break; + case 250000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_250KBITS(); break; + case 500000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_500KBITS(); break; + case 800000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_800KBITS(); break; + case 1000000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_1MBITS(); break; + default: return 0; + } + + twai_general_config_t general = TWAI_GENERAL_CONFIG_DEFAULT(tx_io, rx_io, TWAI_MODE_NORMAL); + twai_filter_config_t filter = TWAI_FILTER_CONFIG_ACCEPT_ALL(); + + if (twai_driver_install(&general, &timing, &filter) != ESP_OK) + return 0; + + if (twai_start() != ESP_OK) + { + twai_driver_uninstall(); + return 0; + } + + ow_twai_initialized = true; + return 1; +} + +void ow_twai_deinit(void) +{ + if (!ow_twai_initialized) + return; + twai_stop(); + twai_driver_uninstall(); + ow_twai_initialized = false; +} + +int ow_twai_transmit(uint32_t id, int extended, int rtr, const uint8_t *data, uint8_t len) +{ + if (!ow_twai_initialized) + return 0; + + twai_message_t msg = {0}; + msg.identifier = id; + msg.extd = extended ? 1 : 0; + msg.rtr = rtr ? 1 : 0; + msg.data_length_code = len; + if (len > 0 && data) + memcpy(msg.data, data, len > 8 ? 8 : len); + + return twai_transmit(&msg, 0) == ESP_OK ? 1 : 0; +} + +int ow_twai_receive(uint32_t *id, int *extended, int *rtr, uint8_t *data, uint8_t *len) +{ + if (!ow_twai_initialized) + return 0; + + twai_message_t msg; + if (twai_receive(&msg, 0) != ESP_OK) + return 0; + + *id = msg.identifier; + *extended = msg.extd; + *rtr = msg.rtr; + *len = msg.data_length_code; + if (msg.data_length_code > 0) + memcpy(data, msg.data, msg.data_length_code > 8 ? 8 : msg.data_length_code); + + return 1; +} + +// -- lwIP netdb wrappers (link-order fix) -- +// D object references lwip_getaddrinfo/lwip_freeaddrinfo but the D object +// appears after liblwip.a in the link. These wrappers are in libmain.a +// which is linked with --whole-archive, ensuring they're always present. + +int ow_lwip_getaddrinfo(const char *nodename, const char *servname, + const struct addrinfo *hints, struct addrinfo **res) +{ + return lwip_getaddrinfo(nodename, servname, hints, res); +} + +void ow_lwip_freeaddrinfo(struct addrinfo *ai) +{ + lwip_freeaddrinfo(ai); +} diff --git a/src/urt/exception.d b/src/urt/exception.d index 1dc3467..422fe82 100644 --- a/src/urt/exception.d +++ b/src/urt/exception.d @@ -32,11 +32,14 @@ void urt_assert(string file, size_t line, string msg) nothrow @nogc import sys.bl808.uart : uart0_puts, uart0_hex; import urt.mem.temp : tconcat; uart0_puts(tconcat("\n*** ASSERT: ", msg, " at ", file, ':', line, '\n')); - while (true) - { - // SPIN! - // (we should probably reboot) - } + while (true) {} + } + else version (Espressif) + { + import urt.io : writef_to, WriteTarget; + writef_to!(WriteTarget.stdout, true)("*** ASSERT: {2} at {0}:{1}", file, line, msg); + import urt.internal.stdc.stdlib : exit; + exit(-1); } else { diff --git a/src/urt/fibre.d b/src/urt/fibre.d index fc2b04e..2952503 100644 --- a/src/urt/fibre.d +++ b/src/urt/fibre.d @@ -293,7 +293,7 @@ class AbortException : Exception } } -void* mainFibre = null; +__gshared void* mainFibre = null; AbortException abortException; @@ -428,6 +428,101 @@ version (UseWindowsFibreAPI) SwitchToFiber(fdata.fiber); } } +else version (FreeRTOS) +{ + // FreeRTOS task-based fibre implementation. + // Each fibre is a FreeRTOS task that blocks on a task notification. + // co_switch does a notify-target + wait-for-notification handoff, + // giving us cooperative coroutine semantics on top of FreeRTOS. + + // C wrappers for FreeRTOS macros (in platforms/esp32s3/main/freertos_fibre.c) + extern(C) int ow_task_create(void function(void*), const(char)*, uint, void*, uint, void**) nothrow @nogc; + extern(C) void ow_task_delete(void*) nothrow @nogc; + extern(C) void* ow_task_current() nothrow @nogc; + extern(C) void ow_task_notify_give(void*) nothrow @nogc; + extern(C) uint ow_task_notify_take(uint) nothrow @nogc; + extern(C) uint ow_task_priority_get(void*) nothrow @nogc; + + struct co_fibre_data + { + void* user_data; + uint stack_size; + void* task_handle; + coentry_t coentry; + } + + co_fibre_data main_fibre_data; + cothread_t co_active_handle = null; + + private inout(co_fibre_data)* co_get_fibre_data(inout cothread_t fibre) pure + => cast(co_fibre_data*)fibre; + + cothread_t co_active() + { + if (!co_active_handle) + { + main_fibre_data.task_handle = ow_task_current(); + co_active_handle = &main_fibre_data; + } + return co_active_handle; + } + + void* co_data() + => (cast(co_fibre_data*)co_active_handle).user_data; + + cothread_t co_derive(void[] memory, coentry_t entry, void* data) + { + return null; // not supported with FreeRTOS tasks + } + + extern(C) static void co_freertos_entry(void* param) nothrow @nogc + { + co_active_handle = cast(cothread_t)param; // set TLS for this task + ow_task_notify_take(0xFFFFFFFF); // block until first co_switch + (cast(co_fibre_data*)param).coentry(); + } + + cothread_t co_create(size_t stack_size, coentry_t entry, void* data) + { + assert(stack_size <= uint.max, "Stack size too large"); + co_active(); // ensure main fibre initialized + + auto fdata = defaultAllocator().allocT!co_fibre_data(); + if (!fdata) return null; + + fdata.user_data = data; + fdata.stack_size = cast(uint)stack_size; + fdata.coentry = entry; + + auto priority = ow_task_priority_get(null); + + if (!ow_task_create(&co_freertos_entry, "fibre", cast(uint)stack_size, + fdata, priority, &fdata.task_handle)) + { + defaultAllocator().freeT(fdata); + return null; + } + + return fdata; + } + + void co_delete(cothread_t handle) + { + auto fdata = cast(co_fibre_data*)handle; + if (fdata && fdata != &main_fibre_data) + { + if (fdata.task_handle) + ow_task_delete(fdata.task_handle); + defaultAllocator().freeT(fdata); + } + } + + void co_switch(cothread_t handle) + { + ow_task_notify_give((cast(co_fibre_data*)handle).task_handle); + ow_task_notify_take(0xFFFFFFFF); + } +} else { align(16) struct co_fibre_data diff --git a/src/urt/file.d b/src/urt/file.d index 34199dc..94c3af4 100644 --- a/src/urt/file.d +++ b/src/urt/file.d @@ -127,7 +127,7 @@ bool file_exists(const(char)[] path) return stat(path.tstringz, &st) == 0 && S_ISREG(st.st_mode); } else - assert(0, "File: not implemented for this platform"); + return false; } Result delete_file(const(char)[] path) @@ -143,7 +143,7 @@ Result delete_file(const(char)[] path) return errno_result(); } else - assert(0, "File: not implemented for this platform"); + return InternalResult.unsupported; // no filesystem on this platform return Result.success; } @@ -161,7 +161,7 @@ Result rename_file(const(char)[] oldPath, const(char)[] newPath) return posix_result(result); } else - assert(0, "File: not implemented for this platform"); + return InternalResult.unsupported; // no filesystem on this platform return Result.success; } @@ -179,7 +179,7 @@ Result copy_file(const(char)[] oldPath, const(char)[] newPath, bool overwriteExi assert(false); } else - assert(0, "File: not implemented for this platform"); + return InternalResult.unsupported; // no filesystem on this platform return Result.success; } @@ -232,7 +232,7 @@ Result get_path(ref const File file, ref char[] buffer) buffer = buffer[0..r]; } else - assert(0, "File: not implemented for this platform"); + return InternalResult.unsupported; // no filesystem on this platform return Result.success; } @@ -269,7 +269,7 @@ Result get_file_attributes(const(char)[] path, out FileAttributes outAttributes) assert(false); } else - assert(0, "File: not implemented for this platform"); + return InternalResult.unsupported; // no filesystem on this platform return Result.success; } @@ -313,7 +313,7 @@ Result get_attributes(ref const File file, out FileAttributes outAttributes) assert(false); } else - assert(0, "File: not implemented for this platform"); + return InternalResult.unsupported; // no filesystem on this platform return InternalResult.unsupported; } @@ -322,9 +322,12 @@ void[] load_file(const(char)[] path, NoGCAllocator allocator = defaultAllocator( { File f; Result r = f.open(path, FileOpenMode.ReadExisting); - if (!r && r.file_result == FileResult.NotFound) - return null; - assert(r, "TODO: handle error"); + if (!r) + { + if (r.file_result == FileResult.NotFound) + return null; + return null; // TODO: are there any errors we can handle? + } ulong size = f.get_size(); assert(size <= size_t.max, "File is too large"); void[] buffer = allocator.alloc(cast(size_t)size); @@ -368,8 +371,7 @@ Result create_directory(const(char)[] path) } else { - assert(0, "File: not implemented for this platform"); - return InternalResult.unsupported; + return InternalResult.unsupported; // no filesystem on this platform } if (r == InternalResult.already_exists) @@ -508,7 +510,7 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags lseek(file.fd, 0, SEEK_END); } else - assert(0, "File: not implemented for this platform"); + return InternalResult.unsupported; // no filesystem on this platform return Result.success; } @@ -520,7 +522,7 @@ bool is_open(ref const File file) else version (Posix) return file.fd != -1; else - assert(0, "File: not implemented for this platform"); + return false; } void close(ref File file) @@ -539,8 +541,6 @@ void close(ref File file) urt.internal.sys.posix.close(file.fd); file.fd = -1; } - else - assert(0, "File: not implemented for this platform"); } ulong get_size(ref const File file) @@ -560,7 +560,7 @@ ulong get_size(ref const File file) return fs.st_size; } else - assert(0, "File: not implemented for this platform"); + return 0; } Result set_size(ref File file, ulong size) @@ -609,7 +609,7 @@ Result set_size(ref File file, ulong size) return errno_result(); } else - assert(0, "File: not implemented for this platform"); + return InternalResult.unsupported; // no filesystem on this platform return Result.success; } @@ -626,7 +626,7 @@ ulong get_pos(ref const File file) else version (Posix) return lseek(file.fd, 0, SEEK_CUR); else - assert(0, "File: not implemented for this platform"); + return 0; } Result set_pos(ref File file, ulong offset) @@ -645,7 +645,7 @@ Result set_pos(ref File file, ulong offset) return errno_result(); } else - assert(0, "File: not implemented for this platform"); + return InternalResult.unsupported; // no filesystem on this platform return Result.success; } @@ -671,7 +671,7 @@ Result read(ref File file, void[] buffer, out size_t bytesRead) bytesRead = n; } else - assert(0, "File: not implemented for this platform"); + return InternalResult.unsupported; // no filesystem on this platform return Result.success; } @@ -703,7 +703,7 @@ Result read_at(ref File file, void[] buffer, ulong offset, out size_t bytesRead) bytesRead = n; } else - assert(0, "File: not implemented for this platform"); + return InternalResult.unsupported; // no filesystem on this platform return Result.success; } @@ -724,7 +724,7 @@ Result write(ref File file, const(void)[] data, out size_t bytesWritten) bytesWritten = n; } else - assert(0, "File: not implemented for this platform"); + return InternalResult.unsupported; // no filesystem on this platform return Result.success; } @@ -752,7 +752,7 @@ Result write_at(ref File file, const(void)[] data, ulong offset, out size_t byte bytesWritten = n; } else - assert(0, "File: not implemented for this platform"); + return InternalResult.unsupported; // no filesystem on this platform return Result.success; } @@ -769,7 +769,7 @@ Result flush(ref File file) return errno_result(); } else - assert(0, "File: not implemented for this platform"); + return InternalResult.unsupported; // no filesystem on this platform return Result.success; } @@ -804,7 +804,7 @@ FileResult file_result(Result result) } } else - assert(0, "File: not implemented for this platform"); + return FileResult.Failure; // no filesystem on this platform } Result get_temp_filename(ref char[] buffer, const(char)[] dstDir, const(char)[] prefix) @@ -838,7 +838,7 @@ Result get_temp_filename(ref char[] buffer, const(char)[] dstDir, const(char)[] return r; } else - assert(0, "File: not implemented for this platform"); + return InternalResult.unsupported; // no filesystem on this platform return Result.success; } diff --git a/src/urt/internal/stdc/errno.d b/src/urt/internal/stdc/errno.d index e25d5f2..d3d7331 100644 --- a/src/urt/internal/stdc/errno.d +++ b/src/urt/internal/stdc/errno.d @@ -39,6 +39,11 @@ version (SystemZ) version = IBMZ_Any; version (X86) version = X86_Any; version (X86_64) version = X86_Any; +// Picolibc forked from newlib and shares its errno constant values. +// The accessor differs (picolibc uses a plain global, newlib uses __errno()), +version (CRuntime_Picolibc) version = NewlibCompat; +version (CRuntime_Newlib) version = NewlibCompat; + @trusted: // Only manipulates errno. nothrow: @nogc: @@ -67,6 +72,21 @@ else version (CRuntime_Musl) alias errno = __errno_location; } } +else version (CRuntime_Picolibc) +{ + version (FreeRTOS) + { + // On FreeRTOS, picolibc defines errno as _Thread_local which conflicts + // with emulated-TLS. Use a C shim to access it indirectly. + extern (C) int* ow_errno_location() nothrow @nogc; + @property ref int errno() nothrow @nogc { return *ow_errno_location(); } + } + else + { + // Bare-metal single-threaded: errno is a plain global. + extern (C) extern int errno; + } +} else version (CRuntime_Newlib) { extern (C) @@ -250,7 +270,7 @@ version (CRuntime_Microsoft) enum ETXTBSY = 139; enum EWOULDBLOCK = 140; } -else version (CRuntime_Newlib) +else version (NewlibCompat) { enum EPERM = 1; enum ENOENT = 2; diff --git a/src/urt/io.d b/src/urt/io.d index dc8b3a3..154da0c 100644 --- a/src/urt/io.d +++ b/src/urt/io.d @@ -15,7 +15,15 @@ template write_to(WriteTarget target, bool newline = false) { static if (target == WriteTarget.stdout || target == WriteTarget.stderr) { - version (FreeStanding) + version (Espressif) + { + foreach (ch; str) + esp_rom_uart_putc(ch); + static if (newline) + esp_rom_uart_putc('\n'); + return cast(int) str.length; + } + else version (FreeStanding) { import sys.bl808.uart : uart0_puts; uart0_puts(str); @@ -83,7 +91,11 @@ alias writeln_debug = write_to!(WriteTarget.debugstring, true); void flush(WriteTarget target = WriteTarget.stdout)() nothrow @nogc { - version (FreeStanding) + version (Espressif) + { + // ROM UART writes are unbuffered + } + else version (FreeStanding) { // UART writes are unbuffered — nothing to flush } @@ -116,6 +128,11 @@ unittest private: -version (FreeStanding) {} +version (Espressif) +{ + // ROM UART putc -- in mask ROM, zero code size + extern(C) void esp_rom_uart_putc(char c) nothrow @nogc; +} +else version (FreeStanding) {} else import urt.internal.stdc.stdio : stdout, stderr, fwrite, fflush; diff --git a/src/urt/mem/alloc.d b/src/urt/mem/alloc.d index 955e8a4..caa6be1 100644 --- a/src/urt/mem/alloc.d +++ b/src/urt/mem/alloc.d @@ -44,6 +44,12 @@ void[] alloc_aligned(size_t size, size_t alignment) pure void* mem; return posix_memalign(&mem, alignment, size) ? null : mem[0 .. size]; } + else version (Espressif) + { + enum MALLOC_CAP_DEFAULT = 1 << 12; + void* mem = heap_caps_aligned_alloc(alignment, size, MALLOC_CAP_DEFAULT); + return mem ? mem[0 .. size] : null; + } else version (FreeStanding) { size_t header_size = (void*).sizeof + alignment; @@ -88,6 +94,8 @@ void free_aligned(void[] mem) pure _aligned_free(mem.ptr); else version (Posix) urt.mem.free(mem.ptr); + else version (Espressif) + heap_caps_aligned_free(mem.ptr); else version (FreeStanding) { void* p = (cast(void**)mem.ptr)[-1]; @@ -116,6 +124,8 @@ size_t memsize(void* ptr) pure return _aligned_msize(ptr); else version (Posix) return malloc_usable_size(ptr); + else version (Espressif) + return heap_caps_get_allocated_size(ptr); else version (FreeStanding) { void* mem = (cast(void**)ptr)[-1]; @@ -148,7 +158,14 @@ version (Windows) extern(C) size_t _aligned_msize(void* memblock) pure; } -version (Posix) +version (Espressif) +{ + // ESP-IDF heap_caps API — provides aligned alloc and size query + extern(C) void* heap_caps_aligned_alloc(size_t alignment, size_t size, uint caps) pure; + extern(C) void heap_caps_aligned_free(void* ptr) pure; + extern(C) size_t heap_caps_get_allocated_size(void* ptr) pure; +} +else version (Posix) { extern(C) size_t malloc_usable_size(void *__ptr) pure; } diff --git a/src/urt/result.d b/src/urt/result.d index d18c37b..d39959c 100644 --- a/src/urt/result.d +++ b/src/urt/result.d @@ -67,6 +67,10 @@ nothrow @nogc: // TODO: should we have a way to convert Result to StringResult, so we can format error messages? +version (Posix) version = Errno; +version (CRuntime_Picolibc) version = Errno; +version (CRuntime_Newlib) version = Errno; + version (Windows) { import urt.internal.sys.windows; @@ -91,14 +95,14 @@ version (Windows) Result getlasterror_result() => Result(GetLastError()); } -else version (Posix) +else version (Errno) { import urt.internal.stdc.errno; enum InternalResult : Result { success = Result.success, - failed = Result(EIO), // not a good general failure, but a lot of people use it this way + failed = Result(EIO), buffer_too_small = Result(ERANGE), invalid_parameter = Result(EINVAL), data_error = Result(EILSEQ), @@ -131,15 +135,4 @@ else version (FreeStanding) aborted = Result(9), no_memory = Result(10), } - - // Bare-metal errno support — provided by the IP stack or libc shim - extern (C) private int* __errno_location() nothrow @nogc; - - @property int errno() nothrow @nogc @trusted - { - return *__errno_location(); - } - - Result errno_result() - => Result(errno); } diff --git a/src/urt/socket.d b/src/urt/socket.d index 8560ee9..017e737 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -33,6 +33,7 @@ else version (Posix) _recv = urt.internal.os.recv, _recvfrom = urt.internal.os.recvfrom, _shutdown = urt.internal.os.shutdown, _close = urt.internal.os.close, _poll = urt.internal.os.poll; + version = BSDSockets; version = HasUnixSocket; version = HasIPv6; @@ -47,9 +48,11 @@ else version (Posix) enum AF_BRIDGE = 7; // Multiprotocol bridge enum AF_INET6 = 10; // IP version 6 } -else version (FreeStanding) +else version (lwIP) { - // Bare-metal: BSD socket constants compatible with lwIP + // lwIP BSD socket API -- constants and structs match lwIP defaults + version = BSDSockets; + alias SocketHandle = int; enum INVALID_SOCKET = -1; @@ -71,63 +74,109 @@ else version (FreeStanding) enum SOL_SOCKET = 0xFFF; - enum MSG_PEEK = 0x02; + enum MSG_PEEK = 0x01; + + enum SHUT_RD = 0; + enum SHUT_WR = 1; + enum SHUT_RDWR = 2; + + // fcntl constants for non-blocking mode + enum F_GETFL = 3; + enum F_SETFL = 4; + enum O_NONBLOCK = 1; + + // socket options + enum SO_REUSEADDR = 0x0004; + enum SO_KEEPALIVE = 0x0008; + enum SO_LINGER = 0x0080; + enum SO_SNDBUF = 0x1001; + enum SO_RCVBUF = 0x1002; + enum SO_ERROR = 0x1007; + enum TCP_NODELAY = 0x01; + enum TCP_KEEPIDLE = 0x03; + enum TCP_KEEPINTVL = 0x04; + enum TCP_KEEPCNT = 0x05; + enum IP_ADD_MEMBERSHIP = 3; + enum IP_MULTICAST_TTL = 5; + enum IP_MULTICAST_LOOP = 7; alias socklen_t = uint; struct in_addr { uint s_addr; } struct in6_addr { ubyte[16] s6_addr; } - struct sockaddr { ushort sa_family; ubyte[14] sa_data; } - struct sockaddr_in { ushort sin_family; ushort sin_port; in_addr sin_addr; ubyte[8] sin_zero; } - struct sockaddr_in6 { ushort sin6_family; ushort sin6_port; uint sin6_flowinfo; in6_addr sin6_addr; uint sin6_scope_id; } - struct sockaddr_storage { ushort ss_family; ubyte[126] _pad; } - struct linger { ushort l_onoff; ushort l_linger; } + struct sockaddr { ubyte sa_len; ubyte sa_family; ubyte[14] sa_data; } + struct sockaddr_in { ubyte sin_len; ubyte sin_family; ushort sin_port; in_addr sin_addr; ubyte[8] sin_zero; } + struct sockaddr_in6 { ubyte sin6_len; ubyte sin6_family; ushort sin6_port; uint sin6_flowinfo; in6_addr sin6_addr; uint sin6_scope_id; } + struct sockaddr_storage { ubyte s2_len; ubyte ss_family; ubyte[2] s2_data1; uint[3] s2_data2; uint[3] s2_data3; } + struct linger { int l_onoff; int l_linger; } struct ip_mreq { in_addr imr_multiaddr; in_addr imr_interface; } struct iovec { void* iov_base; size_t iov_len; } - struct msghdr { void* msg_name; socklen_t msg_namelen; iovec* msg_iov; size_t msg_iovlen; void* msg_control; size_t msg_controllen; int msg_flags; } - - enum SO_ERROR = 0x1007; + struct msghdr { void* msg_name; socklen_t msg_namelen; iovec* msg_iov; int msg_iovlen; void* msg_control; socklen_t msg_controllen; int msg_flags; } - enum POLLRDNORM = 0x0040; - enum POLLWRNORM = 0x0100; - enum POLLERR = 0x0008; - enum POLLHUP = 0x0010; - enum POLLNVAL = 0x0020; + enum POLLRDNORM = 0x10; + enum POLLWRNORM = 0x80; + enum POLLERR = 0x04; + enum POLLHUP = 0x200; + enum POLLNVAL = 0x08; enum AI_PASSIVE = 0x01; enum AI_CANONNAME = 0x02; enum AI_NUMERICHOST = 0x04; - enum AI_V4MAPPED = 0x08; - enum AI_ALL = 0x10; - enum AI_ADDRCONFIG = 0x20; - enum AI_NUMERICSERV = 0x400; + enum AI_NUMERICSERV = 0x08; + enum AI_V4MAPPED = 0x10; + enum AI_ALL = 0x20; + enum AI_ADDRCONFIG = 0x40; struct pollfd { int fd; short events; short revents; } struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; sockaddr* ai_addr; char* ai_canonname; addrinfo* ai_next; } - // BSD socket function stubs — will be provided by the IP stack + // lwIP socket functions -- actual symbol names are lwip_* prefixed extern(C) nothrow @nogc { - int poll(pollfd*, uint, int); - SocketHandle socket(int domain, int type, int protocol); - int _bind(SocketHandle, const(sockaddr)*, socklen_t); - int _listen(SocketHandle, int); - int _connect(SocketHandle, const(sockaddr)*, socklen_t); - SocketHandle _accept(SocketHandle, sockaddr*, socklen_t*); - ptrdiff_t _send(SocketHandle, const(void)*, size_t, int); - ptrdiff_t _sendto(SocketHandle, const(void)*, size_t, int, const(sockaddr)*, socklen_t); - ptrdiff_t _sendmsg(SocketHandle, const(msghdr)*, int); - ptrdiff_t _recv(SocketHandle, void*, size_t, int); - ptrdiff_t _recvfrom(SocketHandle, void*, size_t, int, sockaddr*, socklen_t*); - int _shutdown(SocketHandle, int); - int setsockopt(SocketHandle, int, int, const(void)*, socklen_t); - int getsockopt(SocketHandle, int, int, void*, socklen_t*); - int getsockname(SocketHandle, sockaddr*, socklen_t*); - int getpeername(SocketHandle, sockaddr*, socklen_t*); - int getaddrinfo(const(char)*, const(char)*, const(addrinfo)*, addrinfo**); - void freeaddrinfo(addrinfo*); - int gethostname(char*, size_t); - int close(int); + int lwip_poll(pollfd*, uint, int); + SocketHandle lwip_socket(int, int, int); + int lwip_bind(SocketHandle, const(sockaddr)*, socklen_t); + int lwip_listen(SocketHandle, int); + int lwip_connect(SocketHandle, const(sockaddr)*, socklen_t); + SocketHandle lwip_accept(SocketHandle, sockaddr*, socklen_t*); + ptrdiff_t lwip_send(SocketHandle, const(void)*, size_t, int); + ptrdiff_t lwip_sendto(SocketHandle, const(void)*, size_t, int, const(sockaddr)*, socklen_t); + ptrdiff_t lwip_sendmsg(SocketHandle, const(msghdr)*, int); + ptrdiff_t lwip_recv(SocketHandle, void*, size_t, int); + ptrdiff_t lwip_recvfrom(SocketHandle, void*, size_t, int, sockaddr*, socklen_t*); + int lwip_shutdown(SocketHandle, int); + int lwip_setsockopt(SocketHandle, int, int, const(void)*, socklen_t); + int lwip_getsockopt(SocketHandle, int, int, void*, socklen_t*); + int lwip_getsockname(SocketHandle, sockaddr*, socklen_t*); + int lwip_getpeername(SocketHandle, sockaddr*, socklen_t*); + int ow_lwip_getaddrinfo(const(char)*, const(char)*, const(addrinfo)*, addrinfo**); + void ow_lwip_freeaddrinfo(addrinfo*); + int lwip_close(int); + int lwip_fcntl(int, int, int); } + + // Aliases so the rest of the codebase uses POSIX names + alias _poll = lwip_poll; + alias socket = lwip_socket; + alias _bind = lwip_bind; + alias _listen = lwip_listen; + alias _connect = lwip_connect; + alias _accept = lwip_accept; + alias _send = lwip_send; + alias _sendto = lwip_sendto; + alias _sendmsg = lwip_sendmsg; + alias _recv = lwip_recv; + alias _recvfrom = lwip_recvfrom; + alias _shutdown = lwip_shutdown; + alias setsockopt = lwip_setsockopt; + alias getsockopt = lwip_getsockopt; + alias getsockname = lwip_getsockname; + alias getpeername = lwip_getpeername; + alias getaddrinfo = ow_lwip_getaddrinfo; + alias freeaddrinfo = ow_lwip_freeaddrinfo; + alias _close = lwip_close; + alias fcntl = lwip_fcntl; + + int gethostname(char*, size_t) nothrow @nogc { return -1; } } else static assert(false, "Platform not supported"); @@ -284,7 +333,7 @@ Result close(Socket socket) int result; version (Windows) result = closesocket(socket.handle); - else version (Posix) + else version (BSDSockets) result = _close(socket.handle); else assert(false, "Not implemented!"); @@ -304,7 +353,7 @@ Result shutdown(Socket socket, SocketShutdownMode how) case SocketShutdownMode.write: t = SD_SEND; break; case SocketShutdownMode.read_write: t = SD_BOTH; break; } - else version (Posix) + else version (BSDSockets) { case SocketShutdownMode.read: t = SHUT_RD; break; case SocketShutdownMode.write: t = SHUT_WR; break; @@ -518,9 +567,9 @@ Result sendmsg(Socket socket, const InetAddress* address, MsgFlags flags, const( hdr.msg_name = sock_addr; hdr.msg_namelen = cast(socklen_t)addr_len; hdr.msg_iov = iov.ptr; - hdr.msg_iovlen = n; + hdr.msg_iovlen = cast(typeof(hdr.msg_iovlen))n; hdr.msg_control = cast(void*)control.ptr; - hdr.msg_controllen = control.length; + hdr.msg_controllen = cast(typeof(hdr.msg_controllen))control.length; hdr.msg_flags = 0; sent = _sendmsg(socket.handle, &hdr, map_message_flags(flags)); @@ -670,7 +719,7 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval uint opt = value ? 1 : 0; r.system_code = ioctlsocket(socket.handle, FIONBIO, &opt); } - else version (Posix) + else version (BSDSockets) { int flags = fcntl(socket.handle, F_GETFL, 0); r.system_code = fcntl(socket.handle, F_SETFL, value ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)); @@ -732,7 +781,7 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval break; case OptType.linger: itmp = cast(int)value.as!"seconds"; - ling = linger(!!itmp, cast(ushort)itmp); + ling = linger(!!itmp, cast(typeof(linger.l_linger))itmp); arg = &ling; break; default: assert(false, "Unexpected"); @@ -1067,10 +1116,10 @@ Result poll(PollFd[] pollFds, Duration timeout, out uint numEvents) int r; version (Windows) r = WSAPoll(fds.ptr, cast(uint)pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); - else version (Posix) - r = _poll(fds.ptr, pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); + else version (BSDSockets) + r = _poll(fds.ptr, cast(uint)pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); else - r = poll(fds.ptr, cast(uint)pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); + assert(false, "Not implemented!"); if (r < 0) { numEvents = 0; @@ -1214,7 +1263,7 @@ SocketResult socket_result(Result result) if (result.system_code == WSAEINVAL) return SocketResult.invalid_argument; } - else version (Posix) + else version (Errno) { static if (EAGAIN != EWOULDBLOCK) if (result.system_code == EAGAIN) @@ -1254,6 +1303,8 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ sockaddr_in* ain = cast(sockaddr_in*)sock_addr; memzero(ain, sockaddr_in.sizeof); + version (lwIP) + ain.sin_len = sockaddr_in.sizeof; ain.sin_family = s_addressFamily[AddressFamily.ipv4]; version (Windows) { @@ -1262,7 +1313,7 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ ain.sin_addr.S_un.S_un_b.s_b3 = address._a.ipv4.addr.b[2]; ain.sin_addr.S_un.S_un_b.s_b4 = address._a.ipv4.addr.b[3]; } - else version (Posix) + else version (BSDSockets) ain.sin_addr.s_addr = address._a.ipv4.addr.address; else assert(false, "Not implemented!"); @@ -1279,6 +1330,8 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ sockaddr_in6* ain6 = cast(sockaddr_in6*)sock_addr; memzero(ain6, sockaddr_in6.sizeof); + version (lwIP) + ain6.sin6_len = sockaddr_in6.sizeof; ain6.sin6_family = s_addressFamily[AddressFamily.ipv6]; storeBigEndian(&ain6.sin6_port, cast(ushort)address._a.ipv6.port); storeBigEndian(cast(uint*)&ain6.sin6_flowinfo, address._a.ipv6.flow_info); @@ -1289,6 +1342,8 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ storeBigEndian(&ain6.sin6_addr.in6_u.u6_addr16[a], address._a.ipv6.addr.s[a]); else version (Posix) storeBigEndian(&ain6.sin6_addr.__in6_u.__u6_addr16[a], address._a.ipv6.addr.s[a]); + else version (BSDSockets) + storeBigEndian(cast(ushort*)&ain6.sin6_addr.s6_addr[a * 2], address._a.ipv6.addr.s[a]); else assert(false, "Not implemented!"); } @@ -1389,7 +1444,7 @@ IPAddr make_IPAddr(ref const in_addr in4) addr.b[2] = in4.S_un.S_un_b.s_b3; addr.b[3] = in4.S_un.S_un_b.s_b4; } - else version (Posix) + else version (BSDSockets) addr.address = in4.s_addr; else assert(false, "Not implemented!"); @@ -1405,6 +1460,8 @@ IPv6Addr make_IPv6Addr(ref const in6_addr in6) addr.s[a] = loadBigEndian(&in6.in6_u.u6_addr16[a]); else version (Posix) addr.s[a] = loadBigEndian(&in6.__in6_u.__u6_addr16[a]); + else version (BSDSockets) + addr.s[a] = loadBigEndian(cast(const(ushort)*)&in6.s6_addr[a * 2]); else assert(false, "Not implemented!"); } @@ -1452,7 +1509,12 @@ struct OptInfo OptType platform_type; } -__gshared immutable ushort[AddressFamily.max+1] s_addressFamily = [ +version (lwIP) + alias sa_family_t = ubyte; +else + alias sa_family_t = ushort; + +__gshared immutable sa_family_t[AddressFamily.max+1] s_addressFamily = [ AF_UNSPEC, AF_UNIX, AF_INET, @@ -1610,15 +1672,29 @@ else version (Darwin) OptInfo( TCP_NODELAY, OptType.bool_, OptType.int_ ), ]; } -else version (FreeStanding) +else version (lwIP) { - // Bare-metal stub — socket options not yet supported - __gshared immutable OptInfo[SocketOption.max] s_socketOptions = () { - OptInfo[SocketOption.max] r; - foreach (ref o; r) - o = OptInfo(-1, OptType.unsupported, OptType.unsupported); - return r; - }(); + __gshared immutable OptInfo[SocketOption.max] s_socketOptions = [ + OptInfo( -1, OptType.bool_, OptType.bool_ ), // NonBlocking + OptInfo( SO_KEEPALIVE, OptType.bool_, OptType.int_ ), + OptInfo( SO_LINGER, OptType.duration, OptType.linger ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), // RandomizePort + OptInfo( SO_SNDBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_RCVBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_REUSEADDR, OptType.bool_, OptType.int_ ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), // NoSigPipe + OptInfo( SO_ERROR, OptType.int_, OptType.int_ ), + OptInfo( IP_ADD_MEMBERSHIP, OptType.multicast_group, OptType.multicast_group ), + OptInfo( IP_MULTICAST_LOOP, OptType.bool_, OptType.int_ ), + OptInfo( IP_MULTICAST_TTL, OptType.int_, OptType.int_ ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), // IP_PKTINFO + OptInfo( -1, OptType.unsupported, OptType.unsupported ), // IPV6_PKTINFO + OptInfo( TCP_KEEPIDLE, OptType.duration, OptType.seconds ), + OptInfo( TCP_KEEPINTVL, OptType.duration, OptType.seconds ), + OptInfo( TCP_KEEPCNT, OptType.int_, OptType.int_ ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), // TcpKeepAlive (Apple) + OptInfo( TCP_NODELAY, OptType.bool_, OptType.int_ ), + ]; } else static assert(false, "TODO"); diff --git a/src/urt/system.d b/src/urt/system.d index d953784..1294b28 100644 --- a/src/urt/system.d +++ b/src/urt/system.d @@ -4,6 +4,17 @@ import urt.platform; import urt.processor; import urt.time; +version (Espressif) +{ + enum uint MALLOC_CAP_DEFAULT = 1 << 12; + extern(C) nothrow @nogc + { + size_t heap_caps_get_total_size(uint caps); + size_t heap_caps_get_free_size(uint caps); + size_t heap_caps_get_minimum_free_size(uint caps); + } +} + nothrow @nogc: @@ -117,6 +128,14 @@ SystemInfo get_sysinfo() r.used_memory = r.total_memory - r.avail_memory; r.reserved_memory = r.used_memory; } + else version (Espressif) + { + r.total_memory = heap_caps_get_total_size(MALLOC_CAP_DEFAULT); + r.avail_memory = heap_caps_get_free_size(MALLOC_CAP_DEFAULT); + r.used_memory = r.total_memory - r.avail_memory; + r.peak_memory = r.total_memory - heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT); + r.uptime = getAppTime(); + } else version (BL808) { auto mi = mallinfo(); diff --git a/src/urt/time.d b/src/urt/time.d index fe1e11c..a90f804 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -16,6 +16,10 @@ else version (BL808) { import sys.bl808.timer; } +else version (Espressif) +{ + extern(C) long esp_timer_get_time() nothrow @nogc; +} nothrow @nogc: @@ -724,6 +728,10 @@ MonoTime getTime() { return MonoTime(mtime_read()); } + else version (Espressif) + { + return MonoTime(esp_timer_get_time()); + } else version (FreeStanding) { assert(0, "getTime: not yet implemented for bare-metal"); @@ -752,6 +760,10 @@ SysTime getSysTime() { return SysTime(mtime_read() + sys_time_offset); } + else version (Espressif) + { + return SysTime(esp_timer_get_time() + sys_time_offset); + } else version (FreeStanding) { assert(0, "getSysTime: not yet implemented for bare-metal"); @@ -793,6 +805,8 @@ ulong unixTimeNs(SysTime t) pure return t.ticks; else version (BL808) return t.ticks * nsec_multiplier; + else version (Espressif) + return t.ticks * nsec_multiplier; else version (FreeStanding) return t.ticks; else @@ -807,6 +821,8 @@ SysTime from_unix_time_ns(ulong ns) pure return SysTime(ns); else version (BL808) return SysTime(ns / nsec_multiplier); + else version (Espressif) + return SysTime(ns / nsec_multiplier); else version (FreeStanding) return SysTime(ns); else @@ -852,6 +868,11 @@ void set_utc_time(ulong unix_ns) p.utc_offset = cast(long)hbn_ticks - cast(long)rtc_read(); p.magic = HbnPersist.HBN_MAGIC; } + else version (Espressif) + { + // Offset stored in RAM; lost on reboot. + // TODO: persist to NVS or RTC memory for deep-sleep survival + } } @@ -879,6 +900,11 @@ else version (BL808) enum uint ticks_per_second = mtime_freq_hz; enum uint nsec_multiplier = 1_000_000_000 / mtime_freq_hz; } +else version (Espressif) +{ + enum uint ticks_per_second = 1_000_000; + enum uint nsec_multiplier = 1_000; +} else version (FreeStanding) { enum uint ticks_per_second = 1_000_000_000; @@ -936,6 +962,11 @@ package(urt) void init_clock() rtc_enable(); recalc_sys_time_offset(); } + else version (Espressif) + { + // No wall-clock reference until set_utc_time() is called (e.g. via NTP/SNTP) + cast()sys_time_offset = 0; + } else version (FreeStanding) { // Bare-metal: no wall-clock reference until set_utc_time() is called. From 0faa61a030ad123ee0e544524d3ca7b7f9da27bf Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 7 Apr 2026 01:35:41 +1000 Subject: [PATCH 115/138] Use handy functions from the ESP32 mask rom; save a bunch of program bytes, and theirs are probably optimised nicely for Xtensa. Also, SHA is hardware accelerated. --- src/urt/crc.d | 235 +++++++++++++++++++++++++++--- src/urt/digest/md5.d | 333 ++++++++++++++++++++++++------------------ src/urt/digest/sha.d | 334 ++++++++++++++++++++++++++++++++----------- src/urt/hash.d | 187 ++++++++++++++---------- src/urt/package.d | 7 + 5 files changed, 781 insertions(+), 315 deletions(-) diff --git a/src/urt/crc.d b/src/urt/crc.d index b26b6c9..1faac53 100644 --- a/src/urt/crc.d +++ b/src/urt/crc.d @@ -8,6 +8,7 @@ nothrow @nogc: enum Algorithm : ubyte { + crc8_smbus, crc16_usb, crc16_modbus, crc16_kermit, @@ -18,6 +19,8 @@ enum Algorithm : ubyte crc32_iso_hdlc, crc32_castagnoli, + crc8_itu_t = crc8_smbus, + crc8_autosar = crc8_smbus, crc16_default_short_packet = crc16_kermit, // good default choice for small packets crc32_default = crc32_castagnoli, // has SSE4.2 hardware implementation @@ -69,7 +72,7 @@ T calculate_crc(T = uint)(const void[] data, ref const crc_params params, ref co // compute a CRC with hard-coded parameters T calculate_crc(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = cast(T)param_table[algo].initial^param_table[algo].final_xor) pure - if (is_unsigned_int!T) + if (!hardware_crc_support!(algo, T) && is_unsigned_int!T) { enum crc_params params = param_table[algo]; static assert(params.width <= T.sizeof*8, "T is too small for the CRC width"); @@ -85,7 +88,9 @@ T calculate_crc(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = foreach (b; bytes) { - static if (params.reflect) + static if (params.width <= 8) + crc = table[crc ^ b]; + else static if (params.reflect) crc = (crc >> 8) ^ table[cast(ubyte)crc ^ b]; else crc = cast(T)((crc << 8) ^ table[(crc >> 8) ^ b]); @@ -97,19 +102,61 @@ T calculate_crc(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = return crc; } +T calculate_crc(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = cast(T)param_table[algo].initial^param_table[algo].final_xor) pure + if (hardware_crc_support!(algo, T) && is_unsigned_int!T) +{ + version (Espressif) + { + enum crc_params params = param_table[algo]; + enum T fxor = cast(T)params.final_xor; + + const ubyte* ptr = cast(ubyte*)data.ptr; + uint len = cast(uint)data.length; + + static if (params.width == 32) + { + static if (params.reflect) + alias rom_crc = crc32_le; + else + alias rom_crc = crc32_be; + return cast(T)(~rom_crc(cast(uint)~(initial ^ fxor), ptr, len) ^ fxor); + } + else static if (params.width == 16) + { + static if (params.reflect) + alias rom_crc = crc16_le; + else + alias rom_crc = crc16_be; + return cast(T)(cast(ushort)(~rom_crc(cast(ushort)~(initial ^ fxor), ptr, len) ^ fxor)); + } + else static if (params.width == 8) + { + static if (params.reflect) + alias rom_crc = crc8_le; + else + alias rom_crc = crc8_be; + return cast(T)(cast(ubyte)(~rom_crc(cast(ubyte)~(initial ^ fxor), ptr, len) ^ fxor)); + } + } + else + static assert(false, "Hardware CRC support claimed, but not implemented for this platform"); +} + + // computes 2 CRC's for 2 points in the data stream... T calculate_crc_2(Algorithm algo, T = IntForWidth!(param_table[algo].width*2))(const void[] data, uint early_offset) pure - if (is_unsigned_int!T) + if (!hardware_crc_support!(algo, T) && is_unsigned_int!T) { enum crc_params params = param_table[algo]; static assert(params.width * 2 <= T.sizeof*8, "T is too small for the CRC width"); + alias CT = CRCType!algo; alias table = crc_table!algo; const ubyte[] bytes = cast(ubyte[])data; - T high_crc = 0; - T crc = cast(T)params.initial; + CT high_crc = 0; + CT crc = cast(CT)params.initial; size_t i = 0; for (; i < bytes.length; ++i) @@ -119,35 +166,95 @@ T calculate_crc_2(Algorithm algo, T = IntForWidth!(param_table[algo].width*2))(c high_crc = crc; goto fast_loop; // skips a redundant loop entry check } - static if (params.reflect) + static if (params.width <= 8) + crc = table[crc ^ bytes[i]]; + else static if (params.reflect) crc = (crc >> 8) ^ table[cast(ubyte)crc ^ bytes[i]]; else - crc = cast(T)((crc << 8) ^ table[(crc >> 8) ^ bytes[i]]); + crc = cast(CT)((crc << 8) ^ table[(crc >> 8) ^ bytes[i]]); } goto done; // skip over the fast loop entry check (which will fail) for (; i < bytes.length; ++i) { fast_loop: - static if (params.reflect) + static if (params.width <= 8) + crc = table[crc ^ bytes[i]]; + else static if (params.reflect) crc = (crc >> 8) ^ table[cast(ubyte)crc ^ bytes[i]]; else - crc = cast(T)((crc << 8) ^ table[(crc >> 8) ^ bytes[i]]); + crc = cast(CT)((crc << 8) ^ table[(crc >> 8) ^ bytes[i]]); } done: static if (params.final_xor) { - crc ^= cast(T)params.final_xor; - high_crc ^= cast(T)params.final_xor; + crc ^= cast(CT)params.final_xor; + high_crc ^= cast(CT)params.final_xor; } static if (params.width <= 8) - return ushort(crc | high_crc << 8); + return cast(T)(cast(uint)crc | (cast(uint)high_crc << 8)); else static if (params.width <= 16) - return uint(crc | high_crc << 16); - else if (params.width <= 32) - return ulong(crc | high_crc << 32); + return cast(T)(cast(uint)crc | (cast(uint)high_crc << 16)); + else static if (params.width <= 32) + return cast(T)(cast(ulong)crc | (cast(ulong)high_crc << 32)); +} + +T calculate_crc_2(Algorithm algo, T = IntForWidth!(param_table[algo].width*2))(const void[] data, uint early_offset) pure + if (hardware_crc_support!(algo, T) && is_unsigned_int!T) +{ + version (Espressif) + { + enum crc_params params = param_table[algo]; + + const ubyte* buf = cast(ubyte*)data.ptr; + uint len = cast(uint)data.length; + + enum uint fxor = params.final_xor; + + static if (params.width == 32) + { + static if (params.reflect) + alias rom_crc = crc32_le; + else + alias rom_crc = crc32_be; + + uint raw_init = cast(uint)~(params.initial ^ fxor); + uint raw_early = rom_crc(raw_init, buf, early_offset); + uint early = ~raw_early ^ fxor; + uint full = ~rom_crc(raw_early, buf + early_offset, len - early_offset) ^ fxor; + return cast(T)(cast(ulong)full | (cast(ulong)early << 32)); + } + else static if (params.width == 16) + { + static if (params.reflect) + alias rom_crc = crc16_le; + else + alias rom_crc = crc16_be; + + ushort raw_init = cast(ushort)~(params.initial ^ fxor); + ushort raw_early = rom_crc(raw_init, buf, early_offset); + ushort early = cast(ushort)(~raw_early ^ fxor); + ushort full = cast(ushort)(~rom_crc(raw_early, buf + early_offset, len - early_offset) ^ fxor); + return cast(T)(cast(uint)full | (cast(uint)early << 16)); + } + else static if (params.width == 8) + { + static if (params.reflect) + alias rom_crc = crc8_le; + else + alias rom_crc = crc8_be; + + ubyte raw_init = cast(ubyte)~(params.initial ^ fxor); + ubyte raw_early = rom_crc(raw_init, buf, early_offset); + ubyte early = cast(ubyte)(~raw_early ^ fxor); + ubyte full = cast(ubyte)(~rom_crc(raw_early, buf + early_offset, len - early_offset) ^ fxor); + return cast(T)(cast(ushort)full | (cast(ushort)early << 8)); + } + } + else + static assert(false, "Hardware CRC support claimed, but not implemented for this platform"); } @@ -189,6 +296,8 @@ unittest { immutable ubyte[9] checkData = ['1','2','3','4','5','6','7','8','9']; + // verify all algorithms against their check values ("123456789") + assert(calculate_crc!(Algorithm.crc8_smbus)(checkData[]) == param_table[Algorithm.crc8_smbus].check); assert(calculate_crc!(Algorithm.crc16_modbus)(checkData[]) == param_table[Algorithm.crc16_modbus].check); assert(calculate_crc!(Algorithm.crc16_ezsp)(checkData[]) == param_table[Algorithm.crc16_ezsp].check); assert(calculate_crc!(Algorithm.crc16_kermit)(checkData[]) == param_table[Algorithm.crc16_kermit].check); @@ -199,19 +308,101 @@ unittest assert(calculate_crc!(Algorithm.crc32_iso_hdlc)(checkData[]) == param_table[Algorithm.crc32_iso_hdlc].check); assert(calculate_crc!(Algorithm.crc32_castagnoli)(checkData[]) == param_table[Algorithm.crc32_castagnoli].check); - // check that rolling CRC works... - ushort crc = calculate_crc!(Algorithm.crc16_modbus)(checkData[0 .. 5]); - assert(calculate_crc!(Algorithm.crc16_modbus)(checkData[5 .. 9], crc) == param_table[Algorithm.crc16_modbus].check); - crc = calculate_crc!(Algorithm.crc16_iso_hdlc)(checkData[0 .. 5]); - assert(calculate_crc!(Algorithm.crc16_iso_hdlc)(checkData[5 .. 9], crc) == param_table[Algorithm.crc16_iso_hdlc].check); - uint crc32 = calculate_crc!(Algorithm.crc32_iso_hdlc)(checkData[0 .. 5]); - assert(calculate_crc!(Algorithm.crc32_iso_hdlc)(checkData[5 .. 9], crc32) == param_table[Algorithm.crc32_iso_hdlc].check); + // rolling CRC: split at multiple points and verify accumulation + static foreach (split; 1 .. 9) + { + assert(calculate_crc!(Algorithm.crc8_smbus)(checkData[split .. 9], + calculate_crc!(Algorithm.crc8_smbus)(checkData[0 .. split])) == param_table[Algorithm.crc8_smbus].check); + assert(calculate_crc!(Algorithm.crc16_modbus)(checkData[split .. 9], + calculate_crc!(Algorithm.crc16_modbus)(checkData[0 .. split])) == param_table[Algorithm.crc16_modbus].check); + assert(calculate_crc!(Algorithm.crc16_kermit)(checkData[split .. 9], + calculate_crc!(Algorithm.crc16_kermit)(checkData[0 .. split])) == param_table[Algorithm.crc16_kermit].check); + assert(calculate_crc!(Algorithm.crc16_iso_hdlc)(checkData[split .. 9], + calculate_crc!(Algorithm.crc16_iso_hdlc)(checkData[0 .. split])) == param_table[Algorithm.crc16_iso_hdlc].check); + assert(calculate_crc!(Algorithm.crc16_xmodem)(checkData[split .. 9], + calculate_crc!(Algorithm.crc16_xmodem)(checkData[0 .. split])) == param_table[Algorithm.crc16_xmodem].check); + assert(calculate_crc!(Algorithm.crc16_ccitt_false)(checkData[split .. 9], + calculate_crc!(Algorithm.crc16_ccitt_false)(checkData[0 .. split])) == param_table[Algorithm.crc16_ccitt_false].check); + assert(calculate_crc!(Algorithm.crc32_iso_hdlc)(checkData[split .. 9], + calculate_crc!(Algorithm.crc32_iso_hdlc)(checkData[0 .. split])) == param_table[Algorithm.crc32_iso_hdlc].check); + } + + // empty data returns the default initial value + assert(calculate_crc!(Algorithm.crc8_smbus)(null) == 0x00); + assert(calculate_crc!(Algorithm.crc32_iso_hdlc)(null) == 0x0000_0000); + assert(calculate_crc!(Algorithm.crc16_kermit)(null) == 0x0000); + assert(calculate_crc!(Algorithm.crc16_iso_hdlc)(null) == 0x0000); + assert(calculate_crc!(Algorithm.crc16_xmodem)(null) == 0x0000); + assert(calculate_crc!(Algorithm.crc16_ccitt_false)(null) == 0xFFFF); + assert(calculate_crc!(Algorithm.crc16_modbus)(null) == 0xFFFF); + + // single byte + assert(calculate_crc!(Algorithm.crc32_iso_hdlc)("A") == 0xD3D9_9E8B); + + // dual CRC: verify both halves + // reflected, final_xor=0 + uint dual16_k = calculate_crc_2!(Algorithm.crc16_kermit)(checkData[], 5); + assert((dual16_k & 0xFFFF) == param_table[Algorithm.crc16_kermit].check); + assert((dual16_k >> 16) == calculate_crc!(Algorithm.crc16_kermit)(checkData[0 .. 5])); + + // reflected, final_xor=mask + uint dual16_h = calculate_crc_2!(Algorithm.crc16_iso_hdlc)(checkData[], 5); + assert((dual16_h & 0xFFFF) == param_table[Algorithm.crc16_iso_hdlc].check); + assert((dual16_h >> 16) == calculate_crc!(Algorithm.crc16_iso_hdlc)(checkData[0 .. 5])); + + ulong dual32 = calculate_crc_2!(Algorithm.crc32_iso_hdlc)(checkData[], 5); + assert((dual32 & 0xFFFF_FFFF) == param_table[Algorithm.crc32_iso_hdlc].check); + assert((dual32 >> 32) == calculate_crc!(Algorithm.crc32_iso_hdlc)(checkData[0 .. 5])); + + // unreflected, final_xor=0 + uint dual16_x = calculate_crc_2!(Algorithm.crc16_xmodem)(checkData[], 5); + assert((dual16_x & 0xFFFF) == param_table[Algorithm.crc16_xmodem].check); + assert((dual16_x >> 16) == calculate_crc!(Algorithm.crc16_xmodem)(checkData[0 .. 5])); + + // 8-bit unreflected + ushort dual8 = calculate_crc_2!(Algorithm.crc8_smbus)(checkData[], 5); + assert((dual8 & 0xFF) == param_table[Algorithm.crc8_smbus].check); + assert((dual8 >> 8) == calculate_crc!(Algorithm.crc8_smbus)(checkData[0 .. 5])); } private: +version (Espressif) +{ + // ESP32 have CRC functions in the mask rom which we'll use where applicable + + version (ESP32_S2) enum has_rom_crc_be = false; + else enum has_rom_crc_be = true; + + private extern(C) pure nothrow @nogc + { + uint crc32_le(uint crc, const ubyte* buf, uint len); + ushort crc16_le(ushort crc, const ubyte* buf, uint len); + ubyte crc8_le(ubyte crc, const ubyte* buf, uint len); + static if (has_rom_crc_be) + { + uint crc32_be(uint crc, const ubyte* buf, uint len); + ushort crc16_be(ushort crc, const ubyte* buf, uint len); + ubyte crc8_be(ubyte crc, const ubyte* buf, uint len); + } + } + + private enum rom_crc_match(Algorithm algo) = ( + (param_table[algo].width == 32 && param_table[algo].poly == 0x04C1_1DB7) || + (param_table[algo].width == 16 && param_table[algo].poly == 0x1021) || + (param_table[algo].width == 8 && param_table[algo].poly == 0x07) + ); + + enum hardware_crc_support(Algorithm algo, T) = is(T == CRCType!algo) && rom_crc_match!algo && (param_table[algo].reflect || has_rom_crc_be); +} +else +{ + enum hardware_crc_support(Algorithm algo, T) = false; +} + enum crc_params[] param_table = [ + crc_params( 8, false, 0x07, 0x0000, 0x0000, 0xF4), // crc8_smbus crc_params(16, true, 0x8005, 0xFFFF, 0xFFFF, 0xB4C8), // crc16_usb crc_params(16, true, 0x8005, 0xFFFF, 0x0000, 0x4B37), // crc16_modbus crc_params(16, true, 0x1021, 0x0000, 0x0000, 0x2189), // crc16_kermit diff --git a/src/urt/digest/md5.d b/src/urt/digest/md5.d index e0a59d6..8771adc 100644 --- a/src/urt/digest/md5.d +++ b/src/urt/digest/md5.d @@ -5,85 +5,194 @@ import urt.endian; nothrow @nogc: // pure -struct MD5Context +version (Espressif) { - ulong size; // size of input in bytes - uint[4] buffer; // current accumulation of hash - ubyte[64] input; // input to be used in the next step + struct MD5Context + { + uint[4] buf; + uint[2] bits; + ubyte[64] input; + } - enum uint[4] initState = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 ]; -} + private extern(C) nothrow @nogc + { + void MD5Init(MD5Context* ctx); + void MD5Update(MD5Context* ctx, const ubyte* buf, uint len); + void MD5Final(ubyte* digest, MD5Context* ctx); + } -void md5_init(ref MD5Context ctx) -{ - ctx.size = 0; - ctx.buffer = MD5Context.initState; -} + void md5_init(ref MD5Context ctx) + { + MD5Init(&ctx); + } -void md5_update(ref MD5Context ctx, const void[] input) + void md5_update(ref MD5Context ctx, const void[] data) + { + MD5Update(&ctx, cast(const ubyte*)data.ptr, cast(uint)data.length); + } + + ubyte[16] md5_finalise(ref MD5Context ctx) + { + ubyte[16] digest; + MD5Final(digest.ptr, &ctx); + return digest; + } +} +else { - size_t offset = ctx.size % 64; - ctx.size += input.length; + struct MD5Context + { + ulong size; // size of input in bytes + uint[4] buffer; // current accumulation of hash + ubyte[64] input; // input to be used in the next step + + enum uint[4] initState = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 ]; + } - size_t i = 64 - offset; - if (input.length < i) + void md5_init(ref MD5Context ctx) { - ctx.input[offset .. offset + input.length] = cast(ubyte[])input[]; - return; + ctx.size = 0; + ctx.buffer = MD5Context.initState; } - ctx.input[offset .. 64] = cast(ubyte[])input[0 .. i]; + void md5_update(ref MD5Context ctx, const void[] input) + { + size_t offset = ctx.size % 64; + ctx.size += input.length; + + size_t i = 64 - offset; + if (input.length < i) + { + ctx.input[offset .. offset + input.length] = cast(ubyte[])input[]; + return; + } + + ctx.input[offset .. 64] = cast(ubyte[])input[0 .. i]; + + while (true) + { + uint[16] tmp = void; + foreach (uint j; 0 .. 16) + tmp[j] = loadLittleEndian(cast(uint*)ctx.input.ptr + j); + md5_step(ctx.buffer, tmp); + + size_t tail = input.length - i; + if (tail < 64) + { + ctx.input[0 .. tail] = (cast(ubyte*)input.ptr)[i .. input.length]; + break; + } + ctx.input[] = (cast(ubyte*)input.ptr)[i .. i + 64]; + i += 64; + } + } - while (true) + ubyte[16] md5_finalise(ref MD5Context ctx) { - // The local variable `tmp` our 512-bit chunk separated into 32-bit words - // we can use in calculations uint[16] tmp = void; - foreach (uint j; 0 .. 16) + uint offset = ctx.size % 64; + uint padding_length = offset < 56 ? 56 - offset : (56 + 64) - offset; + + ubyte[64] PADDING = void; + PADDING[0] = 0x80; + PADDING[1 .. padding_length] = 0; + + md5_update(ctx, PADDING[0 .. padding_length]); + ctx.size -= cast(ulong)padding_length; + + foreach (uint j; 0 .. 14) tmp[j] = loadLittleEndian(cast(uint*)ctx.input.ptr + j); + + tmp[14] = cast(uint)(ctx.size*8); + tmp[15] = (ctx.size*8) >> 32; + md5_step(ctx.buffer, tmp); - size_t tail = input.length - i; - if (tail < 64) + uint[4] digest = void; + foreach (uint k; 0 .. 4) + storeLittleEndian(digest.ptr + k, ctx.buffer.ptr[k]); + return cast(ubyte[16])digest; + } + + +private: + + __gshared immutable ubyte[] S = [ + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 + ]; + + __gshared immutable uint[] K = [ + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 + ]; + + // rotates a 32-bit word left by n bits + uint rotate_left(uint x, uint n) + => (x << n) | (x >> (32 - n)); + + // step on 512 bits of input with the main MD5 algorithm + void md5_step(ref uint[4] buffer, ref const uint[16] input) + { + uint AA = buffer[0]; + uint BB = buffer[1]; + uint CC = buffer[2]; + uint DD = buffer[3]; + + uint E; + + uint j; + + foreach (uint i; 0 .. 64) { - ctx.input[0 .. tail] = (cast(ubyte*)input.ptr)[i .. input.length]; - break; + final switch(i / 16) + { + case 0: + E = ((BB & CC) | (~BB & DD)); // F(BB, CC, DD); uint F(uint X, uint Y, uint Z) => ((X & Y) | (~X & Z)); + j = i; + break; + case 1: + E = ((BB & DD) | (CC & ~DD)); // G(BB, CC, DD); uint G(uint X, uint Y, uint Z) => ((X & Z) | (Y & ~Z)); + j = ((i*5) + 1) % 16; + break; + case 2: + E = (BB ^ CC ^ DD); // H(BB, CC, DD); uint H(uint X, uint Y, uint Z) => (X ^ Y ^ Z); + j = ((i*3) + 5) % 16; + break; + case 3: + E = (CC ^ (BB | ~DD)); // I(BB, CC, DD); uint I(uint X, uint Y, uint Z) => (Y ^ (X | ~Z)); + j = (i*7) % 16; + break; + } + + uint temp = DD; + DD = CC; + CC = BB; + BB = BB + rotate_left(AA + E + K[i] + input[j], S[i]); + AA = temp; } - ctx.input[] = (cast(ubyte*)input.ptr)[i .. i + 64]; - i += 64; - } -} -ubyte[16] md5_finalise(ref MD5Context ctx) -{ - uint[16] tmp = void; - uint offset = ctx.size % 64; - uint padding_length = offset < 56 ? 56 - offset : (56 + 64) - offset; - - // padding used to make the size (in bits) of the input congruent to 448 mod 512 - // TODO: on a large memory system, let's make this a global immutable? - ubyte[64] PADDING = void; - PADDING[0] = 0x80; - PADDING[1 .. padding_length] = 0; - - // Fill in the padding and undo the changes to size that resulted from the update - md5_update(ctx, PADDING[0 .. padding_length]); - ctx.size -= cast(ulong)padding_length; - - // Do a final update (internal to this function) - // Last two 32-bit words are the two halves of the size (converted from bytes to bits) - foreach (uint j; 0 .. 14) - tmp[j] = loadLittleEndian(cast(uint*)ctx.input.ptr + j); - - tmp[14] = cast(uint)(ctx.size*8); - tmp[15] = (ctx.size*8) >> 32; - - md5_step(ctx.buffer, tmp); - - uint[4] digest = void; - foreach (uint k; 0 .. 4) - storeLittleEndian(digest.ptr + k, ctx.buffer.ptr[k]); - return cast(ubyte[16])digest; + buffer[0] += AA; + buffer[1] += BB; + buffer[2] += CC; + buffer[3] += DD; + } } unittest @@ -91,92 +200,44 @@ unittest import urt.encoding; MD5Context ctx; + + // empty md5_init(ctx); auto digest = md5_finalise(ctx); assert(digest == HexDecode!"d41d8cd98f00b204e9800998ecf8427e"); + // single string md5_init(ctx); md5_update(ctx, "Hello, World!"); digest = md5_finalise(ctx); assert(digest == HexDecode!"65a8e27d8879283831b664bd8b7f0ad4"); -} + // RFC 1321 test vectors + md5_init(ctx); + md5_update(ctx, "a"); + digest = md5_finalise(ctx); + assert(digest == HexDecode!"0cc175b9c0f1b6a831c399e269772661"); -private: - -__gshared immutable ubyte[] S = [ - 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, - 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, - 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, - 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 -]; - -__gshared immutable uint[] K = [ - 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, - 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, - 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, - 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, - 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, - 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, - 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, - 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, - 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, - 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, - 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, - 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, - 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, - 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, - 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, - 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 -]; - -// rotates a 32-bit word left by n bits -uint rotate_left(uint x, uint n) - => (x << n) | (x >> (32 - n)); - -// step on 512 bits of input with the main MD5 algorithm -void md5_step(ref uint[4] buffer, ref const uint[16] input) -{ - uint AA = buffer[0]; - uint BB = buffer[1]; - uint CC = buffer[2]; - uint DD = buffer[3]; - - uint E; - - uint j; + md5_init(ctx); + md5_update(ctx, "abc"); + digest = md5_finalise(ctx); + assert(digest == HexDecode!"900150983cd24fb0d6963f7d28e17f72"); - foreach (uint i; 0 .. 64) - { - final switch(i / 16) - { - case 0: - E = ((BB & CC) | (~BB & DD)); // F(BB, CC, DD); uint F(uint X, uint Y, uint Z) => ((X & Y) | (~X & Z)); - j = i; - break; - case 1: - E = ((BB & DD) | (CC & ~DD)); // G(BB, CC, DD); uint G(uint X, uint Y, uint Z) => ((X & Z) | (Y & ~Z)); - j = ((i*5) + 1) % 16; - break; - case 2: - E = (BB ^ CC ^ DD); // H(BB, CC, DD); uint H(uint X, uint Y, uint Z) => (X ^ Y ^ Z); - j = ((i*3) + 5) % 16; - break; - case 3: - E = (CC ^ (BB | ~DD)); // I(BB, CC, DD); uint I(uint X, uint Y, uint Z) => (Y ^ (X | ~Z)); - j = (i*7) % 16; - break; - } + md5_init(ctx); + md5_update(ctx, "message digest"); + digest = md5_finalise(ctx); + assert(digest == HexDecode!"f96b697d7cb7938d525a2f31aaf161d0"); - uint temp = DD; - DD = CC; - CC = BB; - BB = BB + rotate_left(AA + E + K[i] + input[j], S[i]); - AA = temp; - } + // progressive: split across multiple updates + md5_init(ctx); + md5_update(ctx, "Hello, "); + md5_update(ctx, "World!"); + digest = md5_finalise(ctx); + assert(digest == HexDecode!"65a8e27d8879283831b664bd8b7f0ad4"); - buffer[0] += AA; - buffer[1] += BB; - buffer[2] += CC; - buffer[3] += DD; + // data spanning multiple 64-byte blocks + md5_init(ctx); + md5_update(ctx, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"); + digest = md5_finalise(ctx); + assert(digest == HexDecode!"57edf4a22be3c955ac49da2e2107b67a"); } diff --git a/src/urt/digest/sha.d b/src/urt/digest/sha.d index 186ede5..ca36a8c 100644 --- a/src/urt/digest/sha.d +++ b/src/urt/digest/sha.d @@ -5,134 +5,302 @@ import urt.endian; nothrow @nogc: // pure -struct SHA1Context +version (Espressif) { - enum DigestBits = 160; + // Uses the hardware SHA accelerator via mask ROM functions. + // The ROM API is consistent across S2/S3/C3/C6/H2 but differs on original ESP32. + version (ESP32) { static assert(false, "Original ESP32 has a different SHA ROM API; not yet supported"); } - enum DigestLen = DigestBits / 8; - enum DigestElements = DigestBits / 32; + private extern(C) nothrow @nogc + { + void ets_sha_enable(); + void ets_sha_disable(); + int ets_sha_init(SHA_CTX* ctx, SHA_TYPE type); + int ets_sha_starts(SHA_CTX* ctx, ushort sha512_t); + void ets_sha_update(SHA_CTX* ctx, const ubyte* input, uint input_bytes, bool update_ctx); + int ets_sha_finish(SHA_CTX* ctx, ubyte* output); + } - ubyte[64] data; - ulong bitlen; - uint datalen; - uint[DigestElements] state; + private enum SHA_TYPE : int { SHA1 = 0, SHA2_224, SHA2_256 } - alias transform = sha1_transform; + private struct SHA_CTX + { + bool start; + bool in_hardware; + SHA_TYPE type; + uint[16] state; + ubyte[128] buffer; + uint[4] total_bits; + } - enum uint[DigestElements] initState = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 ]; + struct SHA1Context + { + enum DigestBits = 160; + enum DigestLen = DigestBits / 8; + private SHA_CTX ctx; + } - __gshared immutable uint[4] K = [ 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 ]; -} + struct SHA224Context + { + enum DigestBits = 224; + enum DigestLen = DigestBits / 8; + private SHA_CTX ctx; + } -struct SHA256Context -{ - enum DigestBits = 256; - - enum DigestLen = DigestBits / 8; - enum DigestElements = DigestBits / 32; - - ubyte[64] data; - ulong bitlen; - uint datalen; - uint[DigestElements] state; - - alias transform = sha256_transform; - - enum uint[DigestElements] initState = [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, - 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ]; - - __gshared immutable uint[64] K = [ - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 - ]; -} + struct SHA256Context + { + enum DigestBits = 256; + enum DigestLen = DigestBits / 8; + private SHA_CTX ctx; + } + void sha_init(Context)(ref Context ctx) + { + ets_sha_enable(); + static if (is(Context == SHA1Context)) + ets_sha_init(&ctx.ctx, SHA_TYPE.SHA1); + else static if (is(Context == SHA224Context)) + ets_sha_init(&ctx.ctx, SHA_TYPE.SHA2_224); + else + ets_sha_init(&ctx.ctx, SHA_TYPE.SHA2_256); + ets_sha_starts(&ctx.ctx, 0); + } -void sha_init(Context)(ref Context ctx) -{ - ctx.datalen = 0; - ctx.bitlen = 0; - ctx.state = Context.initState; -} + void sha_update(Context)(ref Context ctx, const void[] input) + { + ets_sha_update(&ctx.ctx, cast(const ubyte*)input.ptr, cast(uint)input.length, true); + } -void sha_update(Context)(ref Context ctx, const void[] input) + ubyte[Context.DigestLen] sha_finalise(Context)(ref Context ctx) + { + ubyte[Context.DigestLen] digest; + ets_sha_finish(&ctx.ctx, digest.ptr); + ets_sha_disable(); + return digest; + } +} +else { - const(ubyte)[] data = cast(ubyte[])input; + struct SHA1Context + { + enum DigestBits = 160; + + enum DigestLen = DigestBits / 8; + enum DigestElements = DigestBits / 32; + + ubyte[64] data; + ulong bitlen; + uint datalen; + uint[DigestElements] state; + + alias transform = sha1_transform; + + enum uint[DigestElements] initState = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 ]; + + __gshared immutable uint[4] K = [ 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 ]; + } - while (data.length > 0) + struct SHA224Context { - if (ctx.datalen + data.length < 64) - { - ctx.data[ctx.datalen .. ctx.datalen + data.length] = data[]; - ctx.datalen += cast(uint)data.length; - break; - } + enum DigestBits = 224; - ctx.data[ctx.datalen .. 64] = data[0 .. 64 - ctx.datalen]; - data = data[64 - ctx.datalen .. $]; - ctx.bitlen += 512; - ctx.datalen = 0; - Context.transform(ctx, ctx.data); + enum DigestLen = DigestBits / 8; + enum DigestElements = DigestBits / 32; + enum StateElements = 256 / 32; + + ubyte[64] data; + ulong bitlen; + uint datalen; + uint[StateElements] state; + + alias transform = sha256_transform; + + enum uint[StateElements] initState = [ 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, + 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 ]; } -} -ubyte[Context.DigestLen] sha_finalise(Context)(ref Context ctx) -{ - uint i = ctx.datalen; + struct SHA256Context + { + enum DigestBits = 256; + + enum DigestLen = DigestBits / 8; + enum DigestElements = DigestBits / 32; + enum StateElements = DigestBits / 32; + + ubyte[64] data; + ulong bitlen; + uint datalen; + uint[StateElements] state; + + alias transform = sha256_transform; + + enum uint[StateElements] initState = [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ]; + + __gshared immutable uint[64] K = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ]; + } - // Pad whatever data is left in the buffer. - ctx.data[i++] = 0x80; - if (ctx.datalen >= 56) + void sha_init(Context)(ref Context ctx) { - ctx.data[i .. 64] = 0x00; - Context.transform(ctx, ctx.data); - i = 0; + ctx.datalen = 0; + ctx.bitlen = 0; + ctx.state = Context.initState; } - ctx.data[i .. 56] = 0x00; - // Append to the padding the total message's length in bits and transform. - ctx.bitlen += ctx.datalen * 8; - ctx.data[56..64] = ctx.bitlen.nativeToBigEndian; - Context.transform(ctx, ctx.data); + void sha_update(Context)(ref Context ctx, const void[] input) + { + const(ubyte)[] data = cast(ubyte[])input; + + while (data.length > 0) + { + if (ctx.datalen + data.length < 64) + { + ctx.data[ctx.datalen .. ctx.datalen + data.length] = data[]; + ctx.datalen += cast(uint)data.length; + break; + } + + ctx.data[ctx.datalen .. 64] = data[0 .. 64 - ctx.datalen]; + data = data[64 - ctx.datalen .. $]; + ctx.bitlen += 512; + ctx.datalen = 0; + Context.transform(ctx, ctx.data); + } + } - // Since this implementation uses little endian byte ordering and SHA uses big endian, - // reverse all the bytes when copying the final state to the output hash. - uint[Context.DigestElements] digest = void; - foreach (uint j; 0 .. Context.DigestElements) - digest[j] = byte_reverse(ctx.state[j]); + ubyte[Context.DigestLen] sha_finalise(Context)(ref Context ctx) + { + uint i = ctx.datalen; - return cast(ubyte[Context.DigestLen])digest; + ctx.data[i++] = 0x80; + if (ctx.datalen >= 56) + { + ctx.data[i .. 64] = 0x00; + Context.transform(ctx, ctx.data); + i = 0; + } + ctx.data[i .. 56] = 0x00; + + ctx.bitlen += ctx.datalen * 8; + ctx.data[56..64] = ctx.bitlen.nativeToBigEndian; + Context.transform(ctx, ctx.data); + + uint[Context.DigestElements] digest = void; + foreach (uint j; 0 .. Context.DigestElements) + digest[j] = byte_reverse(ctx.state[j]); + + return cast(ubyte[Context.DigestLen])digest; + } } unittest { import urt.encoding; + // SHA-1 SHA1Context ctx; + + // empty sha_init(ctx); auto digest = sha_finalise(ctx); assert(digest == HexDecode!"da39a3ee5e6b4b0d3255bfef95601890afd80709"); + // single string sha_init(ctx); sha_update(ctx, "Hello, World!"); digest = sha_finalise(ctx); assert(digest == HexDecode!"0a0a9f2a6772942557ab5355d76af442f8f65e01"); + // FIPS 180 test vectors + sha_init(ctx); + sha_update(ctx, "abc"); + digest = sha_finalise(ctx); + assert(digest == HexDecode!"a9993e364706816aba3e25717850c26c9cd0d89d"); + + // progressive + sha_init(ctx); + sha_update(ctx, "Hello, "); + sha_update(ctx, "World!"); + digest = sha_finalise(ctx); + assert(digest == HexDecode!"0a0a9f2a6772942557ab5355d76af442f8f65e01"); + + // multi-block (>64 bytes) + sha_init(ctx); + sha_update(ctx, "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + digest = sha_finalise(ctx); + assert(digest == HexDecode!"84983e441c3bd26ebaae4aa1f95129e5e54670f1"); + + // SHA-224 + SHA224Context ctx3; + + // empty + sha_init(ctx3); + auto digest3 = sha_finalise(ctx3); + assert(digest3 == HexDecode!"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"); + + // FIPS 180 test vector + sha_init(ctx3); + sha_update(ctx3, "abc"); + digest3 = sha_finalise(ctx3); + assert(digest3 == HexDecode!"23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7"); + + // progressive + sha_init(ctx3); + sha_update(ctx3, "Hello, "); + sha_update(ctx3, "World!"); + auto digest3b = sha_finalise(ctx3); + sha_init(ctx3); + sha_update(ctx3, "Hello, World!"); + digest3 = sha_finalise(ctx3); + assert(digest3 == digest3b); + + // multi-block + sha_init(ctx3); + sha_update(ctx3, "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + digest3 = sha_finalise(ctx3); + assert(digest3 == HexDecode!"75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525"); + + // SHA-256 SHA256Context ctx2; + + // empty sha_init(ctx2); auto digest2 = sha_finalise(ctx2); assert(digest2 == HexDecode!"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + // single string sha_init(ctx2); sha_update(ctx2, "Hello, World!"); digest2 = sha_finalise(ctx2); assert(digest2 == HexDecode!"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"); + + // FIPS 180 test vectors + sha_init(ctx2); + sha_update(ctx2, "abc"); + digest2 = sha_finalise(ctx2); + assert(digest2 == HexDecode!"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"); + + // progressive + sha_init(ctx2); + sha_update(ctx2, "Hello, "); + sha_update(ctx2, "World!"); + digest2 = sha_finalise(ctx2); + assert(digest2 == HexDecode!"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"); + + // multi-block + sha_init(ctx2); + sha_update(ctx2, "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + digest2 = sha_finalise(ctx2); + assert(digest2 == HexDecode!"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"); } @@ -141,7 +309,7 @@ private: uint ROTLEFT(uint a, uint b) => (a << b) | (a >> (32 - b)); uint ROTRIGHT(uint a, uint b) => (a >> b) | (a << (32 - b)); -void sha1_transform(ref SHA1Context ctx, const ubyte[] data) +void sha1_transform(Context)(ref Context ctx, const ubyte[] data) { uint a, b, c, d, e, i, j, t; uint[80] m = void; @@ -204,7 +372,7 @@ void sha1_transform(ref SHA1Context ctx, const ubyte[] data) ctx.state[4] += e; } -void sha256_transform(ref SHA256Context ctx, const ubyte[] data) +void sha256_transform(Context)(ref Context ctx, const ubyte[] data) { static uint CH(uint x, uint y, uint z) => (x & y) ^ (~x & z); static uint MAJ(uint x, uint y, uint z) => (x & y) ^ (x & z) ^ (y & z); diff --git a/src/urt/hash.d b/src/urt/hash.d index 2a94fb8..d519d21 100644 --- a/src/urt/hash.d +++ b/src/urt/hash.d @@ -57,98 +57,137 @@ unittest } -uint adler32(const void[] data, uint init = 1) +version (Espressif) { - enum A32_BASE = 65521; + private extern(C) uint mz_adler32(uint adler, const ubyte* ptr, size_t buf_len) nothrow @nogc; - assert(data.length <= int.max, "Data length must be less than or equal to int.max"); + uint adler32(const void[] data, uint init = 1) + { + return mz_adler32(init, cast(const ubyte*)data.ptr, data.length); + } +} +else +{ + uint adler32(const void[] data, uint init = 1) + { + enum A32_BASE = 65521; - uint s1 = init & 0xFFFF; - uint s2 = (init >> 16) & 0xFFFF; + assert(data.length <= int.max, "Data length must be less than or equal to int.max"); - version (SmallSize) - { - foreach (ubyte b; cast(ubyte[])data) + uint s1 = init & 0xFFFF; + uint s2 = (init >> 16) & 0xFFFF; + + version (SmallSize) { - version (BranchIsFasterThanMod) + foreach (ubyte b; cast(ubyte[])data) { - s1 += b; - s2 += s1; - if (s1 >= A32_BASE) - s1 -= A32_BASE; - if (s2 >= A32_BASE) - s2 -= A32_BASE; + version (BranchIsFasterThanMod) + { + s1 += b; + s2 += s1; + if (s1 >= A32_BASE) + s1 -= A32_BASE; + if (s2 >= A32_BASE) + s2 -= A32_BASE; + } + else + { + s1 = (s1 + b) % A32_BASE; + s2 = (s2 + s1) % A32_BASE; + } } - else + } + else + { + enum A32_NMAX = 5552; + + const(ubyte)* buf = cast(const ubyte*)data.ptr; + int length = cast(int)data.length; + + while (length > 0) { - s1 = (s1 + b) % A32_BASE; - s2 = (s2 + s1) % A32_BASE; + int k = length < A32_NMAX ? length : A32_NMAX; + int i; + + for (i = k / 16; i; --i, buf += 16) + { + s1 += buf[0]; + s2 += s1; + s1 += buf[1]; + s2 += s1; + s1 += buf[2]; + s2 += s1; + s1 += buf[3]; + s2 += s1; + s1 += buf[4]; + s2 += s1; + s1 += buf[5]; + s2 += s1; + s1 += buf[6]; + s2 += s1; + s1 += buf[7]; + s2 += s1; + + s1 += buf[8]; + s2 += s1; + s1 += buf[9]; + s2 += s1; + s1 += buf[10]; + s2 += s1; + s1 += buf[11]; + s2 += s1; + s1 += buf[12]; + s2 += s1; + s1 += buf[13]; + s2 += s1; + s1 += buf[14]; + s2 += s1; + s1 += buf[15]; + s2 += s1; + } + + for (i = k & 0xF; i; --i) + { + s1 += *buf++; + s2 += s1; + } + + s1 %= A32_BASE; + s2 %= A32_BASE; + + length -= k; } } + + return (s2 << 16) | s1; } - else - { - enum A32_NMAX = 5552; +} - const(ubyte)* buf = cast(const ubyte*)data.ptr; - int length = cast(int)data.length; +unittest +{ + // empty data + assert(adler32(null) == 1); - while (length > 0) - { - int k = length < A32_NMAX ? length : A32_NMAX; - int i; + // RFC 1950 test vectors + assert(adler32("Wikipedia") == 0x11E6_0398); - for (i = k / 16; i; --i, buf += 16) - { - s1 += buf[0]; - s2 += s1; - s1 += buf[1]; - s2 += s1; - s1 += buf[2]; - s2 += s1; - s1 += buf[3]; - s2 += s1; - s1 += buf[4]; - s2 += s1; - s1 += buf[5]; - s2 += s1; - s1 += buf[6]; - s2 += s1; - s1 += buf[7]; - s2 += s1; - - s1 += buf[8]; - s2 += s1; - s1 += buf[9]; - s2 += s1; - s1 += buf[10]; - s2 += s1; - s1 += buf[11]; - s2 += s1; - s1 += buf[12]; - s2 += s1; - s1 += buf[13]; - s2 += s1; - s1 += buf[14]; - s2 += s1; - s1 += buf[15]; - s2 += s1; - } + // single byte + assert(adler32("a") == 0x0062_0062); - for (i = k & 0xF; i; --i) - { - s1 += *buf++; - s2 += s1; - } + // full string + assert(adler32("Hello, World!") == 0x1F9E_046A); - s1 %= A32_BASE; - s2 %= A32_BASE; + // progressive accumulation + uint a1 = adler32("Hello, "); + assert(a1 == 0x09EE_0241); + assert(adler32("World!", a1) == 0x1F9E_046A); - length -= k; - } - } + // all zeros + ubyte[1024] zeros; + assert(adler32(zeros[]) == 0x0400_0001); - return (s2 << 16) | s1; + // known value: "123456789" + assert(adler32("123456789") == 0x091E_01DE); } diff --git a/src/urt/package.d b/src/urt/package.d index d0f2037..a4f8101 100644 --- a/src/urt/package.d +++ b/src/urt/package.d @@ -88,6 +88,13 @@ extern(C) int main(int argc, char** argv) nothrow @nogc @trusted flush!(WriteTarget.stderr)(); run_module_dtors(modules); int result = executed > 0 && passed == executed ? 0 : 1; + + version (FreeStanding) + { + import urt.internal.stdc.stdlib : abort; + writeln_err("Process restarting..."); + abort(); + } } else { From 6f1558c361c93963b1e02dcff0a61101f0ac069b Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 7 Apr 2026 23:49:35 +1000 Subject: [PATCH 116/138] ESP32 main-loop is running. --- src/object.d | 6 ++++-- src/sys/esp32/main.c | 42 +++++++++++++++++++++++++++++++++++++++++ src/sys/esp32/ow_shim.c | 39 ++++++++++++++++---------------------- src/urt/socket.d | 27 ++++++++++++++++++++++---- 4 files changed, 85 insertions(+), 29 deletions(-) diff --git a/src/object.d b/src/object.d index b9e7081..cc22652 100644 --- a/src/object.d +++ b/src/object.d @@ -115,14 +115,16 @@ public import urt.util : min, max, swap; static if (__VERSION__ >= 2113) { + static import urt.atomic; + alias _d_atomicOp = urt.atomic.atomic_op; + static import urt.math; alias _d_pow = urt.math.pow; auto _d_sqrt(T)(T x) { // TODO: should we have a `float` one? - import urt.math : sqrt; - return sqrt(x); + return urt.math.sqrt(x); } } diff --git a/src/sys/esp32/main.c b/src/sys/esp32/main.c index 7573964..06b8b74 100644 --- a/src/sys/esp32/main.c +++ b/src/sys/esp32/main.c @@ -5,15 +5,57 @@ #include "esp_netif.h" #include "esp_event.h" +#include "nvs_flash.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +extern int ets_printf(const char *fmt, ...); extern int main(int argc, char **argv); +// Software watchdog: reboots if main task stops feeding. +// Call ow_watchdog_feed() from the main loop each frame. +static volatile uint32_t watchdog_counter; + +void ow_watchdog_feed(void) +{ + watchdog_counter++; +} + +static void watchdog_task(void *arg) +{ + const int timeout_ms = 5000; + for (;;) + { + uint32_t before = watchdog_counter; + vTaskDelay(pdMS_TO_TICKS(timeout_ms)); + if (watchdog_counter == before) + { + ets_printf("WATCHDOG: main loop stalled for %ds, aborting\n", + timeout_ms / 1000); + abort(); + } + } +} + void app_main(void) { + // Initialize NVS -- required by WiFi for calibration data storage. + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) + { + nvs_flash_erase(); + nvs_flash_init(); + } + // Initialize lwIP and event loop early -- D code may open sockets // during startup before any WiFi/Ethernet interface is created. esp_netif_init(); esp_event_loop_create_default(); + xTaskCreate(watchdog_task, "ow_wdt", 2048, NULL, 1, NULL); + + // Delay so early boot logs are visible on USB Serial/JTAG + vTaskDelay(pdMS_TO_TICKS(3000)); + main(0, (char **)0); } diff --git a/src/sys/esp32/ow_shim.c b/src/sys/esp32/ow_shim.c index 7445417..7915150 100644 --- a/src/sys/esp32/ow_shim.c +++ b/src/sys/esp32/ow_shim.c @@ -172,7 +172,7 @@ int32_t ow_uart_flush(int port) static esp_netif_t *ow_wifi_netif_sta; static esp_netif_t *ow_wifi_netif_ap; -static bool ow_wifi_initialized; +static int ow_wifi_refcount; typedef void (*ow_wifi_event_cb_t)(int event_id, void *data, int data_len); @@ -212,34 +212,35 @@ static void ow_wifi_event_handler(void *arg, esp_event_base_t base, int ow_wifi_init(void) { - if (ow_wifi_initialized) - return 1; - - esp_err_t err = esp_netif_init(); - if (err != ESP_OK) - return 0; - - err = esp_event_loop_create_default(); - if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) + if (ow_wifi_refcount++ > 0) return 0; wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - err = esp_wifi_init(&cfg); + esp_err_t err = esp_wifi_init(&cfg); if (err != ESP_OK) - return 0; + { + ow_wifi_refcount--; + return (int)err; + } + + // Create netifs before registering RX callbacks or starting WiFi, + // so esp_netif_receive() always has valid targets. + if (!ow_wifi_netif_sta) + ow_wifi_netif_sta = esp_netif_create_default_wifi_sta(); + if (!ow_wifi_netif_ap) + ow_wifi_netif_ap = esp_netif_create_default_wifi_ap(); esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &ow_wifi_event_handler, NULL); esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &ow_wifi_event_handler, NULL); - ow_wifi_initialized = true; - return 1; + return 0; } void ow_wifi_deinit(void) { - if (!ow_wifi_initialized) + if (--ow_wifi_refcount > 0) return; esp_wifi_stop(); @@ -255,8 +256,6 @@ void ow_wifi_deinit(void) esp_netif_destroy(ow_wifi_netif_ap); ow_wifi_netif_ap = NULL; } - - ow_wifi_initialized = false; } int ow_wifi_set_mode(int mode) @@ -277,9 +276,6 @@ int ow_wifi_stop(void) int ow_wifi_sta_config(const char *ssid, const char *password, const uint8_t *bssid) { - if (!ow_wifi_netif_sta) - ow_wifi_netif_sta = esp_netif_create_default_wifi_sta(); - wifi_config_t cfg = {0}; if (ssid) { @@ -315,9 +311,6 @@ int ow_wifi_sta_disconnect(void) int ow_wifi_ap_config(const char *ssid, const char *password, uint8_t channel, uint8_t max_conn, uint8_t hidden) { - if (!ow_wifi_netif_ap) - ow_wifi_netif_ap = esp_netif_create_default_wifi_ap(); - wifi_config_t cfg = {0}; if (ssid) { diff --git a/src/urt/socket.d b/src/urt/socket.d index 017e737..8dc0ff0 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -36,6 +36,7 @@ else version (Posix) version = BSDSockets; version = HasUnixSocket; version = HasIPv6; + version = Errno; alias SocketHandle = int; enum INVALID_SOCKET = -1; @@ -52,6 +53,8 @@ else version (lwIP) { // lwIP BSD socket API -- constants and structs match lwIP defaults version = BSDSockets; + version = HasIPv6; + version = Errno; alias SocketHandle = int; enum INVALID_SOCKET = -1; @@ -152,8 +155,11 @@ else version (lwIP) void ow_lwip_freeaddrinfo(addrinfo*); int lwip_close(int); int lwip_fcntl(int, int, int); + int lwip_ioctl(int, int, void*); } + enum FIONBIO = 0x8004667e; // _IOW('f', 126, unsigned long) + // Aliases so the rest of the codebase uses POSIX names alias _poll = lwip_poll; alias socket = lwip_socket; @@ -175,6 +181,7 @@ else version (lwIP) alias freeaddrinfo = ow_lwip_freeaddrinfo; alias _close = lwip_close; alias fcntl = lwip_fcntl; + alias ioctlsocket = lwip_ioctl; int gethostname(char*, size_t) nothrow @nogc { return -1; } } @@ -721,8 +728,17 @@ Result set_socket_option(Socket socket, SocketOption option, const(void)* optval } else version (BSDSockets) { - int flags = fcntl(socket.handle, F_GETFL, 0); - r.system_code = fcntl(socket.handle, F_SETFL, value ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)); + version (lwIP) + { + // lwIP's fcntl has quirks with F_SETFL; use ioctlsocket(FIONBIO) instead. + int opt = value ? 1 : 0; + r.system_code = ioctlsocket(socket.handle, FIONBIO, &opt); + } + else + { + int flags = fcntl(socket.handle, F_GETFL, 0); + r.system_code = fcntl(socket.handle, F_SETFL, value ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)); + } } else assert(false, "Not implemented!"); @@ -857,7 +873,7 @@ Result get_socket_option(Socket socket, SocketOption option, void* output, size_ // determine the option 'level' OptLevel level = get_optlevel(option); - version (HasIPv6) + version (HasIPv6) {} else assert(level != OptLevel.ipv6 && level != OptLevel.icmpv6, "Platform does not support IPv6!"); // platforms don't all agree on option data formats! @@ -1220,8 +1236,10 @@ Result socket_getlasterror() { version (Windows) return Result(WSAGetLastError()); - else + else version (Errno) return errno_result(); + else + static assert(false, "socket_getlasterror not implemented for this platform"); } Result get_socket_error(Socket socket) @@ -1265,6 +1283,7 @@ SocketResult socket_result(Result result) } else version (Errno) { + import urt.internal.stdc.errno; static if (EAGAIN != EWOULDBLOCK) if (result.system_code == EAGAIN) return SocketResult.would_block; From 40880e1450104a1a6255245b18b68e9004cb960d Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 8 Apr 2026 21:23:32 +1000 Subject: [PATCH 117/138] Add a callback-based socket implementation, which the frontend will hook when it introduces an IP stack. --- src/urt/socket.d | 1388 +++++++++++++++++++++++++++------------------- 1 file changed, 806 insertions(+), 582 deletions(-) diff --git a/src/urt/socket.d b/src/urt/socket.d index 8dc0ff0..b731b58 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -6,7 +6,46 @@ public import urt.mem; public import urt.result; public import urt.time; -version (Windows) +version (BL808) + version = SocketCallbacks; + +version (SocketCallbacks) +{ + alias SocketHandle = int; + enum INVALID_SOCKET = -1; + + struct SocketBackend + { + nothrow @nogc: + SocketResult function(AddressFamily, SocketType, Protocol, out Socket) create; + SocketResult function(Socket) close; + SocketResult function(Socket, ref const InetAddress) bind; + SocketResult function(Socket, uint) listen; + SocketResult function(Socket, ref const InetAddress) connect; + SocketResult function(Socket, out Socket, InetAddress*) accept; + SocketResult function(Socket, SocketShutdownMode) shutdown; + SocketResult function(Socket, const(InetAddress)*, MsgFlags, const(void[])[], size_t*) sendmsg; + SocketResult function(Socket, void[], MsgFlags, size_t*) recv; + SocketResult function(Socket, void[], MsgFlags, InetAddress*, size_t*) recvfrom; + SocketResult function(PollFd[], Duration, out uint) poll; + SocketResult function(Socket, SocketOption, const(void)*, size_t) set_option; + SocketResult function(Socket, SocketOption, void*, size_t) get_option; + SocketResult function(Socket, out InetAddress) get_peer_name; + SocketResult function(Socket, out InetAddress) get_socket_name; + SocketResult function(char*, size_t) get_hostname; + SocketResult function(const(char)[], const(char)[], AddressInfo*, AddressInfoResolver*) get_address_info; + bool function(AddressInfoResolver*, out AddressInfo) next_address; + void function(AddressInfoResolver*) free_address_info; + } + + __gshared SocketBackend* _socket_backend; + + void register_socket_backend(SocketBackend* backend) nothrow @nogc + { + _socket_backend = backend; + } +} +else version (Windows) { // TODO: this is in core.sys.windows.winsock2; why do I need it here? pragma(lib, "ws2_32"); @@ -325,116 +364,165 @@ private: Result create_socket(AddressFamily af, SocketType type, Protocol proto, out Socket socket) { - version (HasUnixSocket) {} else - assert(af != AddressFamily.unix, "Unix sockets not supported on this platform!"); + version (SocketCallbacks) + return Result(_socket_backend.create(af, type, proto, socket)); + else + { + version (HasUnixSocket) {} else + assert(af != AddressFamily.unix, "Unix sockets not supported on this platform!"); - socket.handle = .socket(s_addressFamily[af], s_socketType[type], s_protocol[proto]); - if (socket == Socket.invalid) - return socket_getlasterror(); + socket.handle = .socket(s_addressFamily[af], s_socketType[type], s_protocol[proto]); + if (socket == Socket.invalid) + return socket_getlasterror(); - return Result.success; + return Result.success; + } } Result close(Socket socket) { - int result; - version (Windows) - result = closesocket(socket.handle); - else version (BSDSockets) - result = _close(socket.handle); + version (SocketCallbacks) + return Result(_socket_backend.close(socket)); else - assert(false, "Not implemented!"); - if (result < 0) - return socket_getlasterror(); - return Result.success; + { + int result; + version (Windows) + result = closesocket(socket.handle); + else version (BSDSockets) + result = _close(socket.handle); + else + assert(false, "Not implemented!"); + if (result < 0) + return socket_getlasterror(); + return Result.success; + } } Result shutdown(Socket socket, SocketShutdownMode how) { - int t = int(how); - switch (how) + version (SocketCallbacks) + return Result(_socket_backend.shutdown(socket, how)); + else { - version (Windows) + int t = int(how); + switch (how) { - case SocketShutdownMode.read: t = SD_RECEIVE; break; - case SocketShutdownMode.write: t = SD_SEND; break; - case SocketShutdownMode.read_write: t = SD_BOTH; break; - } - else version (BSDSockets) - { - case SocketShutdownMode.read: t = SHUT_RD; break; - case SocketShutdownMode.write: t = SHUT_WR; break; - case SocketShutdownMode.read_write: t = SHUT_RDWR; break; + version (Windows) + { + case SocketShutdownMode.read: t = SD_RECEIVE; break; + case SocketShutdownMode.write: t = SD_SEND; break; + case SocketShutdownMode.read_write: t = SD_BOTH; break; + } + else version (BSDSockets) + { + case SocketShutdownMode.read: t = SHUT_RD; break; + case SocketShutdownMode.write: t = SHUT_WR; break; + case SocketShutdownMode.read_write: t = SHUT_RDWR; break; + } + default: + assert(false, "Invalid `how`"); } - default: - assert(false, "Invalid `how`"); - } - if (_shutdown(socket.handle, t) < 0) - return socket_getlasterror(); - return Result.success; + if (_shutdown(socket.handle, t) < 0) + return socket_getlasterror(); + return Result.success; + } } Result bind(Socket socket, ref const InetAddress address) { - ubyte[512] buffer = void; - size_t addr_len; - sockaddr* sock_addr = make_sockaddr(address, buffer, addr_len); - assert(sock_addr, "Invalid socket address"); + version (SocketCallbacks) + return Result(_socket_backend.bind(socket, address)); + else + { + ubyte[512] buffer = void; + size_t addr_len; + sockaddr* sock_addr = make_sockaddr(address, buffer, addr_len); + assert(sock_addr, "Invalid socket address"); - if (_bind(socket.handle, sock_addr, cast(int)addr_len) < 0) - return socket_getlasterror(); - return Result.success; + if (_bind(socket.handle, sock_addr, cast(int)addr_len) < 0) + return socket_getlasterror(); + return Result.success; + } } Result listen(Socket socket, uint backlog = -1) { - if (_listen(socket.handle, int(backlog & 0x7FFFFFFF)) < 0) - return socket_getlasterror(); - return Result.success; + version (SocketCallbacks) + return Result(_socket_backend.listen(socket, backlog)); + else + { + if (_listen(socket.handle, int(backlog & 0x7FFFFFFF)) < 0) + return socket_getlasterror(); + return Result.success; + } } Result connect(Socket socket, ref const InetAddress address) { - ubyte[512] buffer = void; - size_t addr_len; - sockaddr* sock_addr = make_sockaddr(address, buffer, addr_len); - assert(sock_addr, "Invalid socket address"); + version (SocketCallbacks) + return Result(_socket_backend.connect(socket, address)); + else + { + ubyte[512] buffer = void; + size_t addr_len; + sockaddr* sock_addr = make_sockaddr(address, buffer, addr_len); + assert(sock_addr, "Invalid socket address"); - if (_connect(socket.handle, sock_addr, cast(int)addr_len) < 0) - return socket_getlasterror(); - return Result.success; + if (_connect(socket.handle, sock_addr, cast(int)addr_len) < 0) + return socket_getlasterror(); + return Result.success; + } } Result accept(Socket socket, out Socket connection, InetAddress* remote_address = null, InetAddress* local_address = null) { - char[sockaddr_storage.sizeof] buffer = void; - sockaddr* addr = cast(sockaddr*)buffer.ptr; - socklen_t size = buffer.sizeof; - - connection.handle = _accept(socket.handle, addr, &size); - if (connection == Socket.invalid) - return socket_getlasterror(); - if (remote_address) - *remote_address = make_InetAddress(addr); - if (local_address) + version (SocketCallbacks) + { + Result r = Result(_socket_backend.accept(socket, connection, remote_address)); + if (!r) + return r; + if (local_address) + return get_socket_name(connection, *local_address); + return Result.success; + } + else { - if (getsockname(connection.handle, addr, &size) < 0) + char[sockaddr_storage.sizeof] buffer = void; + sockaddr* addr = cast(sockaddr*)buffer.ptr; + socklen_t size = buffer.sizeof; + + connection.handle = _accept(socket.handle, addr, &size); + if (connection == Socket.invalid) return socket_getlasterror(); - *local_address = make_InetAddress(addr); + if (remote_address) + *remote_address = make_InetAddress(addr); + if (local_address) + { + if (getsockname(connection.handle, addr, &size) < 0) + return socket_getlasterror(); + *local_address = make_InetAddress(addr); + } + // platforms are inconsistent regarding whether accept inherits the listening socket's blocking mode + // for consistentency, we always set blocking on the accepted socket + connection.set_socket_option(SocketOption.non_blocking, false); + return Result.success; } - // platforms are inconsistent regarding whether accept inherits the listening socket's blocking mode - // for consistentency, we always set blocking on the accepted socket - connection.set_socket_option(SocketOption.non_blocking, false); - return Result.success; } Result send(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.none, size_t* bytes_sent = null) - => send(socket, flags, bytes_sent, (&message)[0..1]); +{ + version (SocketCallbacks) + return sendmsg(socket, null, flags, null, bytes_sent, (&message)[0..1]); + else + return send(socket, flags, bytes_sent, (&message)[0..1]); +} Result send(Socket socket, MsgFlags flags, size_t* bytes_sent, const void[][] buffers...) { - version (Windows) + version (SocketCallbacks) + return sendmsg(socket, null, flags, null, bytes_sent, buffers); + else version (Windows) { uint sent; WSABUF[32] bufs = void; @@ -465,7 +553,9 @@ Result send(Socket socket, MsgFlags flags, size_t* bytes_sent, const void[][] bu Result sendto(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.none, const InetAddress* address = null, size_t* bytes_sent = null) { - version (Windows) + version (SocketCallbacks) + return sendmsg(socket, address, flags, null, bytes_sent, (&message)[0..1]); + else version (Windows) return sendto(socket, address, bytes_sent, (&message)[0..1]); else return sendmsg(socket, address, flags, null, bytes_sent, (&message)[0..1]); @@ -473,7 +563,9 @@ Result sendto(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.no Result sendto(Socket socket, const InetAddress* address, size_t* bytes_sent, const void[][] buffers...) { - version (Windows) + version (SocketCallbacks) + return sendmsg(socket, address, MsgFlags.none, null, bytes_sent, buffers); + else version (Windows) { ubyte[sockaddr_storage.sizeof] tmp = void; size_t addr_len; @@ -513,490 +605,563 @@ Result sendto(Socket socket, const InetAddress* address, size_t* bytes_sent, con Result sendmsg(Socket socket, const InetAddress* address, MsgFlags flags, const(void)[] control, size_t* bytes_sent, const void[][] buffers) { - ubyte[sockaddr_storage.sizeof] tmp = void; - size_t addr_len; - sockaddr* sock_addr = null; - if (address) - { - sock_addr = make_sockaddr(*address, tmp, addr_len); - assert(sock_addr, "Invalid socket address"); - } - - version (Windows) + version (SocketCallbacks) + return Result(_socket_backend.sendmsg(socket, address, flags, buffers, bytes_sent)); + else { - uint sent; - WSAMSG msg; - WSABUF[32] bufs = void; - assert(buffers.length <= bufs.length, "Too many buffers!"); - - uint n = 0; - foreach(buffer; buffers) - { - if (buffer.length == 0) - continue; - assert(buffer.length <= uint.max, "Buffer too large!"); - bufs[n].buf = cast(char*)buffer.ptr; - bufs[n++].len = cast(uint)buffer.length; - } - if (n > 0) + ubyte[sockaddr_storage.sizeof] tmp = void; + size_t addr_len; + sockaddr* sock_addr = null; + if (address) { - msg.name = sock_addr; - msg.namelen = cast(int)addr_len; - msg.lpBuffers = bufs.ptr; - msg.dwBufferCount = n; - msg.Control.buf = cast(char*)control.ptr; - msg.Control.len = cast(uint)control.length; - msg.dwFlags = 0; - - int rc = WSASendMsg(socket.handle, &msg, /+map_message_flags(flags)+/ 0, &sent, null, null); // there are no meaningful flags on Windows - if (rc == SOCKET_ERROR) - return socket_getlasterror(); + sock_addr = make_sockaddr(*address, tmp, addr_len); + assert(sock_addr, "Invalid socket address"); } - } - else - { - ptrdiff_t sent; - msghdr hdr; - iovec[32] iov = void; - assert(buffers.length <= iov.length, "Too many buffers!"); - size_t n = 0; - foreach(buffer; buffers) + version (Windows) { - if (buffer.length == 0) - continue; - assert(buffer.length <= uint.max, "Buffer too large!"); - iov[n].iov_base = cast(void*)buffer.ptr; - iov[n++].iov_len = buffer.length; + uint sent; + WSAMSG msg; + WSABUF[32] bufs = void; + assert(buffers.length <= bufs.length, "Too many buffers!"); + + uint n = 0; + foreach(buffer; buffers) + { + if (buffer.length == 0) + continue; + assert(buffer.length <= uint.max, "Buffer too large!"); + bufs[n].buf = cast(char*)buffer.ptr; + bufs[n++].len = cast(uint)buffer.length; + } + if (n > 0) + { + msg.name = sock_addr; + msg.namelen = cast(int)addr_len; + msg.lpBuffers = bufs.ptr; + msg.dwBufferCount = n; + msg.Control.buf = cast(char*)control.ptr; + msg.Control.len = cast(uint)control.length; + msg.dwFlags = 0; + + int rc = WSASendMsg(socket.handle, &msg, /+map_message_flags(flags)+/ 0, &sent, null, null); // there are no meaningful flags on Windows + if (rc == SOCKET_ERROR) + return socket_getlasterror(); + } } - if (n > 0) + else { - hdr.msg_name = sock_addr; - hdr.msg_namelen = cast(socklen_t)addr_len; - hdr.msg_iov = iov.ptr; - hdr.msg_iovlen = cast(typeof(hdr.msg_iovlen))n; - hdr.msg_control = cast(void*)control.ptr; - hdr.msg_controllen = cast(typeof(hdr.msg_controllen))control.length; - hdr.msg_flags = 0; - - sent = _sendmsg(socket.handle, &hdr, map_message_flags(flags)); - if (sent < 0) - return socket_getlasterror(); + ptrdiff_t sent; + msghdr hdr; + iovec[32] iov = void; + assert(buffers.length <= iov.length, "Too many buffers!"); + + size_t n = 0; + foreach(buffer; buffers) + { + if (buffer.length == 0) + continue; + assert(buffer.length <= uint.max, "Buffer too large!"); + iov[n].iov_base = cast(void*)buffer.ptr; + iov[n++].iov_len = buffer.length; + } + if (n > 0) + { + hdr.msg_name = sock_addr; + hdr.msg_namelen = cast(socklen_t)addr_len; + hdr.msg_iov = iov.ptr; + hdr.msg_iovlen = cast(typeof(hdr.msg_iovlen))n; + hdr.msg_control = cast(void*)control.ptr; + hdr.msg_controllen = cast(typeof(hdr.msg_controllen))control.length; + hdr.msg_flags = 0; + + sent = _sendmsg(socket.handle, &hdr, map_message_flags(flags)); + if (sent < 0) + return socket_getlasterror(); + } } + if (bytes_sent) + *bytes_sent = sent; + return Result.success; } - if (bytes_sent) - *bytes_sent = sent; - return Result.success; } Result recv(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.none, size_t* bytes_received) { - Result r = Result.success; - ptrdiff_t bytes = _recv(socket.handle, buffer.ptr, cast(int)buffer.length, map_message_flags(flags)); - if (bytes > 0) - *bytes_received = bytes; + version (SocketCallbacks) + return Result(_socket_backend.recv(socket, buffer, flags, bytes_received)); else { - *bytes_received = 0; - if (bytes == 0) + Result r = Result.success; + ptrdiff_t bytes = _recv(socket.handle, buffer.ptr, cast(int)buffer.length, map_message_flags(flags)); + if (bytes > 0) + *bytes_received = bytes; + else { - // if we request 0 bytes, we receive 0 bytes, and it doesn't imply end-of-stream - if (buffer.length > 0) + *bytes_received = 0; + if (bytes == 0) { - // a graceful disconnection occurred - // TODO: !!! - r = ConnectionClosedResult; -// r = InternalResult(InternalCode.RemoteDisconnected); + // if we request 0 bytes, we receive 0 bytes, and it doesn't imply end-of-stream + if (buffer.length > 0) + { + // a graceful disconnection occurred + // TODO: !!! + r = ConnectionClosedResult; +// r = InternalResult(InternalCode.RemoteDisconnected); + } + } + else + { + Result error = socket_getlasterror(); + // TODO: Do we want a better way to distinguish between receiving a 0-length packet vs no-data (which looks like an error)? + // Is a zero-length packet possible to detect in TCP streams? Makes more sense for recvfrom... + SocketResult sr = socket_result(error); + if (sr != SocketResult.would_block) + r = error; } } - else - { - Result error = socket_getlasterror(); - // TODO: Do we want a better way to distinguish between receiving a 0-length packet vs no-data (which looks like an error)? - // Is a zero-length packet possible to detect in TCP streams? Makes more sense for recvfrom... - SocketResult sr = socket_result(error); - if (sr != SocketResult.would_block) - r = error; - } + return r; } - return r; } Result recvfrom(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.none, InetAddress* sender_address = null, size_t* bytes_received, InetAddress* local_address = null) { - char[sockaddr_storage.sizeof] addr_buffer = void; - sockaddr* addr = cast(sockaddr*)addr_buffer.ptr; - - if (local_address) + version (SocketCallbacks) { - version (Windows) - { - assert(WSARecvMsg, "WSARecvMsg not available!"); - - void[1500] ctrl = void; // HUGE BUFFER! - - WSABUF msg_buf; - msg_buf.buf = cast(char*)buffer.ptr; - msg_buf.len = cast(uint)buffer.length; - - WSAMSG msg; - msg.name = addr; - msg.namelen = addr_buffer.sizeof; - msg.lpBuffers = &msg_buf; - msg.dwBufferCount = 1; - msg.Control.buf = cast(char*)ctrl.ptr; - msg.Control.len = cast(uint)ctrl.length; - msg.dwFlags = 0; - uint bytes; - int r = WSARecvMsg(socket.handle, &msg, &bytes, null, null); - if (r == 0) - *bytes_received = bytes; - else - { - *bytes_received = 0; - goto fail; - } + assert(local_address is null, "local_address not supported on callback backend"); + return Result(_socket_backend.recvfrom(socket, buffer, flags, sender_address, bytes_received)); + } + else + { + char[sockaddr_storage.sizeof] addr_buffer = void; + sockaddr* addr = cast(sockaddr*)addr_buffer.ptr; - // parse the control messages - *local_address = InetAddress(); - for (WSACMSGHDR* c = WSA_CMSG_FIRSTHDR(&msg); c != null; c = WSA_CMSG_NXTHDR(&msg, c)) + if (local_address) + { + version (Windows) { - if (c.cmsg_level == IPPROTO_IP && c.cmsg_type == IP_PKTINFO) + assert(WSARecvMsg, "WSARecvMsg not available!"); + + void[1500] ctrl = void; // HUGE BUFFER! + + WSABUF msg_buf; + msg_buf.buf = cast(char*)buffer.ptr; + msg_buf.len = cast(uint)buffer.length; + + WSAMSG msg; + msg.name = addr; + msg.namelen = addr_buffer.sizeof; + msg.lpBuffers = &msg_buf; + msg.dwBufferCount = 1; + msg.Control.buf = cast(char*)ctrl.ptr; + msg.Control.len = cast(uint)ctrl.length; + msg.dwFlags = 0; + uint bytes; + int r = WSARecvMsg(socket.handle, &msg, &bytes, null, null); + if (r == 0) + *bytes_received = bytes; + else { - IN_PKTINFO* pk = cast(IN_PKTINFO*)WSA_CMSG_DATA(c); - *local_address = InetAddress(make_IPAddr(pk.ipi_addr), 0); // TODO: be nice to populate the listening port... - // pk.ipi_ifindex = receiving interface index + *bytes_received = 0; + goto fail; } - if (c.cmsg_level == IPPROTO_IPV6 && c.cmsg_type == IPV6_PKTINFO) + + // parse the control messages + *local_address = InetAddress(); + for (WSACMSGHDR* c = WSA_CMSG_FIRSTHDR(&msg); c != null; c = WSA_CMSG_NXTHDR(&msg, c)) { - IN6_PKTINFO* pk6 = cast(IN6_PKTINFO*)WSA_CMSG_DATA(c); - *local_address = InetAddress(make_IPv6Addr(pk6.ipi6_addr), 0); // TODO: be nice to populate the listening port... - // pk6.ipi6_ifindex = receiving interface index + if (c.cmsg_level == IPPROTO_IP && c.cmsg_type == IP_PKTINFO) + { + IN_PKTINFO* pk = cast(IN_PKTINFO*)WSA_CMSG_DATA(c); + *local_address = InetAddress(make_IPAddr(pk.ipi_addr), 0); // TODO: be nice to populate the listening port... + // pk.ipi_ifindex = receiving interface index + } + if (c.cmsg_level == IPPROTO_IPV6 && c.cmsg_type == IPV6_PKTINFO) + { + IN6_PKTINFO* pk6 = cast(IN6_PKTINFO*)WSA_CMSG_DATA(c); + *local_address = InetAddress(make_IPv6Addr(pk6.ipi6_addr), 0); // TODO: be nice to populate the listening port... + // pk6.ipi6_ifindex = receiving interface index + } } } + else + { + assert(false, "TODO: call recvmsg and all that..."); + } } else { - assert(false, "TODO: call recvmsg and all that..."); - } - } - else - { - socklen_t size = addr_buffer.sizeof; - ptrdiff_t bytes = _recvfrom(socket.handle, buffer.ptr, cast(int)buffer.length, map_message_flags(flags), addr, &size); - if (bytes >= 0) - *bytes_received = bytes; - else - { - *bytes_received = 0; - goto fail; + socklen_t size = addr_buffer.sizeof; + ptrdiff_t bytes = _recvfrom(socket.handle, buffer.ptr, cast(int)buffer.length, map_message_flags(flags), addr, &size); + if (bytes >= 0) + *bytes_received = bytes; + else + { + *bytes_received = 0; + goto fail; + } } - } - if (sender_address) - *sender_address = make_InetAddress(addr); - return Result.success; + if (sender_address) + *sender_address = make_InetAddress(addr); + return Result.success; -fail: - Result error = socket_getlasterror(); - SocketResult sockRes = socket_result(error); - if (sockRes != SocketResult.no_buffer && // buffers full - sockRes != SocketResult.connection_refused && // posix error - sockRes != SocketResult.connection_reset) // !!! windows may report this error, but it appears to mean something different on posix - return error; - return Result.success; + fail: + Result error = socket_getlasterror(); + SocketResult sockRes = socket_result(error); + if (sockRes != SocketResult.no_buffer && // buffers full + sockRes != SocketResult.connection_refused && // posix error + sockRes != SocketResult.connection_reset) // !!! windows may report this error, but it appears to mean something different on posix + return error; + return Result.success; + } } Result set_socket_option(Socket socket, SocketOption option, const(void)* optval, size_t optlen) { - Result r = Result.success; + version (SocketCallbacks) + return Result(_socket_backend.set_option(socket, option, optval, optlen)); + else + { + Result r = Result.success; - // check the option appears to be the proper datatype - const OptInfo* opt_info = &s_socketOptions[option]; - assert(opt_info.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); - assert(optlen == s_optTypeRtSize[opt_info.rt_type], "Socket option has incorrect payload size!"); + // check the option appears to be the proper datatype + const OptInfo* opt_info = &s_socketOptions[option]; + assert(opt_info.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); + assert(optlen == s_optTypeRtSize[opt_info.rt_type], "Socket option has incorrect payload size!"); - // special case for non-blocking - // this is not strictly a 'socket option', but this rather simplifies our API - if (option == SocketOption.non_blocking) - { - bool value = *cast(const(bool)*)optval; - version (Windows) - { - uint opt = value ? 1 : 0; - r.system_code = ioctlsocket(socket.handle, FIONBIO, &opt); - } - else version (BSDSockets) + // special case for non-blocking + // this is not strictly a 'socket option', but this rather simplifies our API + if (option == SocketOption.non_blocking) { - version (lwIP) + bool value = *cast(const(bool)*)optval; + version (Windows) { - // lwIP's fcntl has quirks with F_SETFL; use ioctlsocket(FIONBIO) instead. - int opt = value ? 1 : 0; + uint opt = value ? 1 : 0; r.system_code = ioctlsocket(socket.handle, FIONBIO, &opt); } - else + else version (BSDSockets) { - int flags = fcntl(socket.handle, F_GETFL, 0); - r.system_code = fcntl(socket.handle, F_SETFL, value ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)); + version (lwIP) + { + // lwIP's fcntl has quirks with F_SETFL; use ioctlsocket(FIONBIO) instead. + int opt = value ? 1 : 0; + r.system_code = ioctlsocket(socket.handle, FIONBIO, &opt); + } + else + { + int flags = fcntl(socket.handle, F_GETFL, 0); + r.system_code = fcntl(socket.handle, F_SETFL, value ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)); + } } + else + assert(false, "Not implemented!"); + return r; } - else - assert(false, "Not implemented!"); - return r; - } -// // Convenience for socket-level no signal since some platforms only support message flag -// if (option == SocketOption.NoSigPipe) -// { -// LockGuard!SharedMutex lock(s_noSignalMut); -// s_noSignal.InsertOrAssign(socket.handle, *cast(const(bool)*)optval); +// // Convenience for socket-level no signal since some platforms only support message flag +// if (option == SocketOption.NoSigPipe) +// { +// LockGuard!SharedMutex lock(s_noSignalMut); +// s_noSignal.InsertOrAssign(socket.handle, *cast(const(bool)*)optval); // -// if (opt_info.platform_type == OptType.unsupported) -// return r; -// } - - // determine the option 'level' - OptLevel level = get_optlevel(option); - version (HasIPv6) {} else - assert(level != OptLevel.ipv6 && level != OptLevel.icmpv6, "Platform does not support IPv6!"); - - // platforms don't all agree on option data formats! - const(void)* arg = optval; - int itmp = void; - linger ling = void; - if (opt_info.rt_type != opt_info.platform_type) - { - switch (opt_info.rt_type) +// if (opt_info.platform_type == OptType.unsupported) +// return r; +// } + + // determine the option 'level' + OptLevel level = get_optlevel(option); + version (HasIPv6) {} else + assert(level != OptLevel.ipv6 && level != OptLevel.icmpv6, "Platform does not support IPv6!"); + + // platforms don't all agree on option data formats! + const(void)* arg = optval; + int itmp = void; + linger ling = void; + if (opt_info.rt_type != opt_info.platform_type) { - // TODO: there are more converstions necessary as options/platforms are added - case OptType.bool_: + switch (opt_info.rt_type) { - const bool value = *cast(const(bool)*)optval; - switch (opt_info.platform_type) + // TODO: there are more converstions necessary as options/platforms are added + case OptType.bool_: { - case OptType.int_: - itmp = value ? 1 : 0; - arg = &itmp; - break; - default: assert(false, "Unexpected"); + const bool value = *cast(const(bool)*)optval; + switch (opt_info.platform_type) + { + case OptType.int_: + itmp = value ? 1 : 0; + arg = &itmp; + break; + default: assert(false, "Unexpected"); + } + break; } - break; - } - case OptType.duration: - { - const Duration value = *cast(const(Duration)*)optval; - switch (opt_info.platform_type) + case OptType.duration: { - case OptType.seconds: - itmp = cast(int)value.as!"seconds"; - arg = &itmp; - break; - case OptType.milliseconds: - itmp = cast(int)value.as!"msecs"; - arg = &itmp; - break; - case OptType.linger: - itmp = cast(int)value.as!"seconds"; - ling = linger(!!itmp, cast(typeof(linger.l_linger))itmp); - arg = &ling; - break; - default: assert(false, "Unexpected"); + const Duration value = *cast(const(Duration)*)optval; + switch (opt_info.platform_type) + { + case OptType.seconds: + itmp = cast(int)value.as!"seconds"; + arg = &itmp; + break; + case OptType.milliseconds: + itmp = cast(int)value.as!"msecs"; + arg = &itmp; + break; + case OptType.linger: + itmp = cast(int)value.as!"seconds"; + ling = linger(!!itmp, cast(typeof(linger.l_linger))itmp); + arg = &ling; + break; + default: assert(false, "Unexpected"); + } + break; } - break; + default: + assert(false, "Unexpected!"); } - default: - assert(false, "Unexpected!"); } - } - // set the option - r.system_code = setsockopt(socket.handle, s_sockOptLevel[level], opt_info.option, cast(const(char)*)arg, s_optTypePlatformSize[opt_info.platform_type]); + // set the option + r.system_code = setsockopt(socket.handle, s_sockOptLevel[level], opt_info.option, cast(const(char)*)arg, s_optTypePlatformSize[opt_info.platform_type]); - return r; + return r; + } } Result set_socket_option(Socket socket, SocketOption option, bool value) { - const OptInfo* opt_info = &s_socketOptions[option]; - if (opt_info.rt_type == OptType.unsupported) - return InternalResult.unsupported; - assert(opt_info.rt_type == OptType.bool_, "Incorrect value type for option"); - return set_socket_option(socket, option, &value, bool.sizeof); + version (SocketCallbacks) + return set_socket_option(socket, option, &value, bool.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.bool_, "Incorrect value type for option"); + return set_socket_option(socket, option, &value, bool.sizeof); + } } Result set_socket_option(Socket socket, SocketOption option, int value) { - const OptInfo* opt_info = &s_socketOptions[option]; - if (opt_info.rt_type == OptType.unsupported) - return InternalResult.unsupported; - assert(opt_info.rt_type == OptType.int_, "Incorrect value type for option"); - return set_socket_option(socket, option, &value, int.sizeof); + version (SocketCallbacks) + return set_socket_option(socket, option, &value, int.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.int_, "Incorrect value type for option"); + return set_socket_option(socket, option, &value, int.sizeof); + } } Result set_socket_option(Socket socket, SocketOption option, Duration value) { - const OptInfo* opt_info = &s_socketOptions[option]; - if (opt_info.rt_type == OptType.unsupported) - return InternalResult.unsupported; - assert(opt_info.rt_type == OptType.duration, "Incorrect value type for option"); - return set_socket_option(socket, option, &value, Duration.sizeof); + version (SocketCallbacks) + return set_socket_option(socket, option, &value, Duration.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.duration, "Incorrect value type for option"); + return set_socket_option(socket, option, &value, Duration.sizeof); + } } Result set_socket_option(Socket socket, SocketOption option, IPAddr value) { - const OptInfo* opt_info = &s_socketOptions[option]; - if (opt_info.rt_type == OptType.unsupported) - return InternalResult.unsupported; - assert(opt_info.rt_type == OptType.inet_addr, "Incorrect value type for option"); - return set_socket_option(socket, option, &value, IPAddr.sizeof); + version (SocketCallbacks) + return set_socket_option(socket, option, &value, IPAddr.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.inet_addr, "Incorrect value type for option"); + return set_socket_option(socket, option, &value, IPAddr.sizeof); + } } Result set_socket_option(Socket socket, SocketOption option, ref MulticastGroup value) { - const OptInfo* opt_info = &s_socketOptions[option]; - if (opt_info.rt_type == OptType.unsupported) - return InternalResult.unsupported; - assert(opt_info.rt_type == OptType.multicast_group, "Incorrect value type for option"); - return set_socket_option(socket, option, &value, MulticastGroup.sizeof); + version (SocketCallbacks) + return set_socket_option(socket, option, &value, MulticastGroup.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.multicast_group, "Incorrect value type for option"); + return set_socket_option(socket, option, &value, MulticastGroup.sizeof); + } } Result get_socket_option(Socket socket, SocketOption option, void* output, size_t outputlen) { - Result r = Result.success; + version (SocketCallbacks) + return Result(_socket_backend.get_option(socket, option, output, outputlen)); + else + { + Result r = Result.success; - // check the option appears to be the proper datatype - const OptInfo* opt_info = &s_socketOptions[option]; - assert(opt_info.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); - assert(outputlen == s_optTypeRtSize[opt_info.rt_type], "Socket option has incorrect payload size!"); + // check the option appears to be the proper datatype + const OptInfo* opt_info = &s_socketOptions[option]; + assert(opt_info.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); + assert(outputlen == s_optTypeRtSize[opt_info.rt_type], "Socket option has incorrect payload size!"); - assert(option != SocketOption.non_blocking, "Socket option NonBlocking cannot be get"); + assert(option != SocketOption.non_blocking, "Socket option NonBlocking cannot be get"); - // determine the option 'level' - OptLevel level = get_optlevel(option); - version (HasIPv6) {} else - assert(level != OptLevel.ipv6 && level != OptLevel.icmpv6, "Platform does not support IPv6!"); + // determine the option 'level' + OptLevel level = get_optlevel(option); + version (HasIPv6) {} else + assert(level != OptLevel.ipv6 && level != OptLevel.icmpv6, "Platform does not support IPv6!"); - // platforms don't all agree on option data formats! - void* arg = output; - int itmp = 0; - linger ling = { 0, 0 }; - if (opt_info.rt_type != opt_info.platform_type) - { - switch (opt_info.platform_type) + // platforms don't all agree on option data formats! + void* arg = output; + int itmp = 0; + linger ling = { 0, 0 }; + if (opt_info.rt_type != opt_info.platform_type) { - case OptType.int_: - case OptType.seconds: - case OptType.milliseconds: + switch (opt_info.platform_type) { - arg = &itmp; - break; - } - case OptType.linger: - { - arg = &ling; - break; + case OptType.int_: + case OptType.seconds: + case OptType.milliseconds: + { + arg = &itmp; + break; + } + case OptType.linger: + { + arg = &ling; + break; + } + default: + assert(false, "Unexpected!"); } - default: - assert(false, "Unexpected!"); } - } - socklen_t writtenLen = s_optTypePlatformSize[opt_info.platform_type]; - // get the option - r.system_code = getsockopt(socket.handle, s_sockOptLevel[level], opt_info.option, cast(char*)arg, &writtenLen); + socklen_t writtenLen = s_optTypePlatformSize[opt_info.platform_type]; + // get the option + r.system_code = getsockopt(socket.handle, s_sockOptLevel[level], opt_info.option, cast(char*)arg, &writtenLen); - if (opt_info.rt_type != opt_info.platform_type) - { - switch (opt_info.rt_type) + if (opt_info.rt_type != opt_info.platform_type) { - // TODO: there are more converstions necessary as options/platforms are added - case OptType.bool_: + switch (opt_info.rt_type) { - bool* value = cast(bool*)output; - switch (opt_info.platform_type) + // TODO: there are more converstions necessary as options/platforms are added + case OptType.bool_: { - case OptType.int_: - *value = !!itmp; - break; - default: assert(false, "Unexpected"); + bool* value = cast(bool*)output; + switch (opt_info.platform_type) + { + case OptType.int_: + *value = !!itmp; + break; + default: assert(false, "Unexpected"); + } + break; } - break; - } - case OptType.duration: - { - Duration* value = cast(Duration*)output; - switch (opt_info.platform_type) + case OptType.duration: { - case OptType.seconds: - *value = seconds(itmp); - break; - case OptType.milliseconds: - *value = msecs(itmp); - break; - case OptType.linger: - *value = seconds(ling.l_linger); - break; - default: assert(false, "Unexpected"); + Duration* value = cast(Duration*)output; + switch (opt_info.platform_type) + { + case OptType.seconds: + *value = seconds(itmp); + break; + case OptType.milliseconds: + *value = msecs(itmp); + break; + case OptType.linger: + *value = seconds(ling.l_linger); + break; + default: assert(false, "Unexpected"); + } + break; } - break; + default: + assert(false, "Unexpected!"); } - default: - assert(false, "Unexpected!"); } - } - assert(opt_info.rt_type != OptType.inet_addr, "TODO: uncomment this block... for some reason, this block causes DMD to do a bad codegen!"); + assert(opt_info.rt_type != OptType.inet_addr, "TODO: uncomment this block... for some reason, this block causes DMD to do a bad codegen!"); /+ - // Options expected in network-byte order - switch (opt_info.rt_type) - { - case OptType.INAddress: + // Options expected in network-byte order + switch (opt_info.rt_type) { - IPAddr* addr = cast(IPAddr*)output; - addr.address = loadBigEndian(&addr.address()); - break; + case OptType.INAddress: + { + IPAddr* addr = cast(IPAddr*)output; + addr.address = loadBigEndian(&addr.address()); + break; + } + default: + break; } - default: - break; - } +/ - return r; + return r; + } } Result get_socket_option(Socket socket, SocketOption option, out bool output) { - const OptInfo* opt_info = &s_socketOptions[option]; - if (opt_info.rt_type == OptType.unsupported) - return InternalResult.unsupported; - assert(opt_info.rt_type == OptType.bool_, "Incorrect value type for option"); - return get_socket_option(socket, option, &output, bool.sizeof); + version (SocketCallbacks) + return get_socket_option(socket, option, &output, bool.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.bool_, "Incorrect value type for option"); + return get_socket_option(socket, option, &output, bool.sizeof); + } } Result get_socket_option(Socket socket, SocketOption option, out int output) { - const OptInfo* opt_info = &s_socketOptions[option]; - if (opt_info.rt_type == OptType.unsupported) - return InternalResult.unsupported; - assert(opt_info.rt_type == OptType.int_, "Incorrect value type for option"); - return get_socket_option(socket, option, &output, int.sizeof); + version (SocketCallbacks) + return get_socket_option(socket, option, &output, int.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.int_, "Incorrect value type for option"); + return get_socket_option(socket, option, &output, int.sizeof); + } } Result get_socket_option(Socket socket, SocketOption option, out Duration output) { - const OptInfo* opt_info = &s_socketOptions[option]; - if (opt_info.rt_type == OptType.unsupported) - return InternalResult.unsupported; - assert(opt_info.rt_type == OptType.duration, "Incorrect value type for option"); - return get_socket_option(socket, option, &output, Duration.sizeof); + version (SocketCallbacks) + return get_socket_option(socket, option, &output, Duration.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.duration, "Incorrect value type for option"); + return get_socket_option(socket, option, &output, Duration.sizeof); + } } Result get_socket_option(Socket socket, SocketOption option, out IPAddr output) { - const OptInfo* opt_info = &s_socketOptions[option]; - if (opt_info.rt_type == OptType.unsupported) - return InternalResult.unsupported; - assert(opt_info.rt_type == OptType.inet_addr, "Incorrect value type for option"); - return get_socket_option(socket, option, &output, IPAddr.sizeof); + version (SocketCallbacks) + return get_socket_option(socket, option, &output, IPAddr.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.inet_addr, "Incorrect value type for option"); + return get_socket_option(socket, option, &output, IPAddr.sizeof); + } } Result set_keepalive(Socket socket, bool enable, Duration keepIdle, Duration keepInterval, int keepCount) @@ -1038,44 +1203,58 @@ Result set_keepalive(Socket socket, bool enable, Duration keepIdle, Duration kee Result get_peer_name(Socket socket, out InetAddress name) { - char[sockaddr_storage.sizeof] buffer; - sockaddr* addr = cast(sockaddr*)buffer; - socklen_t bufferLen = buffer.sizeof; - - int fail = getpeername(socket.handle, addr, &bufferLen); - if (fail == 0) - name = make_InetAddress(addr); + version (SocketCallbacks) + return Result(_socket_backend.get_peer_name(socket, name)); else - return socket_getlasterror(); - return Result.success; + { + char[sockaddr_storage.sizeof] buffer; + sockaddr* addr = cast(sockaddr*)buffer; + socklen_t bufferLen = buffer.sizeof; + + int fail = getpeername(socket.handle, addr, &bufferLen); + if (fail == 0) + name = make_InetAddress(addr); + else + return socket_getlasterror(); + return Result.success; + } } Result get_socket_name(Socket socket, out InetAddress name) { - char[sockaddr_storage.sizeof] buffer; - sockaddr* addr = cast(sockaddr*)buffer; - socklen_t bufferLen = buffer.sizeof; - - int fail = getsockname(socket.handle, addr, &bufferLen); - if (fail == 0) - name = make_InetAddress(addr); + version (SocketCallbacks) + return Result(_socket_backend.get_socket_name(socket, name)); else - return socket_getlasterror(); - return Result.success; + { + char[sockaddr_storage.sizeof] buffer; + sockaddr* addr = cast(sockaddr*)buffer; + socklen_t bufferLen = buffer.sizeof; + + int fail = getsockname(socket.handle, addr, &bufferLen); + if (fail == 0) + name = make_InetAddress(addr); + else + return socket_getlasterror(); + return Result.success; + } } Result get_hostname(char* name, size_t len) { - int fail = gethostname(name, cast(int)len); - if (fail) - return socket_getlasterror(); - return Result.success; + version (SocketCallbacks) + return Result(_socket_backend.get_hostname(name, len)); + else + { + int fail = gethostname(name, cast(int)len); + if (fail) + return socket_getlasterror(); + return Result.success; + } } Result get_address_info(const(char)[] nodeName, const(char)[] service, AddressInfo* hints, out AddressInfoResolver result) { import urt.string : findFirst; - import urt.mem.temp : tstringz; size_t colon = nodeName.findFirst(':'); if (colon < nodeName.length) @@ -1085,73 +1264,89 @@ Result get_address_info(const(char)[] nodeName, const(char)[] service, AddressIn nodeName = nodeName[0 .. colon]; } - addrinfo tmpHints; - if (hints) + version (SocketCallbacks) { - // translate hints... - tmpHints.ai_flags = map_addrinfo_flags(hints.flags); - tmpHints.ai_family = s_addressFamily[hints.family]; - tmpHints.ai_socktype = s_socketType[hints.sock_type]; - tmpHints.ai_protocol = s_protocol[hints.protocol]; - tmpHints.ai_canonname = cast(char*)hints.canon_name; // HAX! - tmpHints.ai_addrlen = 0; - tmpHints.ai_addr = null; - tmpHints.ai_next = null; + // Backend fills the resolver with a defaultAllocator-allocated + // array of AddressInfo entries (sentinel-terminated). + return Result(_socket_backend.get_address_info(nodeName, service, hints, &result)); } + else + { + import urt.mem.temp : tstringz; + + addrinfo tmpHints; + if (hints) + { + // translate hints... + tmpHints.ai_flags = map_addrinfo_flags(hints.flags); + tmpHints.ai_family = s_addressFamily[hints.family]; + tmpHints.ai_socktype = s_socketType[hints.sock_type]; + tmpHints.ai_protocol = s_protocol[hints.protocol]; + tmpHints.ai_canonname = cast(char*)hints.canon_name; // HAX! + tmpHints.ai_addrlen = 0; + tmpHints.ai_addr = null; + tmpHints.ai_next = null; + } - addrinfo* res; - int err = getaddrinfo(nodeName.tstringz, service ? service.tstringz : null, hints ? &tmpHints : null, &res); - if (err != 0) - return Result(err); + addrinfo* res; + int err = getaddrinfo(nodeName.tstringz, service ? service.tstringz : null, hints ? &tmpHints : null, &res); + if (err != 0) + return Result(err); - // if it was used previously - if (result.m_internal[0]) - freeaddrinfo(cast(addrinfo*)result.m_internal[0]); + // if it was used previously + if (result.m_internal[0]) + freeaddrinfo(cast(addrinfo*)result.m_internal[0]); - result.m_internal[0] = res; - result.m_internal[1] = res; + result.m_internal[0] = res; + result.m_internal[1] = res; - return Result.success; + return Result.success; + } } Result poll(PollFd[] pollFds, Duration timeout, out uint numEvents) { - enum MaxFds = 512; - assert(pollFds.length <= MaxFds, "Too many fds!"); - version (Windows) - WSAPOLLFD[MaxFds] fds; - else - pollfd[MaxFds] fds; - for (size_t i = 0; i < pollFds.length; ++i) - { - fds[i].fd = pollFds[i].socket.handle; - fds[i].revents = 0; - fds[i].events = cast(short)(((pollFds[i].request_events & PollEvents.read) ? POLLRDNORM : 0) | - ((pollFds[i].request_events & PollEvents.write) ? POLLWRNORM : 0)); - } - int r; - version (Windows) - r = WSAPoll(fds.ptr, cast(uint)pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); - else version (BSDSockets) - r = _poll(fds.ptr, cast(uint)pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); + version (SocketCallbacks) + return Result(_socket_backend.poll(pollFds, timeout, numEvents)); else - assert(false, "Not implemented!"); - if (r < 0) - { - numEvents = 0; - return socket_getlasterror(); - } - numEvents = r; - for (size_t i = 0; i < pollFds.length; ++i) { - pollFds[i].return_events = cast(PollEvents)( - ((fds[i].revents & POLLRDNORM) ? PollEvents.read : 0) | - ((fds[i].revents & POLLWRNORM) ? PollEvents.write : 0) | - ((fds[i].revents & POLLERR) ? PollEvents.error : 0) | - ((fds[i].revents & POLLHUP) ? PollEvents.hangup : 0) | - ((fds[i].revents & POLLNVAL) ? PollEvents.invalid : 0)); + enum MaxFds = 512; + assert(pollFds.length <= MaxFds, "Too many fds!"); + version (Windows) + WSAPOLLFD[MaxFds] fds; + else + pollfd[MaxFds] fds; + for (size_t i = 0; i < pollFds.length; ++i) + { + fds[i].fd = pollFds[i].socket.handle; + fds[i].revents = 0; + fds[i].events = cast(short)(((pollFds[i].request_events & PollEvents.read) ? POLLRDNORM : 0) | + ((pollFds[i].request_events & PollEvents.write) ? POLLWRNORM : 0)); + } + int r; + version (Windows) + r = WSAPoll(fds.ptr, cast(uint)pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); + else version (BSDSockets) + r = _poll(fds.ptr, cast(uint)pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); + else + assert(false, "Not implemented!"); + if (r < 0) + { + numEvents = 0; + return socket_getlasterror(); + } + numEvents = r; + for (size_t i = 0; i < pollFds.length; ++i) + { + pollFds[i].return_events = cast(PollEvents)( + ((fds[i].revents & POLLRDNORM) ? PollEvents.read : 0) | + ((fds[i].revents & POLLWRNORM) ? PollEvents.write : 0) | + ((fds[i].revents & POLLERR) ? PollEvents.error : 0) | + ((fds[i].revents & POLLHUP) ? PollEvents.hangup : 0) | + ((fds[i].revents & POLLNVAL) ? PollEvents.invalid : 0)); + } + return Result.success; } - return Result.success; } Result poll(ref PollFd pollFd, Duration timeout, out uint numEvents) @@ -1185,7 +1380,12 @@ nothrow @nogc: ~this() { if (m_internal[0]) - freeaddrinfo(cast(addrinfo*)m_internal[0]); + { + version (SocketCallbacks) + _socket_backend.free_address_info(&this); + else + freeaddrinfo(cast(addrinfo*)m_internal[0]); + } } // TODO: this should be a MOVE ONLY assignment! @@ -1207,16 +1407,21 @@ nothrow @nogc: if (!m_internal[1]) return false; - addrinfo* info = cast(addrinfo*)(m_internal[1]); - m_internal[1] = info.ai_next; - - addressInfo.flags = AddressInfoFlags.none; // info.ai_flags is only used for 'hints' - addressInfo.family = map_address_family(info.ai_family); - addressInfo.sock_type = cast(int)info.ai_socktype ? map_socket_type(info.ai_socktype) : SocketType.unknown; - addressInfo.protocol = map_protocol(info.ai_protocol); - addressInfo.canon_name = info.ai_canonname; - addressInfo.address = make_InetAddress(info.ai_addr); - return true; + version (SocketCallbacks) + return _socket_backend.next_address(&this, addressInfo); + else + { + addrinfo* info = cast(addrinfo*)(m_internal[1]); + m_internal[1] = info.ai_next; + + addressInfo.flags = AddressInfoFlags.none; // info.ai_flags is only used for 'hints' + addressInfo.family = map_address_family(info.ai_family); + addressInfo.sock_type = cast(int)info.ai_socktype ? map_socket_type(info.ai_socktype) : SocketType.unknown; + addressInfo.protocol = map_protocol(info.ai_protocol); + addressInfo.canon_name = info.ai_canonname; + addressInfo.address = make_InetAddress(info.ai_addr); + return true; + } } void*[2] m_internal = [ null, null ]; @@ -1232,81 +1437,100 @@ struct PollFd -Result socket_getlasterror() -{ - version (Windows) - return Result(WSAGetLastError()); - else version (Errno) - return errno_result(); - else - static assert(false, "socket_getlasterror not implemented for this platform"); -} - Result get_socket_error(Socket socket) { - Result r; - socklen_t optlen = r.system_code.sizeof; - int callResult = getsockopt(socket.handle, SOL_SOCKET, SO_ERROR, cast(char*)&r.system_code, &optlen); - if (callResult) - r.system_code = callResult; - return r; + version (SocketCallbacks) + { + int err; + Result r = get_socket_option(socket, SocketOption.error, err); + if (r) + r.system_code = err; + return r; + } + else + { + Result r; + socklen_t optlen = r.system_code.sizeof; + int callResult = getsockopt(socket.handle, SOL_SOCKET, SO_ERROR, cast(char*)&r.system_code, &optlen); + if (callResult) + r.system_code = callResult; + return r; + } } // TODO: !!! -enum Result ConnectionClosedResult = Result(-12345); +enum Result ConnectionClosedResult = Result(-12345); SocketResult socket_result(Result result) { - if (result) - return SocketResult.success; - if (result.system_code == ConnectionClosedResult.system_code) - return SocketResult.connection_closed; - version (Windows) - { - if (result.system_code == WSAEWOULDBLOCK) - return SocketResult.would_block; - if (result.system_code == WSAEINPROGRESS) - return SocketResult.would_block; - if (result.system_code == WSAENOBUFS) - return SocketResult.no_buffer; - if (result.system_code == WSAENETDOWN) - return SocketResult.network_down; - if (result.system_code == WSAECONNREFUSED) - return SocketResult.connection_refused; - if (result.system_code == WSAECONNRESET) - return SocketResult.connection_reset; - if (result.system_code == WSAEINTR) - return SocketResult.interrupted; - if (result.system_code == WSAENOTSOCK) - return SocketResult.invalid_socket; - if (result.system_code == WSAEINVAL) - return SocketResult.invalid_argument; - } - else version (Errno) + version (SocketCallbacks) + return cast(SocketResult)result.system_code; + else { - import urt.internal.stdc.errno; - static if (EAGAIN != EWOULDBLOCK) - if (result.system_code == EAGAIN) + if (result) + return SocketResult.success; + if (result.system_code == ConnectionClosedResult.system_code) + return SocketResult.connection_closed; + else version (Windows) + { + if (result.system_code == WSAEWOULDBLOCK) + return SocketResult.would_block; + if (result.system_code == WSAEINPROGRESS) + return SocketResult.would_block; + if (result.system_code == WSAENOBUFS) + return SocketResult.no_buffer; + if (result.system_code == WSAENETDOWN) + return SocketResult.network_down; + if (result.system_code == WSAECONNREFUSED) + return SocketResult.connection_refused; + if (result.system_code == WSAECONNRESET) + return SocketResult.connection_reset; + if (result.system_code == WSAEINTR) + return SocketResult.interrupted; + if (result.system_code == WSAENOTSOCK) + return SocketResult.invalid_socket; + if (result.system_code == WSAEINVAL) + return SocketResult.invalid_argument; + } + else version (Errno) + { + import urt.internal.stdc.errno; + static if (EAGAIN != EWOULDBLOCK) + if (result.system_code == EAGAIN) + return SocketResult.would_block; + if (result.system_code == EWOULDBLOCK) + return SocketResult.would_block; + if (result.system_code == EINPROGRESS) return SocketResult.would_block; - if (result.system_code == EWOULDBLOCK) - return SocketResult.would_block; - if (result.system_code == EINPROGRESS) - return SocketResult.would_block; - if (result.system_code == ENOMEM) - return SocketResult.no_buffer; - if (result.system_code == ENETDOWN) - return SocketResult.network_down; - if (result.system_code == ECONNREFUSED) - return SocketResult.connection_refused; - if (result.system_code == ECONNRESET) - return SocketResult.connection_reset; - if (result.system_code == EINTR) - return SocketResult.interrupted; - if (result.system_code == EINVAL) - return SocketResult.invalid_argument; + if (result.system_code == ENOMEM) + return SocketResult.no_buffer; + if (result.system_code == ENETDOWN) + return SocketResult.network_down; + if (result.system_code == ECONNREFUSED) + return SocketResult.connection_refused; + if (result.system_code == ECONNRESET) + return SocketResult.connection_reset; + if (result.system_code == EINTR) + return SocketResult.interrupted; + if (result.system_code == EINVAL) + return SocketResult.invalid_argument; + } + return SocketResult.failure; } - return SocketResult.failure; } +private: + +version (SocketCallbacks) {} else { + +Result socket_getlasterror() +{ + version (Windows) + return Result(WSAGetLastError()); + else version (Errno) + return errno_result(); + else + static assert(false, "socket_getlasterror not implemented for this platform"); +} sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_t addr_len) { @@ -1487,8 +1711,6 @@ IPv6Addr make_IPv6Addr(ref const in6_addr in6) return addr; } -private: - enum OptLevel : ubyte { socket, @@ -1902,3 +2124,5 @@ version (Windows) extern(Windows) int WSASend(SOCKET s, LPWSABUF lpBuffers, uint dwBufferCount, uint* lpNumberOfBytesSent, uint dwFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); extern(Windows) int WSASendTo(SOCKET s, LPWSABUF lpBuffers, uint dwBufferCount, uint* lpNumberOfBytesSent, uint dwFlags, const(sockaddr)* lpTo, int iTolen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); } + +} // SocketCallbacks From cb37e38f52ed61d5103766c700838d485a0203b7 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 9 Apr 2026 01:17:24 +1000 Subject: [PATCH 118/138] Release builds can have debug/trace logs! --- src/urt/log.d | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/urt/log.d b/src/urt/log.d index 66c6a38..11de672 100644 --- a/src/urt/log.d +++ b/src/urt/log.d @@ -105,13 +105,10 @@ void log_warningf(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { writ void log_noticef(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.notice, tag, null, fmt, args); } void log_infof(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.info, tag, null, fmt, args); } -debug -{ - void log_debug(T...)(const(char)[] tag, ref T args) { write_log(Severity.debug_, tag, null, args); } - void log_trace(T...)(const(char)[] tag, ref T args) { write_log(Severity.trace, tag, null, args); } - void log_debugf(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.debug_, tag, null, fmt, args); } - void log_tracef(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.trace, tag, null, fmt, args); } -} +void log_debug(T...)(const(char)[] tag, ref T args) { write_log(Severity.debug_, tag, null, args); } +void log_trace(T...)(const(char)[] tag, ref T args) { write_log(Severity.trace, tag, null, args); } +void log_debugf(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.debug_, tag, null, fmt, args); } +void log_tracef(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.trace, tag, null, fmt, args); } // this can be declared in any scope to automatically prefix log messages with a tag (e.g. module name) // eg: alias log = Log!"my.module"; @@ -134,13 +131,10 @@ template Log(string tag) void alertf(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.alert, tag, null, fmt, args); } void emergencyf(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.emergency, tag, null, fmt, args); } - debug - { - void debug_(T...)(ref T args) { write_log(Severity.debug_, tag, null, args); } - void trace(T...)(ref T args) { write_log(Severity.trace, tag, null, args); } - void debugf(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.debug_, tag, null, fmt, args); } - void tracef(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.trace, tag, null, fmt, args); } - } + void debug_(T...)(ref T args) { write_log(Severity.debug_, tag, null, args); } + void trace(T...)(ref T args) { write_log(Severity.trace, tag, null, args); } + void debugf(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.debug_, tag, null, fmt, args); } + void tracef(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.trace, tag, null, fmt, args); } } void write_log(T...)(Severity severity, const(char)[] tag, const(char)[] object_name, ref T args) From 0e791f6167ae8770aec0caa4391cd23c71b6476f Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 9 Apr 2026 01:41:58 +1000 Subject: [PATCH 119/138] Add prelim support for BL618 micro. --- src/sys/bl618/irq.d | 26 +++++ src/sys/bl618/package.d | 41 ++++++++ src/sys/bl618/start.S | 206 +++++++++++++++++++++++++++++++++++++++ src/sys/bl618/syscalls.d | 44 +++++++++ src/sys/bl618/timer.d | 61 ++++++++++++ src/sys/bl618/uart.d | 91 +++++++++++++++++ src/urt/exception.d | 5 +- src/urt/system.d | 11 ++- src/urt/time.d | 15 +-- 9 files changed, 489 insertions(+), 11 deletions(-) create mode 100644 src/sys/bl618/irq.d create mode 100644 src/sys/bl618/package.d create mode 100644 src/sys/bl618/start.S create mode 100644 src/sys/bl618/syscalls.d create mode 100644 src/sys/bl618/timer.d create mode 100644 src/sys/bl618/uart.d diff --git a/src/sys/bl618/irq.d b/src/sys/bl618/irq.d new file mode 100644 index 0000000..9d6738b --- /dev/null +++ b/src/sys/bl618/irq.d @@ -0,0 +1,26 @@ +/// BL618 interrupt controller driver +/// +/// BL616/BL618 uses a CLIC-style interrupt controller. +/// +/// TODO: Implement from BL616 register map. +module sys.bl618.irq; + +@nogc nothrow: + +/// Globally disable interrupts +void irq_disable() +{ + asm @nogc nothrow + { + "csrc mstatus, 0x0008"; // Clear MIE + } +} + +/// Globally enable interrupts +void irq_enable() +{ + asm @nogc nothrow + { + "csrs mstatus, 0x0008"; // Set MIE + } +} diff --git a/src/sys/bl618/package.d b/src/sys/bl618/package.d new file mode 100644 index 0000000..e6bea6b --- /dev/null +++ b/src/sys/bl618/package.d @@ -0,0 +1,41 @@ +/// BL618 platform package (T-Head E907 RV32IMAFC) +/// +/// Provides sys_init() as the single entry point for all +/// hardware initialization. Call from main() before anything else. +module sys.bl618; + +public import sys.bl618.uart; +public import sys.bl618.irq; +public import sys.bl618.timer; + +@nogc nothrow: + +private extern(C) void __register_frame_info(const void*, void*); +private extern(C) extern const ubyte __eh_frame_start; +private ubyte[48] __eh_frame_object; // pre-allocated storage for libgcc + +/// Initialize BL618 hardware. +/// Call once at the top of main() before any other OpenWatt code. +/// +/// Order matters: +/// 1. UART — so we have debug output for everything after +/// 2. IRQ table — already done by start.S (_init_interrupts) +/// 3. Timer — periodic tick for main loop +extern(C) void sys_init() +{ + // Register .eh_frame with libgcc's unwinder so that DWARF exception + // handling works (required for fibre abort, etc.). + __register_frame_info(&__eh_frame_start, &__eh_frame_object); + + uart0_puts("BL618: sys_init\n"); + + // Timer: set up 20Hz tick (50ms) for the main loop + timer_set_periodic(50_000, &tick_stub); + + uart0_puts("BL618: ready\n"); +} + +private void tick_stub() @nogc nothrow +{ + // placeholder — will drive urt.time / Application frame tick +} diff --git a/src/sys/bl618/start.S b/src/sys/bl618/start.S new file mode 100644 index 0000000..717c01e --- /dev/null +++ b/src/sys/bl618/start.S @@ -0,0 +1,206 @@ +/* BL618 (T-Head E907 RV32IMAFC) startup + * + * Single-core startup — no M0 handshake needed (unlike BL808). + * + * Boot flow: + * 1. CPU setup — disable IRQ, enable extensions, FPU, trap vectors + * 2. Core regs — gp, tp, sp + * 3. Section init — .got, .tdata, .tbss, .bss, .data + * 4. Caches — I-cache enable + * 5. Interrupts — dispatch table, enable MIE+MEIE + * 6. D runtime — sys_init, .init_array, main + * + * T-Head custom CSR numbers: + * mxstatus = 0x7C0 (T-Head ISA extension enable) + * mhcr = 0x7C1 (hardware cache control) + * mhint = 0x7C5 (performance hints) + * + * TODO: Verify CSR values and peripheral base addresses against + * actual BL618 hardware. Based on BL616 reference manual. + */ +#define CSR_MXSTATUS 0x7C0 +#define CSR_MHCR 0x7C1 +#define CSR_MHINT 0x7C5 + + .section .text.entry, "ax" + .global _start + .type _start, @function + .align 2 + +_start: + /* ── 1. CPU setup ─────────────────────────────────────────────── */ + + /* Disable interrupts */ + csrc mstatus, 0x0008 /* MIE */ + + /* Enable T-Head ISA extensions (THEADISAEE + MM) */ + li t0, (1 << 22) | (1 << 15) + csrs CSR_MXSTATUS, t0 + + /* Enable FPU (FS = Initial) — E907 has single-precision FPU */ + li t0, (1 << 13) + csrs mstatus, t0 + + /* Set trap vector table (vectored mode) */ + la t0, __vectors + ori t0, t0, 1 + csrw mtvec, t0 + + /* ── 2. Core registers ────────────────────────────────────────── */ + + .option push + .option norelax + la gp, __global_pointer$ + .option pop + la tp, _tdata_start + la sp, _stack_top + + /* ── 3. Section init ──────────────────────────────────────────── */ + + /* Copy .got from flash (LMA) to DTCM (VMA) */ + la a0, _got_start + la a1, _got_load + la a2, _got_end + bgeu a0, a2, .Lgot_done +.Lgot_copy: + lw t0, 0(a1) + sw t0, 0(a0) + addi a0, a0, 4 + addi a1, a1, 4 + bltu a0, a2, .Lgot_copy +.Lgot_done: + + /* Copy .tdata from flash to DTCM */ + la a0, _tdata_start + la a1, _tdata_load + la a2, _tdata_end + bgeu a0, a2, .Ltdata_done +.Ltdata_copy: + lw t0, 0(a1) + sw t0, 0(a0) + addi a0, a0, 4 + addi a1, a1, 4 + bltu a0, a2, .Ltdata_copy +.Ltdata_done: + + /* Zero .tbss */ + la a0, _tbss_start + la a2, _tbss_end + bgeu a0, a2, .Ltbss_done +.Ltbss_zero: + sw zero, 0(a0) + addi a0, a0, 4 + bltu a0, a2, .Ltbss_zero +.Ltbss_done: + + /* Copy .data from flash to OCRAM */ + la a0, _data_start + la a1, _data_load + la a2, _data_end + bgeu a0, a2, .Ldata_done +.Ldata_copy: + lw t0, 0(a1) + sw t0, 0(a0) + addi a0, a0, 4 + addi a1, a1, 4 + bltu a0, a2, .Ldata_copy +.Ldata_done: + + /* Zero .bss */ + la a0, _bss_start + la a2, _bss_end + bgeu a0, a2, .Lbss_done +.Lbss_zero: + sw zero, 0(a0) + addi a0, a0, 4 + bltu a0, a2, .Lbss_zero +.Lbss_done: + + /* ── 4. Caches ────────────────────────────────────────────────── */ + + /* Enable I-cache */ + li t0, (1 << 0) /* IE: I-cache enable */ + csrs CSR_MHCR, t0 + + /* ── 5. Interrupts ────────────────────────────────────────────── */ + + /* Clear CLIC/PLIC pending — TODO: verify PLIC base for BL618 */ + + /* Enable machine external interrupts */ + li t0, (1 << 11) /* MEIE */ + csrs mie, t0 + /* Enable global interrupts */ + csrs mstatus, 0x0008 /* MIE */ + + /* ── 6. D runtime ─────────────────────────────────────────────── */ + + call sys_init + + /* Run .init_array (D module constructors, etc.) */ + la s0, __init_array_start + la s1, __init_array_end + bgeu s0, s1, .Linit_done +.Linit_loop: + lw t0, 0(s0) + jalr t0 + addi s0, s0, 4 + bltu s0, s1, .Linit_loop +.Linit_done: + + /* Enter main — should never return */ + call main + + /* If main returns, spin */ +.Lhalt: + wfi + j .Lhalt + + .size _start, . - _start + +/* ── Interrupt vector table ───────────────────────────────────────── */ + .section .text, "ax" + .global __vectors + .type __vectors, @function + .align 8 + +__vectors: + j _trap_handler /* 0: exception / sync trap */ + .align 2 + j _trap_handler /* 1: supervisor SW */ + .align 2 + j _trap_handler /* 2: reserved */ + .align 2 + j _trap_handler /* 3: machine SW */ + .align 2 + j _trap_handler /* 4: reserved */ + .align 2 + j _trap_handler /* 5: supervisor timer */ + .align 2 + j _trap_handler /* 6: reserved */ + .align 2 + j _trap_handler /* 7: machine timer */ + .align 2 + j _trap_handler /* 8: reserved */ + .align 2 + j _trap_handler /* 9: supervisor external */ + .align 2 + j _trap_handler /* 10: reserved */ + .align 2 + j _irq_handler /* 11: machine external */ + .align 2 + +/* ── Default trap handler ─────────────────────────────────────────── */ + .global _trap_handler + .weak _trap_handler + .type _trap_handler, @function +_trap_handler: + /* TODO: save context, call crash handler */ + j _trap_handler + +/* ── External IRQ handler ─────────────────────────────────────────── */ + .global _irq_handler + .weak _irq_handler + .type _irq_handler, @function +_irq_handler: + /* TODO: PLIC claim/complete dispatch — wire to sys.bl618.irq */ + j _irq_handler diff --git a/src/sys/bl618/syscalls.d b/src/sys/bl618/syscalls.d new file mode 100644 index 0000000..1e15a91 --- /dev/null +++ b/src/sys/bl618/syscalls.d @@ -0,0 +1,44 @@ +/// BL618 newlib/picolibc syscall stubs +/// +/// Minimal stubs to satisfy picolibc's syscall requirements. +/// Same pattern as BL808 — most are no-ops for baremetal. +module sys.bl618.syscalls; + +@nogc nothrow: + +private extern(C) extern const void* __heap_start; +private extern(C) extern const void* __heap_end; + +private __gshared void* _heap_ptr; + +extern(C) void* _sbrk(ptrdiff_t incr) +{ + if (_heap_ptr is null) + _heap_ptr = cast(void*) &__heap_start; + + void* prev = _heap_ptr; + void* next = _heap_ptr + incr; + + if (next > cast(void*) &__heap_end) + return cast(void*) -1; + + _heap_ptr = next; + return prev; +} + +extern(C) int _write(int fd, const void* buf, size_t count) +{ + import sys.bl618.uart : uart0_puts; + if (fd == 1 || fd == 2) + uart0_puts((cast(const(char)*) buf)[0 .. count]); + return cast(int) count; +} + +extern(C) int _read(int, void*, size_t) { return 0; } +extern(C) int _close(int) { return -1; } +extern(C) int _lseek(int, int, int) { return 0; } +extern(C) int _fstat(int, void*) { return 0; } +extern(C) int _isatty(int) { return 1; } +extern(C) void _exit(int) { while (true) {} } +extern(C) int _kill(int, int) { return -1; } +extern(C) int _getpid() { return 1; } diff --git a/src/sys/bl618/timer.d b/src/sys/bl618/timer.d new file mode 100644 index 0000000..7b3a7b5 --- /dev/null +++ b/src/sys/bl618/timer.d @@ -0,0 +1,61 @@ +/// BL618 timer driver +/// +/// T-Head E907 (RV32IMAFC) has standard RISC-V mtime/mtimecmp. +/// mtime runs at 1MHz (same as BL808's C906). +/// +/// TODO: Verify mtime frequency on actual BL618 hardware. +module sys.bl618.timer; + +@nogc nothrow: + +// ================================================================ +// mtime frequency — 1MHz assumed (same as BL808) +// TODO: verify against BL616/BL618 clock tree +// ================================================================ + +enum uint mtime_freq_hz = 1_000_000; + +// ================================================================ +// Time reading +// ================================================================ + +/// Read the monotonic mtime counter via rdtime. +/// RV32: reads high/low halves with retry on rollover. +ulong mtime_read() +{ + // RV32 requires reading timeh:time atomically. + // If timeh changes between reads, retry. + uint hi1, lo, hi2; + do + { + asm @nogc nothrow { "rdtimeh %0" : "=r" (hi1); } + asm @nogc nothrow { "rdtime %0" : "=r" (lo); } + asm @nogc nothrow { "rdtimeh %0" : "=r" (hi2); } + } + while (hi1 != hi2); + return (cast(ulong) hi1 << 32) | lo; +} + +// ================================================================ +// Periodic tick +// ================================================================ + +alias TimerCallback = void function() @nogc nothrow; + +private __gshared TimerCallback tick_callback; +private __gshared uint tick_interval; + +/// Set up a periodic timer interrupt. +/// Params: +/// period_us = period in microseconds (mtime ticks at 1MHz) +/// cb = callback to invoke on each tick +void timer_set_periodic(uint period_us, TimerCallback cb) +{ + tick_interval = period_us; + tick_callback = cb; + + // TODO: write mtimecmp and enable timer interrupt + // ulong now = mtime_read(); + // mtimecmp_write(now + tick_interval); + // enable_irq(IrqClass.timer); +} diff --git a/src/sys/bl618/uart.d b/src/sys/bl618/uart.d new file mode 100644 index 0000000..a2b3595 --- /dev/null +++ b/src/sys/bl618/uart.d @@ -0,0 +1,91 @@ +/// BL618 UART driver +/// +/// BL616/BL618 has 2 UARTs sharing the same register-level IP block as BL808: +/// +/// UART0 0x2000_A000 Default console +/// UART1 0x2000_A100 General purpose +/// +/// Register layout is identical to BL808 (BL6xx/BL8xx common UART IP). +/// Single-core E907 has direct PLIC access to all UART interrupts. +/// +/// TODO: Implement full driver from BL616 register map. +/// Stubbed to provide the API surface needed by serial.d. +module sys.bl618.uart; + +nothrow @nogc: + +enum UartId : uint { uart0 = 0, uart1 = 1 } + +enum UartParity : ubyte { none, even, odd } +enum UartStopBits : ubyte { one, one_point_five, two } + +struct UartConfig +{ + uint baud_rate = 115200; + ubyte data_bits = 8; + UartParity parity = UartParity.none; + UartStopBits stop_bits = UartStopBits.one; +} + +/// Open and configure a UART. +bool uart_open(UartId id, UartConfig cfg) +{ + // TODO: configure UART registers + return true; +} + +/// Close a UART. +void uart_close(UartId id) +{ + // TODO: disable UART +} + +/// Read available bytes from UART RX ring buffer. +ptrdiff_t uart_read(UartId id, void[] buffer) +{ + // TODO: read from RX ring buffer + return 0; +} + +/// Write bytes to UART TX. +ptrdiff_t uart_write(UartId id, const(void)[] data) +{ + // TODO: write to TX FIFO/ring buffer + return 0; +} + +/// Poll hardware FIFOs — call from update() to drain/fill ring buffers. +void uart_poll(UartId id) +{ + // TODO: poll RX/TX FIFOs +} + +/// Check for UART errors (framing, parity, overflow). +bool uart_check_errors(UartId id) +{ + // TODO: check error status register + return false; +} + +/// Return number of bytes available in RX buffer. +ptrdiff_t uart_rx_pending(UartId id) +{ + // TODO: return ring buffer count + return 0; +} + +/// Flush TX buffer (blocking). +ptrdiff_t uart_flush(UartId id) +{ + // TODO: drain TX ring buffer + return 0; +} + +/// Transmit a string on UART0 (blocking, polled). +/// Used for early boot messages before the full console is up. +void uart0_puts(const(char)[] s) +{ + // TODO: direct register write to UART0 TX FIFO + // UART0 base: 0x2000_A000, FIFO_WDATA offset: 0x88 + // For now, no-op until registers are wired up. +} diff --git a/src/urt/exception.d b/src/urt/exception.d index 422fe82..a5d251c 100644 --- a/src/urt/exception.d +++ b/src/urt/exception.d @@ -27,9 +27,10 @@ void urt_assert(string file, size_t line, string msg) nothrow @nogc if (msg.length == 0) msg = "Assertion failed"; - version (BL808) + version (Bouffalo) { - import sys.bl808.uart : uart0_puts, uart0_hex; + version (BL808) import sys.bl808.uart : uart0_puts; + else import sys.bl618.uart : uart0_puts; import urt.mem.temp : tconcat; uart0_puts(tconcat("\n*** ASSERT: ", msg, " at ", file, ':', line, '\n')); while (true) {} diff --git a/src/urt/system.d b/src/urt/system.d index 1294b28..2e0e302 100644 --- a/src/urt/system.d +++ b/src/urt/system.d @@ -53,6 +53,13 @@ void sleep(Duration duration) if (!was_enabled) disable_irq(IrqClass.timer); } + else version (BL618) + { + // TODO: use mtimecmp + WFI once IRQ driver is implemented + import sys.bl618.timer : mtime_read; + ulong deadline = mtime_read() + duration.as!"usecs"; + while (mtime_read() < deadline) {} + } else { usleep(cast(uint)duration.as!"usecs"); @@ -136,7 +143,7 @@ SystemInfo get_sysinfo() r.peak_memory = r.total_memory - heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT); r.uptime = getAppTime(); } - else version (BL808) + else version (Bouffalo) { auto mi = mallinfo(); r.total_memory = heap_len(); @@ -201,7 +208,7 @@ unittest package: -version (BL808) +version (Bouffalo) { extern(C) extern __gshared { void* __heap_start; diff --git a/src/urt/time.d b/src/urt/time.d index a90f804..c9461e8 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -12,9 +12,10 @@ else version (Posix) { import urt.internal.sys.posix; } -else version (BL808) +else version (Bouffalo) { - import sys.bl808.timer; + version (BL808) import sys.bl808.timer; + else import sys.bl618.timer; } else version (Espressif) { @@ -724,7 +725,7 @@ MonoTime getTime() clock_gettime(CLOCK_MONOTONIC, &ts); return MonoTime(ts.tv_sec * 1_000_000_000 + ts.tv_nsec); } - else version (BL808) + else version (Bouffalo) { return MonoTime(mtime_read()); } @@ -756,7 +757,7 @@ SysTime getSysTime() clock_gettime(CLOCK_REALTIME, &ts); return SysTime(ts.tv_sec * 1_000_000_000 + ts.tv_nsec); } - else version (BL808) + else version (Bouffalo) { return SysTime(mtime_read() + sys_time_offset); } @@ -803,7 +804,7 @@ ulong unixTimeNs(SysTime t) pure return (t.ticks - unix_epoch_as_filetime) * 100UL; else version (Posix) return t.ticks; - else version (BL808) + else version (Bouffalo) return t.ticks * nsec_multiplier; else version (Espressif) return t.ticks * nsec_multiplier; @@ -819,7 +820,7 @@ SysTime from_unix_time_ns(ulong ns) pure return SysTime(ns / 100UL + unix_epoch_as_filetime); else version (Posix) return SysTime(ns); - else version (BL808) + else version (Bouffalo) return SysTime(ns / nsec_multiplier); else version (Espressif) return SysTime(ns / nsec_multiplier); @@ -895,7 +896,7 @@ else version (Posix) enum uint ticks_per_second = 1_000_000_000; enum uint nsec_multiplier = 1; } -else version (BL808) +else version (Bouffalo) { enum uint ticks_per_second = mtime_freq_hz; enum uint nsec_multiplier = 1_000_000_000 / mtime_freq_hz; From 3037d0a32ae2124937dce68ea2b66eb300589ca8 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 10 Apr 2026 02:47:42 +1000 Subject: [PATCH 120/138] Rejigged a centralised driver API, and implemented it for a bunch of embedded targets. --- Makefile | 1 + src/sys/baremetal/can.d | 374 +++++++++++++++++++++ src/sys/baremetal/irq.d | 215 ++++++++++++ src/sys/baremetal/timer.d | 231 +++++++++++++ src/sys/baremetal/uart.d | 472 +++++++++++++++++++++++++++ src/sys/baremetal/wifi.d | 454 ++++++++++++++++++++++++++ src/sys/bk7231/irq.d | 85 +++++ src/sys/bk7231/package.d | 32 ++ src/sys/bk7231/start.S | 108 ++++++ src/sys/bk7231/syscalls.d | 55 ++++ src/sys/bk7231/timer.d | 143 ++++++++ src/sys/bk7231/uart.d | 385 ++++++++++++++++++++++ src/sys/bl618/irq.d | 16 +- src/sys/bl618/package.d | 4 +- src/sys/bl618/syscalls.d | 16 +- src/sys/bl618/timer.d | 48 +-- src/sys/bl618/uart.d | 44 +-- src/sys/bl808/irq.d | 7 + src/sys/bl808/package.d | 4 +- src/sys/bl808/syscalls.d | 2 +- src/sys/bl808/timer.d | 5 + src/sys/bl808/uart.d | 71 ++-- src/sys/esp32/can.d | 222 +++++++++++++ src/sys/esp32/irq.d | 59 ++++ src/sys/esp32/ow_shim.c | 278 ++++++++-------- src/sys/esp32/timer.d | 38 +++ src/sys/esp32/uart.d | 100 ++++++ src/sys/esp32/wifi.d | 302 +++++++++++++++++ src/sys/rp2350/boot2.S | 69 ++++ src/sys/rp2350/irq.d | 59 ++++ src/sys/rp2350/package.d | 96 ++++++ src/sys/rp2350/start.S | 244 ++++++++++++++ src/sys/rp2350/syscalls.d | 52 +++ src/sys/rp2350/timer.d | 80 +++++ src/sys/rp2350/uart.d | 195 +++++++++++ src/sys/stm32/irq.d | 59 ++++ src/sys/stm32/package.d | 108 ++++++ src/sys/stm32/start.S | 195 +++++++++++ src/sys/stm32/syscalls.d | 50 +++ src/sys/stm32/timer.d | 73 +++++ src/sys/stm32/uart.d | 260 +++++++++++++++ src/urt/digest/sha.d | 249 +++++++++----- src/urt/exception.d | 5 +- src/urt/internal/exception.d | 4 +- src/urt/internal/sys/posix/package.d | 3 +- src/urt/io.d | 2 +- src/urt/package.d | 5 + src/urt/platform.d | 40 ++- src/urt/socket.d | 2 +- src/urt/system.d | 54 +-- src/urt/time.d | 118 +++---- 51 files changed, 5326 insertions(+), 467 deletions(-) create mode 100644 src/sys/baremetal/can.d create mode 100644 src/sys/baremetal/irq.d create mode 100644 src/sys/baremetal/timer.d create mode 100644 src/sys/baremetal/uart.d create mode 100644 src/sys/baremetal/wifi.d create mode 100644 src/sys/bk7231/irq.d create mode 100644 src/sys/bk7231/package.d create mode 100644 src/sys/bk7231/start.S create mode 100644 src/sys/bk7231/syscalls.d create mode 100644 src/sys/bk7231/timer.d create mode 100644 src/sys/bk7231/uart.d create mode 100644 src/sys/esp32/can.d create mode 100644 src/sys/esp32/irq.d create mode 100644 src/sys/esp32/timer.d create mode 100644 src/sys/esp32/uart.d create mode 100644 src/sys/esp32/wifi.d create mode 100644 src/sys/rp2350/boot2.S create mode 100644 src/sys/rp2350/irq.d create mode 100644 src/sys/rp2350/package.d create mode 100644 src/sys/rp2350/start.S create mode 100644 src/sys/rp2350/syscalls.d create mode 100644 src/sys/rp2350/timer.d create mode 100644 src/sys/rp2350/uart.d create mode 100644 src/sys/stm32/irq.d create mode 100644 src/sys/stm32/package.d create mode 100644 src/sys/stm32/start.S create mode 100644 src/sys/stm32/syscalls.d create mode 100644 src/sys/stm32/timer.d create mode 100644 src/sys/stm32/uart.d diff --git a/Makefile b/Makefile index 497d35c..324c6be 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ DEPFILE := $(OBJDIR)/$(TARGETNAME).d DFLAGS := $(DFLAGS) -preview=bitfields -preview=rvaluerefparam -preview=nosharedaccess -preview=in SOURCES := $(shell find "$(SRCDIR)" -type f -name '*.d' -not -path '$(SRCDIR)/sys/*') +SOURCES := $(SOURCES) $(shell find "$(SRCDIR)/sys/baremetal" -type f -name '*.d') SOURCES := $(SOURCES) $(SRCDIR)/urt/internal/mbedtls.c ifeq ($(PLATFORM),riscv64) diff --git a/src/sys/baremetal/can.d b/src/sys/baremetal/can.d new file mode 100644 index 0000000..41857a9 --- /dev/null +++ b/src/sys/baremetal/can.d @@ -0,0 +1,374 @@ +module sys.baremetal.can; + +import urt.result : Result, InternalResult; + +version (Espressif) + public import sys.esp32.can; +else +{ + enum uint num_can = 0; + enum bool has_can_fd = false; +} + +nothrow @nogc: + + +enum CanError : ubyte +{ + none = 0, + bit = 1 << 0, // bit error (dominant/recessive mismatch) + stuff = 1 << 1, // bit stuffing violation + crc = 1 << 2, // CRC mismatch + form = 1 << 3, // fixed-form bit field violation + ack = 1 << 4, // no ACK from any receiver + overrun = 1 << 5, // RX FIFO overflow +} + +enum CanBusState : ubyte +{ + error_active, // TEC/REC < 128, normal operation + error_warning, // TEC or REC >= 96 + error_passive, // TEC or REC >= 128, can't send active error frames + bus_off, // TEC >= 256, node disconnected from bus +} + +struct CanConfig +{ + uint bitrate = 500_000; // nominal bitrate (bits/s) + uint data_bitrate; // FD data phase bitrate (0 = classic CAN only) + ubyte tx_gpio = ubyte.max; // GPIO pin for TX (max = platform default) + ubyte rx_gpio = ubyte.max; // GPIO pin for RX (max = platform default) + + // Bit timing (0 = auto-calculate from bitrate) + ubyte sjw; // synchronization jump width (1-4 TQ) + ubyte tseg1; // time segment 1 / prop + phase1 (1-16 TQ) + ubyte tseg2; // time segment 2 / phase2 (1-8 TQ) + ushort brp; // baud rate prescaler (1-1024) +} + +struct CanFrame +{ + uint id; // 11-bit standard or 29-bit extended + bool extended; // true = 29-bit ID, false = 11-bit + bool rtr; // remote transmission request + bool fd; // CAN FD frame (up to 64 bytes, DLC 9-15 = 12..64) + bool brs; // FD bit rate switch (data phase at data_bitrate) + ubyte dlc; // data length code (0-8 classic, 0-15 FD) + ubyte[8] data; // classic CAN payload (FD >8 bytes needs external buffer) +} + +struct CanFilter +{ + uint id; // ID to match + uint mask; // 1-bits = must match, 0-bits = don't care + bool extended; // match extended frames only + bool rtr; // match RTR frames only + bool match_all; // ignore id/mask, accept everything (default) +} + +// Called from ISR when a frame has been received. +alias CanRxCallback = void function(Can can, ref const CanFrame frame) nothrow @nogc; + +// Called from ISR when a TX mailbox becomes free. +alias CanTxCallback = void function(Can can) nothrow @nogc; + +// Called when bus state changes (error active/passive/bus-off). +alias CanBusStateCallback = void function(Can can, CanBusState state) nothrow @nogc; + +// Caller-owned transmit operation token. +struct CanTxOp +{ + enum Status : ubyte + { + idle, + pending, + complete, + cancelled, + error, + arbitration_lost, + } + + alias Callback = void function(Can* can, ref CanTxOp op) nothrow @nogc; + + Status status; + void* user_data; + Callback cb; + + bool is_pending() const => status == Status.pending; + bool is_done() const => status >= Status.complete; +} + +struct Can +{ + ubyte port = ubyte.max; +} + +bool is_open(ref const Can can) +{ + return can.port != ubyte.max; +} + + +// ════════════════════════════════════════════════════════════════════ +// Error type +// ════════════════════════════════════════════════════════════════════ + +CanError can_result(Result result) +{ + return cast(CanError)result.system_code; +} + + +// ════════════════════════════════════════════════════════════════════ +// Implementation +// ════════════════════════════════════════════════════════════════════ + +// Lifecycle + +void can_init() +{ + if (_init_refcount++ == 0) + { + // TODO: enable clocks/power for CAN peripheral block + } +} + +void can_deinit() +{ + assert(_init_refcount > 0); + if (--_init_refcount == 0) + { + // TODO: disable clocks/power for CAN peripheral block + } +} + +// Port operations + +Result can_open(ref Can can, ubyte port, ref const CanConfig cfg, CanRxCallback rx_cb = null) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + { + if (port >= num_can) + return InternalResult.invalid_parameter; + + if (!can_open(port, cfg)) + return InternalResult.failed; + + can.port = port; + return Result.success; + } +} + +Result can_reconfigure(ref Can can, ref const CanConfig cfg) +{ + assert(false, "TODO: can_reconfigure (hot reconfigure)"); +} + +void can_close(ref Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + can_close(can.port); + can.port = ubyte.max; +} + +// Transmit + +int can_transmit(ref Can can, ref const CanFrame frame, CanTxOp* op = null) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + { + int ret = can_transmit(can.port, frame); + if (op !is null) + { + op.status = ret >= 0 ? CanTxOp.Status.complete : CanTxOp.Status.error; + if (op.cb !is null) + op.cb(&can, *op); + } + return ret; + } +} + +void can_tx_abort(ref Can can, CanTxOp* op) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + { + can_tx_abort(can.port); + if (op !is null) + { + op.status = CanTxOp.Status.cancelled; + if (op.cb !is null) + op.cb(&can, *op); + } + } +} + +// Receive + +bool can_receive(ref Can can, out CanFrame frame) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + return can_receive(can.port, frame); +} + +size_t can_rx_available(ref const Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + return can_rx_available(can.port); +} + +void can_rx_flush(ref Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + can_rx_flush(can.port); +} + +// Filters + +Result can_filter_set(ref Can can, ubyte slot, ref const CanFilter filter) +{ + assert(false, "TODO: can_filter_set"); +} + +Result can_filter_clear(ref Can can, ubyte slot) +{ + assert(false, "TODO: can_filter_clear"); +} + +void can_filter_clear_all(ref Can can) +{ + assert(false, "TODO: can_filter_clear_all"); +} + +// Bus status + +CanBusState can_bus_state(ref const Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + return can_bus_state(can.port); +} + +ubyte can_tx_error_count(ref const Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + return can_tx_error_count(can.port); +} + +ubyte can_rx_error_count(ref const Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + return can_rx_error_count(can.port); +} + +CanError can_check_errors(ref Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + return can_check_errors(can.port); +} + +// Bus recovery + +Result can_bus_recover(ref Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + { + if (can_bus_recover(can.port)) + return Result.success; + return InternalResult.failed; + } +} + +// Callbacks + +void can_set_rx_callback(ref Can can, CanRxCallback cb) +{ + assert(false, "TODO: can_set_rx_callback"); +} + +void can_set_tx_callback(ref Can can, CanTxCallback cb) +{ + assert(false, "TODO: can_set_tx_callback"); +} + +void can_set_bus_state_callback(ref Can can, CanBusStateCallback cb) +{ + assert(false, "TODO: can_set_bus_state_callback"); +} + +// Poll (for polled mode - check HW FIFOs and invoke callbacks) + +void can_poll(ref Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + can_poll(can.port); +} + + +// ════════════════════════════════════════════════════════════════════ +// Tests +// ════════════════════════════════════════════════════════════════════ + +unittest +{ + static if (num_can > 0) + { + Can c; + CanConfig cfg; + + can_init(); + + // Out-of-range port + auto r = can_open(c, cast(ubyte)num_can, cfg); + assert(!r); + assert(!c.is_open); + + // Open/close each valid port + foreach (p; 0 .. num_can) + { + Can port; + auto r2 = can_open(port, cast(ubyte)p, cfg); + assert(r2, "can_open failed"); + assert(port.is_open); + assert(port.port == p); + + can_poll(port); + + assert(can_check_errors(port) == CanError.none); + + can_close(port); + assert(!port.is_open); + } + + can_deinit(); + } +} + + +private: + +__gshared ubyte _init_refcount; diff --git a/src/sys/baremetal/irq.d b/src/sys/baremetal/irq.d new file mode 100644 index 0000000..6cfd9df --- /dev/null +++ b/src/sys/baremetal/irq.d @@ -0,0 +1,215 @@ +// Unified baremetal interrupt controller driver +// +// Normalizes the IRQ API across RISC-V PLIC, ARM NVIC, Beken ICU, etc. +// Platform drivers export capabilities; the baremetal layer re-exports +// them and provides a uniform function surface. +module sys.baremetal.irq; + +version (BL808_M0) + public import sys.bl618.irq; +else version (BL808) + public import sys.bl808.irq; +else version (BL618) + public import sys.bl618.irq; +else version (Beken) + public import sys.bk7231.irq; +else version (RP2350) + public import sys.rp2350.irq; +else version (STM32) + public import sys.stm32.irq; +else version (Espressif) + public import sys.esp32.irq; +else +{ + enum bool has_plic = false; + enum bool has_nvic = false; + enum bool has_per_irq_control = false; + enum bool has_irq_priority = false; + enum bool has_wait_for_interrupt = false; + enum bool has_irq_diagnostics = false; + enum uint irq_max = 0; +} + +nothrow @nogc: + +alias IrqHandler = void function(uint irq) nothrow @nogc; + + +// ════════════════════════════════════════════════════════════════════ +// Driver API +// ════════════════════════════════════════════════════════════════════ + +// Global interrupt control + +// Disable all interrupt delivery. Returns previous state. +bool irq_global_disable() +{ + static if (has_plic) + return disable_interrupts(); + else static if (irq_max > 0) + { + irq_disable(); + return true; + } + else + assert(false, "no IRQ controller"); +} + +// Enable all interrupt delivery. Returns previous state. +bool irq_global_enable() +{ + static if (has_plic) + return enable_interrupts(); + else static if (irq_max > 0) + { + irq_enable(); + return true; + } + else + assert(false, "no IRQ controller"); +} + +// Set global interrupt state. Returns previous state. +bool irq_global_set(bool enabled) +{ + return enabled ? irq_global_enable() : irq_global_disable(); +} + +// RAII-style critical section guard. +// Usage: auto guard = irq_critical(); +struct IrqGuard +{ + private bool _prev; + @disable this(); + @disable this(this); + + ~this() nothrow @nogc + { + irq_global_set(_prev); + } +} + +IrqGuard irq_critical() +{ + IrqGuard g = void; + g._prev = irq_global_disable(); + return g; +} + +// Per-IRQ control + +// Enable a specific peripheral interrupt. Returns previous state. +bool irq_line_enable(uint irq) +{ + static if (has_per_irq_control) + { + static if (has_plic) + return enable_irq(irq); + else + { + irq_set_enable(irq); + return false; + } + } + else + assert(false, "no per-IRQ control"); +} + +// Disable a specific peripheral interrupt. Returns previous state. +bool irq_line_disable(uint irq) +{ + static if (has_per_irq_control) + { + static if (has_plic) + return disable_irq(irq); + else + { + irq_clear_enable(irq); + return false; + } + } + else + assert(false, "no per-IRQ control"); +} + +// Set priority for a peripheral interrupt (0 = highest). +void irq_line_set_priority(uint irq, ubyte priority) +{ + static if (has_irq_priority) + irq_set_priority(irq, priority); + else + assert(false, "no IRQ priority support"); +} + +// Handler registration + +// Install an interrupt handler. Returns the previous handler for chaining. +IrqHandler irq_handler_set(IrqHandler handler) +{ + static if (has_plic) + return irq_set_handler(handler); + else + assert(false, "TODO: irq_handler_set for this platform"); +} + +// Power management + +// Halt CPU until an interrupt fires. Near-zero power draw. +void irq_wait() +{ + static if (has_wait_for_interrupt) + wait_for_interrupt(); + else + assert(false, "WFI not available"); +} + +// Diagnostics + +static if (has_irq_diagnostics) +{ + ref uint irq_total_count() + { + return irq_count; + } + + uint[] irq_hit_histogram() + { + return irq_histogram[]; + } +} + + +// ════════════════════════════════════════════════════════════════════ +// Tests +// ════════════════════════════════════════════════════════════════════ + +unittest +{ + static if (has_plic) static assert(has_per_irq_control); + static if (has_nvic) static assert(has_per_irq_control); + + static if (irq_max > 0) + { + // Global disable/enable round-trip + bool prev = irq_global_disable(); + irq_global_enable(); + + // Critical section guard + { + auto guard = irq_critical(); + } + + static if (has_per_irq_control) + { + irq_line_disable(0); + irq_line_enable(0); + } + + static if (has_irq_diagnostics) + { + uint total = irq_total_count(); + uint[] hist = irq_hit_histogram(); + assert(hist.length == irq_max); + } + } +} diff --git a/src/sys/baremetal/timer.d b/src/sys/baremetal/timer.d new file mode 100644 index 0000000..b047557 --- /dev/null +++ b/src/sys/baremetal/timer.d @@ -0,0 +1,231 @@ +module sys.baremetal.timer; + +import urt.time : Duration; + +version (BL808_M0) + public import sys.bl618.timer; +else version (BL808) + public import sys.bl808.timer; +else version (BL618) + public import sys.bl618.timer; +else version (Beken) + public import sys.bk7231.timer; +else version (RP2350) + public import sys.rp2350.timer; +else version (STM32) + public import sys.stm32.timer; +else version (Espressif) + public import sys.esp32.timer; +else +{ + enum uint mtime_freq_hz = 0; + enum bool has_mtime = false; + enum bool has_rtc = false; + enum bool has_mcycle = false; + enum bool has_timer_stop = false; + enum bool has_wfi_sleep = false; +} + +nothrow @nogc: + + +alias TimerCallback = void function() nothrow @nogc; + +// ════════════════════════════════════════════════════════════════════ +// Driver API +// ════════════════════════════════════════════════════════════════════ + +// Lifecycle + +void timer_init() +{ + if (_init_refcount++ == 0) + { + version (Beken) + timer_hw_init(); + } +} + +void timer_deinit() +{ + assert(_init_refcount > 0); + if (--_init_refcount == 0) + { + static if (has_timer_stop) + periodic_stop(); + static if (has_rtc) + rtc_stop(); + // TODO: disable timer IRQs at the interrupt controller level + } +} + +// Monotonic clock + +// Read the monotonic tick counter. Units are platform-specific; +// use mtime_freq_hz to convert to real time. +static if (has_mtime) + alias monotonic_read = mtime_read; +else +{ + ulong monotonic_read() + { + assert(false, "monotonic_read not available"); + } +} + +// Read CPU cycle counter (for profiling, NOT timekeeping). +// Stops during WFI, rate changes with clock scaling. +static if (has_mcycle) + alias cycle_read = mcycle_read; +else +{ + ulong cycle_read() + { + assert(false, "cycle_read not available"); + } +} + +// Periodic tick + +// Set up a periodic timer interrupt at the given interval. +void periodic_set(Duration interval, TimerCallback cb) +{ + static if (has_mtime) + { + ulong ticks = interval.as!"nsecs" * mtime_freq_hz / 1_000_000_000; + timer_set_periodic(cast(uint)ticks, cb); + } + else + assert(false, "TODO: periodic_set not available"); +} + +// Stop the periodic timer. +void periodic_stop() +{ + static if (has_timer_stop) + timer_stop(); + else + assert(false, "TODO: periodic_stop not available"); +} + +// One-shot wakeup + +// Schedule a one-shot interrupt at the given absolute tick value. +// Used by sleep() to wake from WFI. +void oneshot_set(ulong tick_value) +{ + static if (has_wfi_sleep) + mtimecmp_write_oneshot(tick_value); + else + assert(false, "TODO: oneshot_set not available"); +} + +// RTC (battery-backed real-time counter) + +// Enable the RTC counter. Does not reset it. +void rtc_start() +{ + static if (has_rtc) + rtc_enable(); + else + assert(false, "TODO: rtc_start not available"); +} + +// Disable and reset the RTC counter to zero. +void rtc_stop() +{ + static if (has_rtc) + rtc_reset(); + else + assert(false, "TODO: rtc_stop not available"); +} + +// Read the RTC tick counter. +static if (has_rtc) + alias rtc_now = rtc_read; +else +{ + ulong rtc_now() + { + assert(false, "TODO: rtc_now not available"); + } +} + +// Access persistent state in battery-retained RAM. +static if (has_rtc) +{ + HbnPersist* persistent_state() + { + return hbn_persist(); + } +} + + +// ════════════════════════════════════════════════════════════════════ +// Tests +// ════════════════════════════════════════════════════════════════════ + +unittest +{ + // Capabilities are consistent + static assert(mtime_freq_hz > 0 || !has_mtime); + + timer_init(); + + // Monotonic clock + static if (has_mtime) + { + ulong t1 = monotonic_read(); + ulong t2 = monotonic_read(); + assert(t2 >= t1); + } + + // Cycle counter + static if (has_mcycle) + { + ulong c1 = cycle_read(); + ulong c2 = cycle_read(); + assert(c2 > c1); + } + + // One-shot wakeup + static if (has_wfi_sleep) + { + // Set a oneshot far in the future, then cancel by setting to max. + // This verifies the register write doesn't crash. + ulong now = monotonic_read(); + oneshot_set(now + mtime_freq_hz); // 1 second from now + oneshot_set(ulong.max); // cancel + } + + // Periodic set/stop (only test on platforms that support stop, + // otherwise we can't clean up) + static if (has_mtime && has_timer_stop) + { + __gshared bool tick_fired = false; + periodic_set(Duration.from!"msecs"(50), () { tick_fired = true; }); + periodic_stop(); + // We can't easily test that the callback fires without + // waiting + running the interrupt, but at least verify + // set/stop don't crash. + } + + // RTC + static if (has_rtc) + { + rtc_start(); + ulong r1 = rtc_now(); + ulong r2 = rtc_now(); + assert(r2 >= r1); + + auto p = persistent_state(); + assert(p !is null); + } + + timer_deinit(); +} + + +private: + +__gshared ubyte _init_refcount; diff --git a/src/sys/baremetal/uart.d b/src/sys/baremetal/uart.d new file mode 100644 index 0000000..ce4762d --- /dev/null +++ b/src/sys/baremetal/uart.d @@ -0,0 +1,472 @@ +module sys.baremetal.uart; + +import urt.result : Result, InternalResult; +import urt.time : Duration; + +version (BL808_M0) + public import sys.bl618.uart; +else version (BL808) + public import sys.bl808.uart; +else version (BL618) + public import sys.bl618.uart; +else version (Beken) + public import sys.bk7231.uart; +else version (RP2350) + public import sys.rp2350.uart; +else version (STM32) + public import sys.stm32.uart; +else version (Espressif) + public import sys.esp32.uart; +else + enum uint num_uarts = 0; + +nothrow @nogc: + + +enum UartError : ubyte +{ + none = 0, + framing = 1 << 0, + parity = 1 << 1, + overrun = 1 << 2, + noise = 1 << 3, + break_ = 1 << 4, +} + +enum StopBits : ubyte +{ + half, + one, + one_point_five, + two, +} + +enum Parity : ubyte +{ + none, + even, + odd, + mark, + space +} + +enum FlowControl : ubyte +{ + none, + hardware, + software, + dsr_dtr, + + rts_cts = hardware, + xon_xoff = software +} + +enum DriveMode : ubyte +{ + polled, // caller must call uart_poll; no interrupts + interrupt, // IRQ-driven FIFO drain into software ring buffer + dma, // DMA circular RX, DMA one-shot TX + auto_, // driver picks best available (dma > interrupt > polled) +} + +struct Rs485Config +{ + bool enabled; + bool de_active_high = true; // DE pin polarity + ubyte de_gpio = ubyte.max; // GPIO pin for driver enable (max = auto/hardware) + ushort de_assert_us; // us to assert DE before first TX bit + ushort de_deassert_us; // us to hold DE after last TX bit + ushort turnaround_us; // minimum idle time between RX end and TX start +} + +struct UartConfig +{ + uint baud_rate = 115200; + ubyte data_bits = 8; + StopBits stop_bits = StopBits.one; + Parity parity = Parity.none; + FlowControl flow_control = FlowControl.none; + DriveMode drive_mode = DriveMode.auto_; + ubyte tx_gpio = ubyte.max; // GPIO pin for TX (max = platform default) + ubyte rx_gpio = ubyte.max; // GPIO pin for RX (max = platform default) + ubyte rts_gpio = ubyte.max; // GPIO pin for RTS (max = platform default) + ubyte cts_gpio = ubyte.max; // GPIO pin for CTS (max = platform default) + Rs485Config rs485; +} + +// Called from ISR/DMA when received data is available in the RX buffer. +// rx_avail: number of bytes ready to read. +alias UartRxCallback = void function(Uart uart, size_t rx_avail) nothrow @nogc; + +// Called from ISR/DMA when TX buffer space becomes available (e.g. FIFO +// drains below threshold). The callee should feed more data via uart_write. +// tx_avail: number of bytes that can be written to the TX buffer. +alias UartTxCallback = void function(Uart uart, size_t tx_avail) nothrow @nogc; + +// Called from ISR to pull the next chunk of data for an async transmit. +// offset: bytes already supplied so far (including initial buffer). +// The driver provides a buffer; the callee fills it and returns how many +// bytes were written. Called repeatedly until the transfer length is met. +alias UartTxSupplyCallback = size_t function(Uart uart, size_t offset, void[] buf) nothrow @nogc; + +// Called when the RX line has been idle for the configured threshold. +// Used for RS-485 frame boundary detection and bus arbitration. +alias UartLineIdleCallback = void function(Uart uart) nothrow @nogc; + +// Caller-owned write operation token. Pass to any write call to track +// completion and support cancellation. The driver writes status from +// ISR context; the caller polls it from main context. +struct UartWriteOp +{ + enum Status : ubyte + { + idle, // not submitted + pending, // driver is working on it + complete, // all bytes consumed / transmitted + cancelled, // cancelled via uart_write_cancel + error, // hardware error during transfer + } + + alias Callback = void function(Uart* uart, ref UartWriteOp op) nothrow @nogc; + + Status status; + size_t bytes_sent; // updated by driver as transfer progresses + void* user_data; + Callback cb; + + bool is_pending() const => status == Status.pending; + bool is_done() const => status >= Status.complete; +} + +struct Uart +{ + ubyte port = ubyte.max; +} + +bool is_open(ref const Uart uart) +{ + return uart.port != ubyte.max; +} + + +// ════════════════════════════════════════════════════════════════════ +// Error type +// ════════════════════════════════════════════════════════════════════ + +UartError uart_result(Result result) +{ + return cast(UartError)result.system_code; +} + + +// ════════════════════════════════════════════════════════════════════ +// Implementation +// ════════════════════════════════════════════════════════════════════ + +// Lifecycle + +void uart_init() +{ + if (_init_refcount++ == 0) + { + // TODO: enable clocks/power for UART peripheral block + } +} + +void uart_deinit() +{ + assert(_init_refcount > 0); + if (--_init_refcount == 0) + { + // TODO: disable clocks/power for UART peripheral block + } +} + +// Port operations + +Result uart_open(ref Uart uart, ubyte port, ref const UartConfig cfg, size_t buf_size = 0, UartRxCallback rx_cb = null, UartTxCallback tx_cb = null) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + { + if (port >= num_uarts) + return InternalResult.invalid_parameter; + + if (!uart_hw_open(port, cfg)) + return InternalResult.failed; + + uart.port = port; + return Result.success; + } +} + +Result uart_reconfigure(ref Uart uart, ref const UartConfig cfg) +{ + assert(false, "TODO: uart_reconfigure (hot reconfigure)"); +} + +void uart_close(ref Uart uart) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + uart_hw_close(uart.port); + uart.port = ubyte.max; +} + +// Single-byte I/O + +bool uart_putc(ref Uart uart, ubyte c) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + return uart_hw_write(uart.port, (cast(void*)&c)[0 .. 1]) == 1; +} + +bool uart_getc(ref Uart uart, out ubyte c) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + return uart_hw_read(uart.port, (cast(void*)&c)[0 .. 1]) == 1; +} + +// Bulk I/O + +ptrdiff_t uart_read(ref Uart uart, void[] buffer, Duration timeout = Duration.zero) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + { + if (timeout != Duration.zero) + assert(false, "TODO: uart_read with timeout"); + uart_hw_poll(uart.port); + return uart_hw_read(uart.port, buffer); + } +} + +ptrdiff_t uart_write(ref Uart uart, const(void)[] data, UartWriteOp* op = null) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + { + auto n = uart_hw_write(uart.port, data); + if (op !is null) + { + op.bytes_sent = n > 0 ? cast(size_t)n : 0; + op.status = n >= 0 ? UartWriteOp.Status.complete : UartWriteOp.Status.error; + if (op.cb !is null) + op.cb(&uart, *op); + } + return n; + } +} + +ptrdiff_t uart_writev(ref Uart uart, const(void[])[] buffers, UartWriteOp* op = null) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + { + ptrdiff_t total = 0; + foreach (buf; buffers) + { + auto n = uart_hw_write(uart.port, buf); + if (n < 0) + { + if (op !is null) + { + op.bytes_sent = cast(size_t)total; + op.status = UartWriteOp.Status.error; + if (op.cb !is null) + op.cb(&uart, *op); + } + return n; + } + total += n; + if (n < buf.length) + break; + } + if (op !is null) + { + op.bytes_sent = cast(size_t)total; + op.status = UartWriteOp.Status.complete; + if (op.cb !is null) + op.cb(&uart, *op); + } + return total; + } +} + +ptrdiff_t uart_write_async(ref Uart uart, size_t total_len, UartTxSupplyCallback supply_cb, const(void)[] initial = null, UartWriteOp* op = null) +{ + assert(false, "TODO: uart_write_async (needs ISR integration)"); +} + +void uart_write_cancel(ref Uart uart, UartWriteOp* op) +{ + assert(false, "TODO: uart_write_cancel"); +} + +// Buffer queries + +size_t uart_rx_available(ref const Uart uart) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + { + auto n = uart_hw_rx_pending(uart.port); + return n > 0 ? cast(size_t)n : 0; + } +} + +size_t uart_tx_available(ref const Uart uart) +{ + assert(false, "TODO: uart_tx_available"); +} + +size_t uart_tx_queued(ref const Uart uart) +{ + assert(false, "TODO: uart_tx_queued"); +} + +// Buffer control + +void uart_rx_flush(ref Uart uart) +{ + assert(false, "TODO: uart_rx_flush"); +} + +void uart_tx_flush(ref Uart uart) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + uart_hw_flush(uart.port); +} + +void uart_tx_drain(ref Uart uart) +{ + assert(false, "TODO: uart_tx_drain (block until TX idle)"); +} + +void uart_tx_abort(ref Uart uart) +{ + assert(false, "TODO: uart_tx_abort"); +} + +// Line status + +bool uart_line_idle(ref const Uart uart) +{ + assert(false, "TODO: uart_line_idle"); +} + +void uart_set_idle_threshold(ref Uart uart, uint bit_times) +{ + assert(false, "TODO: uart_set_idle_threshold"); +} + +void uart_set_idle_callback(ref Uart uart, UartLineIdleCallback cb) +{ + assert(false, "TODO: uart_set_idle_callback"); +} + +// TX timing + +void uart_tx_gap(ref Uart uart, Duration gap) +{ + assert(false, "TODO: uart_tx_gap"); +} + +void uart_send_break(ref Uart uart, Duration duration) +{ + assert(false, "TODO: uart_send_break"); +} + +// Error and status + +UartError uart_check_errors(ref Uart uart) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + return uart_hw_check_errors(uart.port) ? UartError.framing : UartError.none; +} + +void uart_poll(ref Uart uart) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + uart_hw_poll(uart.port); +} + +// Early boot (pre-driver, polled, blocking) + +void uart0_putc(ubyte c) +{ + static if (num_uarts == 0) {} + else uart0_puts((cast(char*)&c)[0 .. 1]); +} + +void uart0_puts(const(char)[] s) +{ + static if (num_uarts == 0) {} + else uart0_hw_puts(s); +} + + +// ════════════════════════════════════════════════════════════════════ +// Tests +// ════════════════════════════════════════════════════════════════════ + +unittest +{ + static if (num_uarts > 0) + { + Uart u; + UartConfig cfg; + + uart_init(); + + // Out-of-range port + auto r = uart_open(u, cast(ubyte)num_uarts, cfg); + assert(!r); + assert(!u.is_open); + + // Open/close each valid port (skip port 0 -- it's usually the console) + foreach (p; 1 .. num_uarts) + { + Uart port; + auto r2 = uart_open(port, cast(ubyte)p, cfg); + assert(r2, "uart_open failed"); + assert(port.is_open); + assert(port.port == p); + + // RX should be empty on a freshly opened port + assert(uart_rx_available(port) == 0); + + // Poll should not crash + uart_poll(port); + + // Check errors on a clean port + assert(uart_check_errors(port) == UartError.none); + + uart_close(port); + assert(!port.is_open); + } + + uart_deinit(); + } +} + + +private: + +__gshared ubyte _init_refcount; diff --git a/src/sys/baremetal/wifi.d b/src/sys/baremetal/wifi.d new file mode 100644 index 0000000..4ec37cc --- /dev/null +++ b/src/sys/baremetal/wifi.d @@ -0,0 +1,454 @@ +module sys.baremetal.wifi; + +import urt.result : Result, InternalResult; + +version (Espressif) + public import sys.esp32.wifi; +else + enum uint num_wifi = 0; + +nothrow @nogc: + + +// ════════════════════════════════════════════════════════════════════ +// Types +// ════════════════════════════════════════════════════════════════════ + +enum WifiError : ubyte +{ + none = 0, + auth_failed = 1 << 0, + no_ap = 1 << 1, // target AP not found + assoc_failed = 1 << 2, // association rejected + timeout = 1 << 3, + tx_failed = 1 << 4, + internal = 1 << 5, +} + +// Virtual interface type within a radio. +enum WifiVif : ubyte +{ + sta, + ap, +} + +enum WifiMode : ubyte +{ + none, // radio idle, no virtual interfaces active + sta, // station only + ap, // access point only + apsta, // concurrent AP + STA +} + +enum WifiAuth : ubyte +{ + open, + wep, + wpa_psk, + wpa2_psk, + wpa_wpa2_psk, + wpa3_psk, + wpa2_wpa3_psk, + wpa2_enterprise, + wpa3_enterprise, +} + +enum WifiBand : ubyte +{ + any, + band_2g4, // 2.4 GHz + band_5g, // 5 GHz + band_6g, // 6 GHz (Wi-Fi 6E) +} + +enum WifiBandwidth : ubyte +{ + bw_20mhz, + bw_40mhz, + bw_80mhz, + bw_160mhz, +} + +enum WifiEvent : ubyte +{ + sta_connected, + sta_disconnected, + ap_started, + ap_stopped, + ap_sta_connected, // a client joined our AP + ap_sta_disconnected, // a client left our AP + scan_done, +} + +struct WifiConfig +{ + byte tx_power; // dBm (0 = platform default) + ubyte channel; // fixed channel (0 = auto) + WifiBand band; + ubyte[2] country; // ISO 3166-1 alpha-2 (e.g. "US"), 0 = default +} + +struct WifiStaConfig +{ + const(char)[] ssid; + const(char)[] password; + ubyte[6] bssid; // filter by BSSID (all zeros = any) + WifiBand band; + bool pmf_required; // protected management frames +} + +struct WifiApConfig +{ + const(char)[] ssid; + const(char)[] password; + WifiAuth auth = WifiAuth.wpa2_wpa3_psk; + ubyte channel; // 0 = auto + ubyte max_clients = 4; + bool hidden; // suppress SSID broadcast + WifiBandwidth bandwidth; +} + +struct WifiScanConfig +{ + const(char)[] ssid; // filter by SSID (empty = all) + ubyte[6] bssid; // filter by BSSID (all zeros = any) + ubyte channel; // scan single channel (0 = all) + WifiBand band; + bool passive; // passive scan (listen only, no probe requests) + ushort dwell_ms; // per-channel dwell time (0 = platform default) +} + +struct WifiScanResult +{ + ubyte[6] bssid; + ubyte channel; + byte rssi; // dBm + WifiAuth auth; + WifiBand band; + WifiBandwidth bandwidth; + ubyte ssid_len; + char[32] ssid_buf; + + const(char)[] ssid() const pure + => ssid_buf[0 .. ssid_len]; +} + +struct WifiStaInfo +{ + ubyte[6] mac; + byte rssi; +} + +// Called from ISR/driver when an Ethernet frame is received on a +// virtual interface. Data includes the 14-byte Ethernet header. +alias WifiRxCallback = void function(Wifi wifi, WifiVif vif, const(ubyte)[] data) nothrow @nogc; + +// Called when a wifi event occurs. Replaces per-event callbacks +// to match the single-callback pattern used by the router layer. +alias WifiEventCallback = void function(Wifi wifi, WifiEvent event, const(void)* data) nothrow @nogc; + +struct Wifi +{ + ubyte port = ubyte.max; +} + +bool is_open(ref const Wifi wifi) +{ + return wifi.port != ubyte.max; +} + + +// ════════════════════════════════════════════════════════════════════ +// Error type +// ════════════════════════════════════════════════════════════════════ + +WifiError wifi_result(Result result) +{ + return cast(WifiError)result.system_code; +} + + +// ════════════════════════════════════════════════════════════════════ +// Implementation +// ════════════════════════════════════════════════════════════════════ + +// Lifecycle + +void wifi_init() +{ + if (_init_refcount++ == 0) + { + // TODO: enable clocks/power for WiFi peripheral block + } +} + +void wifi_deinit() +{ + assert(_init_refcount > 0); + if (--_init_refcount == 0) + { + // TODO: disable clocks/power for WiFi peripheral block + } +} + +// Radio operations + +Result wifi_open(ref Wifi wifi, ubyte port, ref const WifiConfig cfg) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (port >= num_wifi) + return InternalResult.invalid_parameter; + + if (!wifi_open(port, cfg)) + return InternalResult.failed; + + wifi.port = port; + return Result.success; + } +} + +void wifi_close(ref Wifi wifi) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + wifi_close(wifi.port); + wifi.port = ubyte.max; +} + +Result wifi_set_mode(ref Wifi wifi, WifiMode mode) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_set_mode(wifi.port, mode)) + return InternalResult.failed; + return Result.success; + } +} + +// Station (client) operations + +Result wifi_sta_configure(ref Wifi wifi, ref const WifiStaConfig cfg) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_sta_configure(wifi.port, cfg)) + return InternalResult.failed; + return Result.success; + } +} + +// Begin association. Completion is signalled via WifiEvent.sta_connected +// or WifiEvent.sta_disconnected through the event callback. +Result wifi_sta_connect(ref Wifi wifi) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_sta_connect(wifi.port)) + return InternalResult.failed; + return Result.success; + } +} + +Result wifi_sta_disconnect(ref Wifi wifi) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_sta_disconnect(wifi.port)) + return InternalResult.failed; + return Result.success; + } +} + +// Access point operations + +Result wifi_ap_configure(ref Wifi wifi, ref const WifiApConfig cfg) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_ap_configure(wifi.port, cfg)) + return InternalResult.failed; + return Result.success; + } +} + +// Query stations currently connected to our AP. +// Returns the number of entries written (up to buf.length). +size_t wifi_ap_get_clients(ref Wifi wifi, WifiStaInfo[] buf) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + return wifi_ap_get_clients(wifi.port, buf); +} + +// Scanning + +Result wifi_scan_start(ref Wifi wifi, ref const WifiScanConfig cfg) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_scan_start(wifi.port, cfg)) + return InternalResult.failed; + return Result.success; + } +} + +void wifi_scan_stop(ref Wifi wifi) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + wifi_scan_stop(wifi.port); +} + +// Retrieve scan results after WifiEvent.scan_done. +// Returns the number of entries written (up to buf.length). +size_t wifi_scan_get_results(ref Wifi wifi, WifiScanResult[] buf) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + return wifi_scan_get_results(wifi.port, buf); +} + +// Frame TX/RX + +// Transmit an Ethernet frame (including 14-byte header) on a +// virtual interface. Returns 0 on success, negative on error. +int wifi_tx(ref Wifi wifi, WifiVif vif, const(ubyte)[] data) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + return wifi_tx(wifi.port, vif, data); +} + +void wifi_set_rx_callback(ref Wifi wifi, WifiRxCallback cb) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + wifi_set_rx_callback(wifi.port, cb); +} + +// Queries + +Result wifi_get_mac(ref Wifi wifi, WifiVif vif, ref ubyte[6] mac) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_get_mac(wifi.port, vif, mac)) + return InternalResult.failed; + return Result.success; + } +} + +ubyte wifi_get_channel(ref Wifi wifi) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + return wifi_get_channel(wifi.port); +} + +// STA-only: signal strength of current connection. +byte wifi_get_rssi(ref Wifi wifi) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + return wifi_get_rssi(wifi.port); +} + +Result wifi_set_tx_power(ref Wifi wifi, byte power_dbm) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_set_tx_power(wifi.port, power_dbm)) + return InternalResult.failed; + return Result.success; + } +} + +// Events + +void wifi_set_event_callback(ref Wifi wifi, WifiEventCallback cb) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + wifi_set_event_callback(wifi.port, cb); +} + +// Poll (for platforms without native event delivery) + +void wifi_poll(ref Wifi wifi) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + wifi_poll(wifi.port); +} + + +// ════════════════════════════════════════════════════════════════════ +// Tests +// ════════════════════════════════════════════════════════════════════ + +unittest +{ + static if (num_wifi > 0) + { + Wifi w; + WifiConfig cfg; + + wifi_init(); + + // Out-of-range port + auto r = wifi_open(w, cast(ubyte)num_wifi, cfg); + assert(!r); + assert(!w.is_open); + + // Open/close each valid port + foreach (p; 0 .. num_wifi) + { + Wifi port; + auto r2 = wifi_open(port, cast(ubyte)p, cfg); + assert(r2, "wifi_open failed"); + assert(port.is_open); + assert(port.port == p); + + wifi_poll(port); + + wifi_close(port); + assert(!port.is_open); + } + + wifi_deinit(); + } +} + + +private: + +__gshared ubyte _init_refcount; diff --git a/src/sys/bk7231/irq.d b/src/sys/bk7231/irq.d new file mode 100644 index 0000000..e63d899 --- /dev/null +++ b/src/sys/bk7231/irq.d @@ -0,0 +1,85 @@ +// BK7231 interrupt controller (shared by BK7231N and BK7231T) +// +// ARM968E-S uses a custom Beken interrupt controller (not ARM GIC/NVIC). +// The BK7231N ICU (Interrupt Control Unit) is at 0x0080_2000. +module sys.bk7231.irq; + +import core.volatile; + +@nogc nothrow: + +enum bool has_plic = false; +enum bool has_nvic = false; +enum bool has_per_irq_control = true; +enum bool has_irq_priority = false; +enum bool has_wait_for_interrupt = false; +enum bool has_irq_diagnostics = false; +enum uint irq_max = 32; + +// Disable all IRQs (set CPSR I+F bits) +void irq_disable() +{ + uint cpsr; + asm @nogc nothrow { "mrs %0, cpsr" : "=r" (cpsr); } + cpsr |= 0xC0; + asm @nogc nothrow { "msr cpsr_c, %0" :: "r" (cpsr); } +} + +// Enable IRQs (clear CPSR I bit) +void irq_enable() +{ + uint cpsr; + asm @nogc nothrow { "mrs %0, cpsr" : "=r" (cpsr); } + cpsr &= ~0x80; + asm @nogc nothrow { "msr cpsr_c, %0" :: "r" (cpsr); } +} + +void irq_set_enable(uint irq_num) +{ + uint mask = volatileLoad(cast(uint*)(cast(size_t)(icu_base + icu_int_enable))); + mask |= (1u << irq_num); + volatileStore(cast(uint*)(cast(size_t)(icu_base + icu_int_enable)), mask); +} + +void irq_clear_enable(uint irq_num) +{ + uint mask = volatileLoad(cast(uint*)(cast(size_t)(icu_base + icu_int_enable))); + mask &= ~(1u << irq_num); + volatileStore(cast(uint*)(cast(size_t)(icu_base + icu_int_enable)), mask); +} + + +private: + +enum uint icu_base = 0x0080_2000; + +// ICU register offsets (from SDK icu.h) +enum +{ + icu_peri_clk_pwd = 2 * 4, // 0x08: peripheral clock power-down (1=off) + icu_clk_gating = 3 * 4, // 0x0C: peripheral clock gating + icu_int_enable = 16 * 4, // 0x40: interrupt enable mask (FIQ [31:16] | IRQ [15:0]) + icu_global_int = 17 * 4, // 0x44: global IRQ/FIQ enable + icu_int_raw = 18 * 4, // 0x48: raw interrupt status + icu_int_status = 19 * 4, // 0x4C: masked interrupt status + icu_arm_wakeup = 20 * 4, // 0x50: ARM wakeup enable +} + +// IRQ bit positions in icu_int_enable / icu_int_status +enum : uint +{ + IRQ_UART1 = 1 << 0, + IRQ_UART2 = 1 << 1, + IRQ_I2C1 = 1 << 2, + IRQ_IRDA = 1 << 3, + IRQ_I2C2 = 1 << 5, + IRQ_SPI = 1 << 6, + IRQ_GPIO = 1 << 7, + IRQ_TIMER = 1 << 8, + IRQ_PWM = 1 << 9, + IRQ_ADC = 1 << 11, + IRQ_SDIO = 1 << 12, + IRQ_SEC = 1 << 13, + IRQ_LA = 1 << 14, + IRQ_DMA = 1 << 15, +} diff --git a/src/sys/bk7231/package.d b/src/sys/bk7231/package.d new file mode 100644 index 0000000..d51ce04 --- /dev/null +++ b/src/sys/bk7231/package.d @@ -0,0 +1,32 @@ +// BK7231 platform package (ARM968E-S, ARMv5TE) +// +// Provides sys_init() as the single entry point for all +// hardware initialization. Called from start.S before main(). +module sys.bk7231; + +public import sys.bk7231.uart; +public import sys.bk7231.irq; +public import sys.bk7231.timer; + +import sys.baremetal.uart : UartConfig; + +@nogc nothrow: + +private extern(C) void __register_frame_info(const void*, void*); +private extern(C) extern const ubyte __eh_frame_start; +private ubyte[48] __eh_frame_object; + +extern(C) void sys_init() +{ + __register_frame_info(&__eh_frame_start, &__eh_frame_object); + + // Init UART1 (console) at default baud for early output + uart_hw_init(0, UartConfig.init); + + uart0_hw_puts("BK7231: sys_init\r\n"); + + // Init freerunning timer for monotonic clock + timer_hw_init(); + + uart0_hw_puts("BK7231: ready\r\n"); +} diff --git a/src/sys/bk7231/start.S b/src/sys/bk7231/start.S new file mode 100644 index 0000000..45c2842 --- /dev/null +++ b/src/sys/bk7231/start.S @@ -0,0 +1,108 @@ +/* BK7231N (ARM968E-S, ARMv5TE) startup + * + * Boot flow: + * 1. Beken bootloader validates image, jumps to Reset_Handler + * 2. Reset_Handler runs: + * a. Set up stack pointer + * b. Copy .got from flash to SRAM + * c. Copy .data from flash to SRAM + * d. Copy .tdata from flash to SRAM + * e. Zero .bss + * f. Call sys_init (platform init) + * g. Run .init_array (D module constructors) + * h. Call main + * + * ARM968E-S has no vector table relocation (no VTOR). Exception vectors live + * at 0x00000000 (or 0xFFFF0000 if high-vectors enabled). The Beken bootloader + * owns the vector table; we install handlers via the SDK's IRQ registration API. + */ + + .arm + .cpu arm968e-s + .fpu softvfp + + .section .text, "ax" + .global Reset_Handler + .type Reset_Handler, %function +Reset_Handler: + + /* -- Disable IRQs during init -- */ + mrs r0, cpsr + orr r0, r0, #0xC0 /* set I+F bits */ + msr cpsr_c, r0 + + /* -- Set up stack -- */ + ldr sp, =_stack_top + + /* -- Copy .got from flash (LMA) to SRAM (VMA) -- */ + ldr r0, =_got_start + ldr r1, =_got_load + ldr r2, =_got_end + cmp r0, r1 + beq .Lgot_done + b .Lgot_check +.Lgot_copy: + ldr r3, [r1], #4 + str r3, [r0], #4 +.Lgot_check: + cmp r0, r2 + blo .Lgot_copy +.Lgot_done: + + /* -- Copy .data from flash to SRAM -- */ + ldr r0, =_data_start + ldr r1, =_data_load + ldr r2, =_data_end + b .Ldata_check +.Ldata_copy: + ldr r3, [r1], #4 + str r3, [r0], #4 +.Ldata_check: + cmp r0, r2 + blo .Ldata_copy + + /* -- Copy .tdata from flash to SRAM -- */ + ldr r0, =_tdata_start + ldr r1, =_tdata_load + ldr r2, =_tdata_end + b .Ltdata_check +.Ltdata_copy: + ldr r3, [r1], #4 + str r3, [r0], #4 +.Ltdata_check: + cmp r0, r2 + blo .Ltdata_copy + + /* -- Zero .bss -- */ + ldr r0, =_bss_start + ldr r2, =_bss_end + mov r3, #0 + b .Lbss_check +.Lbss_zero: + str r3, [r0], #4 +.Lbss_check: + cmp r0, r2 + blo .Lbss_zero + + /* -- Platform init -- */ + bl sys_init + + /* -- Run .init_array (D module constructors) -- */ + ldr r4, =__init_array_start + ldr r5, =__init_array_end + b .Linit_check +.Linit_loop: + ldr r0, [r4], #4 + mov lr, pc + bx r0 +.Linit_check: + cmp r4, r5 + blo .Linit_loop + + /* -- Enter main (should never return) -- */ + bl main + +.Lhalt: + b .Lhalt + + .size Reset_Handler, . - Reset_Handler diff --git a/src/sys/bk7231/syscalls.d b/src/sys/bk7231/syscalls.d new file mode 100644 index 0000000..e4d8f3d --- /dev/null +++ b/src/sys/bk7231/syscalls.d @@ -0,0 +1,55 @@ +// BK7231 newlib/picolibc syscall stubs +// +// Minimal stubs to satisfy picolibc's syscall requirements. +// Same pattern as RP2350 -- most are no-ops for baremetal. +module sys.bk7231.syscalls; + +@nogc nothrow: + +private extern(C) extern const void* __heap_start; +private extern(C) extern const void* __heap_end; + +private __gshared void* _heap_ptr; + +extern(C) void* _sbrk(ptrdiff_t incr) +{ + if (_heap_ptr is null) + _heap_ptr = cast(void*)&__heap_start; + + void* prev = _heap_ptr; + void* next = _heap_ptr + incr; + + if (next > cast(void*)&__heap_end) + return cast(void*)-1; + + _heap_ptr = next; + return prev; +} + +extern(C) int _write(int fd, const void* buf, size_t count) +{ + import sys.bk7231.uart : uart0_hw_puts; + if (fd == 1 || fd == 2) + uart0_hw_puts((cast(const(char)*)buf)[0 .. count]); + return cast(int)count; +} + +extern(C) int _read(int, void*, size_t) { return 0; } +extern(C) int _close(int) { return -1; } +extern(C) int _lseek(int, int, int) { return 0; } +extern(C) int _fstat(int, void*) { return 0; } +extern(C) int _isatty(int) { return 1; } +extern(C) void _exit(int) { while (true) {} } +extern(C) int _kill(int, int) { return -1; } +extern(C) int _getpid() { return 1; } +extern(C) int usleep(uint) { return 0; } + +// DWARF unwinder stubs -- ARM uses EHABI, not DWARF. +// These satisfy link-time references from the exception personality code. +extern(C) void __register_frame_info(const void*, void*) {} +extern(C) size_t _Unwind_GetIPInfo(void*, int*) { return 0; } +extern(C) void _Unwind_SetGR(void*, int, size_t) {} +extern(C) void _Unwind_SetIP(void*, size_t) {} + +// ARM EHABI resume unwind -- called by LDC's exception propagation +extern(C) void _d_eh_resume_unwind(void*) {} diff --git a/src/sys/bk7231/timer.d b/src/sys/bk7231/timer.d new file mode 100644 index 0000000..b1f9193 --- /dev/null +++ b/src/sys/bk7231/timer.d @@ -0,0 +1,143 @@ +// BK7231 timer driver (shared by BK7231N and BK7231T) +// +// The BK7231 timer/PWM block lives at PWM_NEW_BASE = 0x0080_2A00. +// Timers 0-2 run from 26MHz, timers 3-5 from 32kHz. +// +// We use Timer0 as a freerunning monotonic clock source at 26MHz. +// The counter counts up from 0 to the period register value, then wraps. +// With period = 0xFFFF_FFFF, it wraps every ~165s at 26MHz. +// +// Reading the current count is a two-step process: +// 1. Write (timer_index << 2) | 1 to READ_CTL +// 2. Poll READ_CTL until bit 0 clears +// 3. Read current count from READ_VALUE +// +// Register layout verified against Beken SDK: +// sdk/OpenBK7231N/platforms/bk7231n/bk7231n_os/beken378/driver/pwm/bk_timer.h +// sdk/OpenBK7231N/platforms/bk7231n/bk7231n_os/beken378/driver/pwm/bk_timer.c +module sys.bk7231.timer; + +import core.volatile; + +@nogc nothrow: + + +// ─── Timer/PWM base (from SDK pwm.h) ──────────────────────────────── + +private enum uint PWM_NEW_BASE = 0x0080_2A00; + + +// ─── Timer0-2 registers (26MHz group) ──────────────────────────────── + +private enum : uint +{ + // Period/end-value registers (one per timer, writable) + TIMER0_PERIOD = PWM_NEW_BASE + 0 * 4, // 0x0080_2A00 + TIMER1_PERIOD = PWM_NEW_BASE + 1 * 4, // 0x0080_2A04 + TIMER2_PERIOD = PWM_NEW_BASE + 2 * 4, // 0x0080_2A08 + + // Shared control register for Timer0-2 + TIMER0_2_CTL = PWM_NEW_BASE + 3 * 4, // 0x0080_2A0C + + // Counter read-back registers (BK7231N/U; BK7231T has them in practice) + TIMER0_2_READ_CTL = PWM_NEW_BASE + 4 * 4, // 0x0080_2A10 + TIMER0_2_READ_VALUE = PWM_NEW_BASE + 5 * 4, // 0x0080_2A14 +} + + +// ─── Timer0-2 control register bits ────────────────────────────────── + +private enum : uint +{ + TIMER0_EN = 1 << 0, + TIMER1_EN = 1 << 1, + TIMER2_EN = 1 << 2, + CLK_DIV_POS = 3, // 3-bit divider: actual_div = reg_val + 1 + CLK_DIV_MASK = 0x7 << 3, + TIMER0_INT = 1 << 7, // Timer0 interrupt flag (write 1 to clear) + TIMER1_INT = 1 << 8, + TIMER2_INT = 1 << 9, + INT_FLAG_MASK = 0x7 << 7, +} + + +// ─── ICU clock power for timers ────────────────────────────────────── +// From SDK icu.h: ICU_PERI_CLK_PWD = ICU_BASE + 2*4 = 0x0080_2008 + +private enum uint ICU_PERI_CLK_PWD = 0x0080_2008; +private enum uint PWD_TIMER_26M_CLK = 1 << 20; + + +// ─── Public interface ──────────────────────────────────────────────── + +enum uint mtime_freq_hz = 26_000_000; +enum bool has_mtime = true; +enum bool has_rtc = false; +enum bool has_mcycle = false; +enum bool has_timer_stop = false; +enum bool has_wfi_sleep = false; + +private enum uint timer_freq_hz = mtime_freq_hz; + +private __gshared uint timer_high; +private __gshared uint timer_last; + +void timer_hw_init() +{ + // Enable 26MHz timer clock (clear power-down bit) + uint pwd = volatileLoad(cast(uint*)(cast(size_t)ICU_PERI_CLK_PWD)); + pwd &= ~PWD_TIMER_26M_CLK; + volatileStore(cast(uint*)(cast(size_t)ICU_PERI_CLK_PWD), pwd); + + // Set Timer0 period to maximum (freerunning) + volatileStore(cast(uint*)(cast(size_t)TIMER0_PERIOD), 0xFFFF_FFFF); + + // Configure control: enable Timer0, div=1 (CLK_DIV=0), clear interrupt flags + uint ctl = volatileLoad(cast(uint*)(cast(size_t)TIMER0_2_CTL)); + ctl &= ~(CLK_DIV_MASK | INT_FLAG_MASK); // div=0 means /1, clear int flags + ctl |= TIMER0_EN; + volatileStore(cast(uint*)(cast(size_t)TIMER0_2_CTL), ctl); + + timer_high = 0; + timer_last = 0; +} + +// Read the current Timer0 count via the READ_CTL/READ_VALUE mechanism. +// Hardware clears READ_CTL bit 0 when the snapshot is ready. +private uint timer0_read_count() +{ + enum uint read_ctl_addr = TIMER0_2_READ_CTL; + enum uint read_val_addr = TIMER0_2_READ_VALUE; + + // Trigger read for timer index 0: (index << 2) | 1 + volatileStore(cast(uint*)(cast(size_t)read_ctl_addr), (0 << 2) | 1); + + // Poll until hardware clears bit 0 (should be near-instant at 26MHz) + while (volatileLoad(cast(uint*)(cast(size_t)read_ctl_addr)) & 1) + {} + + return volatileLoad(cast(uint*)(cast(size_t)read_val_addr)); +} + +// Read monotonic 64-bit tick count. +// Must be called at least once per ~165 seconds (2^32 / 26MHz) to catch +// rollovers. The main loop at 20Hz guarantees this. +ulong mtime_read() +{ + uint now = timer0_read_count(); + if (now < timer_last) + ++timer_high; + timer_last = now; + return (cast(ulong)timer_high << 32) | now; +} + +alias TimerCallback = void function() @nogc nothrow; + +private __gshared TimerCallback tick_callback; + +void timer_set_periodic(uint period_us, TimerCallback cb) +{ + tick_callback = cb; + // TODO: configure Timer1 for periodic interrupt at period_us + // Requires IRQ vector dispatch in start.S (not yet implemented) +} diff --git a/src/sys/bk7231/uart.d b/src/sys/bk7231/uart.d new file mode 100644 index 0000000..dd18d50 --- /dev/null +++ b/src/sys/bk7231/uart.d @@ -0,0 +1,385 @@ +// BK7231 UART driver (shared by BK7231N and BK7231T) +// +// BK7231 has 2x UARTs: +// UART1 0x0080_2100 Log/console (TX=GPIO11, RX=GPIO10) +// UART2 0x0080_2200 Data/general purpose (TX=GPIO0, RX=GPIO1) +// +// Both have 128-byte TX/RX FIFOs. +// +// Register layout and init sequence verified against Beken SDK: +// sdk/OpenBK7231N/platforms/bk7231n/bk7231n_os/beken378/driver/uart/uart.h +// sdk/OpenBK7231N/platforms/bk7231n/bk7231n_os/beken378/driver/uart/uart_bk.c +module sys.bk7231.uart; + +import core.volatile; + +import sys.baremetal.uart : FlowControl, Parity, StopBits, UartConfig; + +nothrow @nogc: + +enum num_uarts = 2; +enum uint uart_clock_hz = 26_000_000; +enum bool has_irq_driven_uart = false; +enum bool has_dma_driven_uart = false; + + +// ─── Register offsets (from SDK uart.h) ────────────────────────────── + +private enum uint[2] uart_bases = [0x0080_2100, 0x0080_2200]; + +private enum : uint +{ + REG_CONFIG = 0x00, // Baud rate, data bits, parity, stop bits, TX/RX enable + REG_FIFO_CONFIG = 0x04, // FIFO thresholds, RX stop detect time + REG_FIFO_STATUS = 0x08, // FIFO counts and flags (read-only) + REG_FIFO_PORT = 0x0C, // Data read/write port + REG_INT_ENABLE = 0x10, // Interrupt enable + REG_INT_STATUS = 0x14, // Interrupt status (write 1 to clear) + REG_FLOW_CONFIG = 0x18, // Flow control + REG_WAKE_CONFIG = 0x1C, // Wakeup configuration +} + + +// ─── CONFIG register (0x00) ────────────────────────────────────────── + +private enum : uint +{ + CFG_TX_ENABLE = 1 << 0, + CFG_RX_ENABLE = 1 << 1, + CFG_IRDA = 1 << 2, + CFG_DATA_LEN_POS = 3, // 2 bits: 0=5, 1=6, 2=7, 3=8 + CFG_DATA_LEN_MASK = 0x3 << 3, + CFG_PARITY_EN = 1 << 5, + CFG_PARITY_ODD = 1 << 6, // 0=even, 1=odd + CFG_STOP_LEN_2 = 1 << 7, // 0=1 stop, 1=2 stop + CFG_CLK_DIV_POS = 8, // 13-bit baud divisor + CFG_CLK_DIV_MASK = 0x1FFF << 8, +} + + +// ─── FIFO_CONFIG register (0x04) ───────────────────────────────────── + +private enum : uint +{ + FIFO_TX_THRESHOLD_POS = 0, // 8 bits + FIFO_TX_THRESHOLD_MASK = 0xFF, + FIFO_RX_THRESHOLD_POS = 8, // 8 bits + FIFO_RX_THRESHOLD_MASK = 0xFF << 8, + FIFO_RX_STOP_TIME_POS = 16, // 3 bits: 0=32, 1=64, 2=128, 3=256 clks + FIFO_RX_STOP_TIME_MASK = 0x7 << 16, +} + + +// ─── FIFO_STATUS register (0x08, read-only) ────────────────────────── + +private enum : uint +{ + STAT_TX_FIFO_COUNT_MASK = 0xFF, // bits [7:0] + STAT_RX_FIFO_COUNT_POS = 8, + STAT_RX_FIFO_COUNT_MASK = 0xFF << 8, // bits [15:8] + STAT_TX_FIFO_FULL = 1 << 16, + STAT_TX_FIFO_EMPTY = 1 << 17, + STAT_RX_FIFO_FULL = 1 << 18, + STAT_RX_FIFO_EMPTY = 1 << 19, + STAT_FIFO_WR_READY = 1 << 20, + STAT_FIFO_RD_READY = 1 << 21, +} + + +// ─── INT_ENABLE (0x10) / INT_STATUS (0x14) ─────────────────────────── +// Same bit layout for both registers. + +private enum : uint +{ + INT_TX_FIFO_NEED_WRITE = 1 << 0, + INT_RX_FIFO_NEED_READ = 1 << 1, + INT_RX_FIFO_OVERFLOW = 1 << 2, + INT_RX_PARITY_ERR = 1 << 3, + INT_RX_STOP_ERR = 1 << 4, + INT_TX_STOP_END = 1 << 5, + INT_RX_STOP_END = 1 << 6, + INT_RXD_WAKEUP = 1 << 7, + + INT_ALL_ERRORS = INT_RX_FIFO_OVERFLOW | INT_RX_PARITY_ERR | INT_RX_STOP_ERR, +} + + +// ─── FLOW_CONFIG register (0x18) ───────────────────────────────────── + +private enum : uint +{ + FLOW_LOW_CNT_POS = 0, // 8 bits: assert RTS below this count + FLOW_LOW_CNT_MASK = 0xFF, + FLOW_HIGH_CNT_POS = 8, // 8 bits: de-assert RTS above this count + FLOW_HIGH_CNT_MASK = 0xFF << 8, + FLOW_CTRL_EN = 1 << 16, + FLOW_RTS_POLARITY = 1 << 17, + FLOW_CTS_POLARITY = 1 << 18, +} + + +// ─── Hardware constants ────────────────────────────────────────────── + +private enum uint FIFO_DEPTH = 128; + +// SDK defaults (uart.h): TX_FIFO_THRD=0x40, RX_FIFO_THRD=0x30 +private enum uint SDK_TX_FIFO_THRESHOLD = 0x40; +private enum uint SDK_RX_FIFO_THRESHOLD = 0x30; +private enum uint SDK_RX_STOP_DETECT = 0; // 32 clock cycles + + +// ─── ICU registers for UART clock control ──────────────────────────── +// From SDK icu.h: ICU_PERI_CLK_PWD = ICU_BASE + 2*4 + +private enum uint ICU_BASE = 0x0080_2000; +private enum uint ICU_PERI_CLK_PWD = ICU_BASE + 2 * 4; // 0x0080_2008 + +// Power-down bits (1=powered down, 0=running) +private enum uint PWD_UART1_CLK = 1 << 0; +private enum uint PWD_UART2_CLK = 1 << 1; + + +// ─── GPIO registers for pin mux ────────────────────────────────────── +// From SDK gpio.h / gpio.c: gpio_enable_second_function() + +private enum uint GPIO_BASE = 0x0080_2800; +private enum uint GPIO_FUNC_CFG = GPIO_BASE + 32 * 4; // 0x0080_2880 + +// Per-pin config register values (from SDK gpio.c gpio_config()) +private enum uint GMODE_SECOND_FUNC_PULL_UP = 0x78; // FUNC_EN | OUTPUT_EN | PULL_EN | PULL_UP + +// Default pin assignments +private enum ubyte[2] default_tx_pins = [11, 0]; // UART1=GPIO11, UART2=GPIO0 +private enum ubyte[2] default_rx_pins = [10, 1]; // UART1=GPIO10, UART2=GPIO1 + + +// ─── Register access helpers ───────────────────────────────────────── + +private uint reg_read(uint addr) +{ + return volatileLoad(cast(uint*)(cast(size_t)addr)); +} + +private void reg_write(uint addr, uint val) +{ + volatileStore(cast(uint*)(cast(size_t)addr), val); +} + + +// ─── GPIO pin mux ──────────────────────────────────────────────────── +// Mirrors SDK gpio_enable_second_function() for UART modes. +// Both GFUNC_MODE_UART1 and GFUNC_MODE_UART2 use PERIAL_MODE_1 (value 0) +// with config_mode = GMODE_SECOND_FUNC_PULL_UP. + +private void gpio_setup_uart_pins(uint id) +{ + ubyte tx_pin = default_tx_pins[id]; + ubyte rx_pin = default_rx_pins[id]; + + // Set per-pin config to second-function with pull-up + reg_write(GPIO_BASE + tx_pin * 4, GMODE_SECOND_FUNC_PULL_UP); + reg_write(GPIO_BASE + rx_pin * 4, GMODE_SECOND_FUNC_PULL_UP); + + // Set function mux to PERIAL_MODE_1 (value 0) for each pin. + // GPIO_FUNC_CFG has 2 bits per pin for GPIO 0-15. + uint func_cfg = reg_read(GPIO_FUNC_CFG); + func_cfg &= ~(0x3u << (tx_pin * 2)); + func_cfg &= ~(0x3u << (rx_pin * 2)); + reg_write(GPIO_FUNC_CFG, func_cfg); +} + + +// ─── ICU clock control ─────────────────────────────────────────────── + +private void icu_uart_clock_enable(uint id) +{ + // Clear power-down bit to enable clock + uint pwd = reg_read(ICU_PERI_CLK_PWD); + pwd &= ~((id == 0) ? PWD_UART1_CLK : PWD_UART2_CLK); + reg_write(ICU_PERI_CLK_PWD, pwd); +} + +private void icu_uart_clock_disable(uint id) +{ + uint pwd = reg_read(ICU_PERI_CLK_PWD); + pwd |= (id == 0) ? PWD_UART1_CLK : PWD_UART2_CLK; + reg_write(ICU_PERI_CLK_PWD, pwd); +} + + +// ─── Public API ────────────────────────────────────────────────────── + +bool uart_hw_init(uint id, UartConfig cfg) +{ + if (id >= num_uarts) + return false; + + immutable uint base = uart_bases[id]; + + // Step 1: Enable peripheral clock (SDK: CMD_CLK_PWR_UP) + icu_uart_clock_enable(id); + + // Step 2: Configure GPIO pins for UART function (SDK: CMD_GPIO_ENABLE_SECOND) + gpio_setup_uart_pins(id); + + // Step 3: Disable TX/RX during configuration + reg_write(base + REG_CONFIG, 0); + + // Step 4: Configure FIFO thresholds and RX stop detect time + // SDK defaults: TX_FIFO_THRD=0x40, RX_FIFO_THRD=0x30, RX_STOP_DETECT=0 (32 clks) + reg_write(base + REG_FIFO_CONFIG, + (SDK_TX_FIFO_THRESHOLD << FIFO_TX_THRESHOLD_POS) + | (SDK_RX_FIFO_THRESHOLD << FIFO_RX_THRESHOLD_POS) + | (SDK_RX_STOP_DETECT << FIFO_RX_STOP_TIME_POS)); + + // Step 5: Disable flow control (enable only if requested) + uint flow = 0; + if (cfg.flow_control == FlowControl.hardware) + { + // Assert RTS when RX FIFO < 32, de-assert when > 96 + flow = FLOW_CTRL_EN + | (0x20 << FLOW_LOW_CNT_POS) + | (0x60 << FLOW_HIGH_CNT_POS); + } + reg_write(base + REG_FLOW_CONFIG, flow); + + // Step 6: Disable wakeup + reg_write(base + REG_WAKE_CONFIG, 0); + + // Step 7: Disable all interrupts (polled mode) + reg_write(base + REG_INT_ENABLE, 0); + + // Step 8: Clear any pending interrupt status + uint pending = reg_read(base + REG_INT_STATUS); + reg_write(base + REG_INT_STATUS, pending); + + // Step 9: Build CONFIG register and enable + // Baud divisor: clock / baud - 1 (SDK: uart_hw_init) + uint divisor = uart_clock_hz / cfg.baud_rate - 1; + + uint config = CFG_TX_ENABLE | CFG_RX_ENABLE; + + // Data length: 5=0, 6=1, 7=2, 8=3 + config |= ((cfg.data_bits - 5) & 0x3) << CFG_DATA_LEN_POS; + + if (cfg.parity != Parity.none) + { + config |= CFG_PARITY_EN; + if (cfg.parity == Parity.odd) + config |= CFG_PARITY_ODD; + } + + if (cfg.stop_bits == StopBits.two) + config |= CFG_STOP_LEN_2; + + config |= (divisor & 0x1FFF) << CFG_CLK_DIV_POS; + + reg_write(base + REG_CONFIG, config); + + return true; +} + +bool uart_hw_open(uint id, UartConfig cfg) +{ + return uart_hw_init(id, cfg); +} + +void uart_hw_close(uint id) +{ + if (id >= num_uarts) + return; + + immutable uint base = uart_bases[id]; + + // Disable all interrupts + reg_write(base + REG_INT_ENABLE, 0); + + // Disable TX and RX + uint config = reg_read(base + REG_CONFIG); + config &= ~(CFG_TX_ENABLE | CFG_RX_ENABLE); + reg_write(base + REG_CONFIG, config); +} + +ptrdiff_t uart_hw_read(uint id, void[] buffer) +{ + immutable uint base = uart_bases[id]; + auto buf = cast(ubyte[])buffer; + ptrdiff_t n = 0; + + while (n < buf.length) + { + if (reg_read(base + REG_FIFO_STATUS) & STAT_RX_FIFO_EMPTY) + break; + buf[n] = cast(ubyte)(reg_read(base + REG_FIFO_PORT) & 0xFF); + ++n; + } + return n; +} + +ptrdiff_t uart_hw_write(uint id, const(void)[] data) +{ + immutable uint base = uart_bases[id]; + auto buf = cast(const(ubyte)[])data; + ptrdiff_t n = 0; + + while (n < buf.length) + { + if (reg_read(base + REG_FIFO_STATUS) & STAT_TX_FIFO_FULL) + break; + reg_write(base + REG_FIFO_PORT, buf[n]); + ++n; + } + return n; +} + +void uart_hw_poll(uint id) +{ + // Polled FIFO mode: clear any accumulated error/status interrupts + // so they don't pile up even though we're not using interrupt mode. + immutable uint base = uart_bases[id]; + uint status = reg_read(base + REG_INT_STATUS); + if (status) + reg_write(base + REG_INT_STATUS, status); +} + +bool uart_hw_check_errors(uint id) +{ + immutable uint base = uart_bases[id]; + uint status = reg_read(base + REG_INT_STATUS); + uint errors = status & INT_ALL_ERRORS; + + if (errors) + { + // Clear the error flags by writing 1 + reg_write(base + REG_INT_STATUS, errors); + return true; + } + return false; +} + +ptrdiff_t uart_hw_rx_pending(uint id) +{ + uint status = reg_read(uart_bases[id] + REG_FIFO_STATUS); + return (status & STAT_RX_FIFO_COUNT_MASK) >> STAT_RX_FIFO_COUNT_POS; +} + +ptrdiff_t uart_hw_flush(uint id) +{ + immutable uint base = uart_bases[id]; + while (!(reg_read(base + REG_FIFO_STATUS) & STAT_TX_FIFO_EMPTY)) + {} + return 0; +} + +// Blocking puts for early boot (before the serial stream is up). +// Uses UART1 (the log/console UART) at 0x0080_2100. +void uart0_hw_puts(const(char)[] s) +{ + enum uint base = 0x0080_2100; + foreach (c; s) + { + while (reg_read(base + REG_FIFO_STATUS) & STAT_TX_FIFO_FULL) + {} + reg_write(base + REG_FIFO_PORT, cast(uint)c); + } +} diff --git a/src/sys/bl618/irq.d b/src/sys/bl618/irq.d index 9d6738b..7c04137 100644 --- a/src/sys/bl618/irq.d +++ b/src/sys/bl618/irq.d @@ -1,13 +1,17 @@ -/// BL618 interrupt controller driver -/// -/// BL616/BL618 uses a CLIC-style interrupt controller. -/// -/// TODO: Implement from BL616 register map. +// BL618 interrupt controller driver (CLIC-style) module sys.bl618.irq; @nogc nothrow: -/// Globally disable interrupts +enum bool has_plic = false; +enum bool has_nvic = false; +enum bool has_per_irq_control = false; +enum bool has_irq_priority = false; +enum bool has_wait_for_interrupt = false; +enum bool has_irq_diagnostics = false; +enum uint irq_max = 0; + +// Globally disable interrupts void irq_disable() { asm @nogc nothrow diff --git a/src/sys/bl618/package.d b/src/sys/bl618/package.d index e6bea6b..f56a9ff 100644 --- a/src/sys/bl618/package.d +++ b/src/sys/bl618/package.d @@ -27,12 +27,12 @@ extern(C) void sys_init() // handling works (required for fibre abort, etc.). __register_frame_info(&__eh_frame_start, &__eh_frame_object); - uart0_puts("BL618: sys_init\n"); + uart0_hw_puts("BL618: sys_init\n"); // Timer: set up 20Hz tick (50ms) for the main loop timer_set_periodic(50_000, &tick_stub); - uart0_puts("BL618: ready\n"); + uart0_hw_puts("BL618: ready\n"); } private void tick_stub() @nogc nothrow diff --git a/src/sys/bl618/syscalls.d b/src/sys/bl618/syscalls.d index 1e15a91..3597412 100644 --- a/src/sys/bl618/syscalls.d +++ b/src/sys/bl618/syscalls.d @@ -6,21 +6,23 @@ module sys.bl618.syscalls; @nogc nothrow: -private extern(C) extern const void* __heap_start; -private extern(C) extern const void* __heap_end; +private extern(C) extern __gshared { + pragma(mangle, "__heap_start") void* _heap_start_ptr; + pragma(mangle, "__heap_end") void* _heap_end_ptr; +} private __gshared void* _heap_ptr; extern(C) void* _sbrk(ptrdiff_t incr) { if (_heap_ptr is null) - _heap_ptr = cast(void*) &__heap_start; + _heap_ptr = cast(void*)&_heap_start_ptr; void* prev = _heap_ptr; void* next = _heap_ptr + incr; - if (next > cast(void*) &__heap_end) - return cast(void*) -1; + if (next > cast(void*)&_heap_end_ptr) + return cast(void*)-1; _heap_ptr = next; return prev; @@ -28,9 +30,9 @@ extern(C) void* _sbrk(ptrdiff_t incr) extern(C) int _write(int fd, const void* buf, size_t count) { - import sys.bl618.uart : uart0_puts; + import sys.bl618.uart : uart0_hw_puts; if (fd == 1 || fd == 2) - uart0_puts((cast(const(char)*) buf)[0 .. count]); + uart0_hw_puts((cast(const(char)*) buf)[0 .. count]); return cast(int) count; } diff --git a/src/sys/bl618/timer.d b/src/sys/bl618/timer.d index 7b3a7b5..6bd3883 100644 --- a/src/sys/bl618/timer.d +++ b/src/sys/bl618/timer.d @@ -1,30 +1,22 @@ -/// BL618 timer driver -/// -/// T-Head E907 (RV32IMAFC) has standard RISC-V mtime/mtimecmp. -/// mtime runs at 1MHz (same as BL808's C906). -/// -/// TODO: Verify mtime frequency on actual BL618 hardware. +// BL618 timer driver +// +// T-Head E907 (RV32IMAFC) has standard RISC-V mtime/mtimecmp. +// mtime runs at 1MHz (same as BL808's C906). module sys.bl618.timer; @nogc nothrow: -// ================================================================ -// mtime frequency — 1MHz assumed (same as BL808) -// TODO: verify against BL616/BL618 clock tree -// ================================================================ - enum uint mtime_freq_hz = 1_000_000; - -// ================================================================ -// Time reading -// ================================================================ - -/// Read the monotonic mtime counter via rdtime. -/// RV32: reads high/low halves with retry on rollover. +enum bool has_mtime = true; +enum bool has_rtc = false; +enum bool has_mcycle = false; +enum bool has_timer_stop = false; +enum bool has_wfi_sleep = false; + +// Read the monotonic mtime counter via rdtime. +// RV32: reads high/low halves with retry on rollover. ulong mtime_read() { - // RV32 requires reading timeh:time atomically. - // If timeh changes between reads, retry. uint hi1, lo, hi2; do { @@ -33,29 +25,17 @@ ulong mtime_read() asm @nogc nothrow { "rdtimeh %0" : "=r" (hi2); } } while (hi1 != hi2); - return (cast(ulong) hi1 << 32) | lo; + return (ulong(hi1) << 32) | lo; } -// ================================================================ -// Periodic tick -// ================================================================ - alias TimerCallback = void function() @nogc nothrow; private __gshared TimerCallback tick_callback; private __gshared uint tick_interval; -/// Set up a periodic timer interrupt. -/// Params: -/// period_us = period in microseconds (mtime ticks at 1MHz) -/// cb = callback to invoke on each tick void timer_set_periodic(uint period_us, TimerCallback cb) { tick_interval = period_us; tick_callback = cb; - - // TODO: write mtimecmp and enable timer interrupt - // ulong now = mtime_read(); - // mtimecmp_write(now + tick_interval); - // enable_irq(IrqClass.timer); + assert(false, "TODO: write mtimecmp and enable timer interrupt"); } diff --git a/src/sys/bl618/uart.d b/src/sys/bl618/uart.d index a2b3595..60c2be4 100644 --- a/src/sys/bl618/uart.d +++ b/src/sys/bl618/uart.d @@ -12,70 +12,56 @@ /// Stubbed to provide the API surface needed by serial.d. module sys.bl618.uart; -nothrow @nogc: - -enum UartId : uint { uart0 = 0, uart1 = 1 } +import sys.baremetal.uart : Parity, StopBits, UartConfig; -enum UartParity : ubyte { none, even, odd } -enum UartStopBits : ubyte { one, one_point_five, two } +nothrow @nogc: -struct UartConfig -{ - uint baud_rate = 115200; - ubyte data_bits = 8; - UartParity parity = UartParity.none; - UartStopBits stop_bits = UartStopBits.one; -} +enum num_uarts = 2; +enum uint uart_clock_hz = 40_000_000; +enum bool has_irq_driven_uart = false; +enum bool has_dma_driven_uart = false; -/// Open and configure a UART. -bool uart_open(UartId id, UartConfig cfg) +bool uart_hw_open(uint id, UartConfig cfg) { // TODO: configure UART registers return true; } -/// Close a UART. -void uart_close(UartId id) +void uart_hw_close(uint id) { // TODO: disable UART } -/// Read available bytes from UART RX ring buffer. -ptrdiff_t uart_read(UartId id, void[] buffer) +ptrdiff_t uart_hw_read(uint id, void[] buffer) { // TODO: read from RX ring buffer return 0; } -/// Write bytes to UART TX. -ptrdiff_t uart_write(UartId id, const(void)[] data) +ptrdiff_t uart_hw_write(uint id, const(void)[] data) { // TODO: write to TX FIFO/ring buffer return 0; } -/// Poll hardware FIFOs — call from update() to drain/fill ring buffers. -void uart_poll(UartId id) +void uart_hw_poll(uint id) { // TODO: poll RX/TX FIFOs } -/// Check for UART errors (framing, parity, overflow). -bool uart_check_errors(UartId id) +bool uart_hw_check_errors(uint id) { // TODO: check error status register return false; } -/// Return number of bytes available in RX buffer. -ptrdiff_t uart_rx_pending(UartId id) +ptrdiff_t uart_hw_rx_pending(uint id) { // TODO: return ring buffer count return 0; } -/// Flush TX buffer (blocking). -ptrdiff_t uart_flush(UartId id) +ptrdiff_t uart_hw_flush(uint id) { // TODO: drain TX ring buffer return 0; @@ -83,7 +69,7 @@ ptrdiff_t uart_flush(UartId id) /// Transmit a string on UART0 (blocking, polled). /// Used for early boot messages before the full console is up. -void uart0_puts(const(char)[] s) +void uart0_hw_puts(const(char)[] s) { // TODO: direct register write to UART0 TX FIFO // UART0 base: 0x2000_A000, FIFO_WDATA offset: 0x88 diff --git a/src/sys/bl808/irq.d b/src/sys/bl808/irq.d index 3e2a848..8324463 100644 --- a/src/sys/bl808/irq.d +++ b/src/sys/bl808/irq.d @@ -4,6 +4,13 @@ import core.volatile; nothrow @nogc: +enum bool has_plic = true; +enum bool has_nvic = false; +enum bool has_per_irq_control = true; +enum bool has_irq_priority = false; +enum bool has_wait_for_interrupt = true; +enum bool has_irq_diagnostics = true; + // ================================================================ // CPU interrupt control diff --git a/src/sys/bl808/package.d b/src/sys/bl808/package.d index 01b0dbf..9fd26b2 100644 --- a/src/sys/bl808/package.d +++ b/src/sys/bl808/package.d @@ -32,7 +32,7 @@ extern(C) void sys_init() // UART0 is already initialized by M0 before D0 boots. // Just confirm we're alive. - uart0_puts("BL808 D0: sys_init\n"); + uart0_hw_puts("BL808 D0: sys_init\n"); // Timer: set up 20Hz tick (50ms) for the main loop // TODO: wire this to Application.run() instead of a stub @@ -41,7 +41,7 @@ extern(C) void sys_init() // IPC: initialize XRAM ring buffers ipc_init(); - uart0_puts("BL808 D0: ready\n"); + uart0_hw_puts("BL808 D0: ready\n"); } private void tick_stub() @nogc nothrow diff --git a/src/sys/bl808/syscalls.d b/src/sys/bl808/syscalls.d index c69ab3b..6d5e253 100644 --- a/src/sys/bl808/syscalls.d +++ b/src/sys/bl808/syscalls.d @@ -25,7 +25,7 @@ extern(C) int _read(int fd, void* buf, size_t n) @nogc nothrow { return 0; } extern(C) int _write(int fd, const(void)* buf, size_t n) @nogc nothrow { if (fd == 1 || fd == 2) - uart0_puts((cast(const(char)*) buf)[0 .. n]); + uart0_hw_puts((cast(const(char)*) buf)[0 .. n]); return cast(int) n; } diff --git a/src/sys/bl808/timer.d b/src/sys/bl808/timer.d index d7a7e91..7b49a42 100644 --- a/src/sys/bl808/timer.d +++ b/src/sys/bl808/timer.d @@ -51,6 +51,11 @@ private enum ulong HBN_RTC_TIME_H = HBN_BASE + 0x10; // ================================================================ enum uint mtime_freq_hz = 1_000_000; +enum bool has_mtime = true; +enum bool has_rtc = true; +enum bool has_mcycle = true; +enum bool has_timer_stop = true; +enum bool has_wfi_sleep = true; // ================================================================ // Time reading diff --git a/src/sys/bl808/uart.d b/src/sys/bl808/uart.d index 9d39f84..0ca6863 100644 --- a/src/sys/bl808/uart.d +++ b/src/sys/bl808/uart.d @@ -24,6 +24,8 @@ module sys.bl808.uart; import core.volatile; import sys.bl808.irq; +import sys.baremetal.uart : Parity, StopBits, UartConfig; + nothrow @nogc: @@ -31,9 +33,12 @@ nothrow @nogc: // Register definitions // ══════════════════════════════════════════════════════════════════════════════ -enum NUM_UARTS = 4; +enum num_uarts = 4; +enum uint uart_clock_hz = 40_000_000; +enum bool has_irq_driven_uart = true; +enum bool has_dma_driven_uart = false; -private immutable uint[NUM_UARTS] uart_base = [ +private immutable uint[num_uarts] uart_base = [ 0x2000_A000, // UART0 0x2000_A100, // UART1 0x2000_AA00, // UART2 (shared with ISO11898 CAN) @@ -130,7 +135,7 @@ private enum : uint } // FIFO depth -enum UART_FIFO_MAX = 32; +private enum UART_FIFO_MAX = 32; // RX FIFO threshold — interrupt fires when RX FIFO count >= this value. // Set to 16 so we drain before the 32-byte FIFO overflows. @@ -161,30 +166,19 @@ private alias Ring = RingBuffer!512; // Driver API // ══════════════════════════════════════════════════════════════════════════════ -enum UartParity : ubyte { none, odd, even } -enum UartStopBits : ubyte { half, one, one_point_five, two } - -struct UartConfig -{ - uint baud_rate = 9600; - ubyte data_bits = 8; // 5..8 - UartStopBits stop_bits = UartStopBits.one; - UartParity parity = UartParity.none; -} - // Per-UART state -private __gshared Ring[NUM_UARTS] rx_ring; -private __gshared Ring[NUM_UARTS] tx_ring; -private __gshared bool[NUM_UARTS] uart_open_flag; +private __gshared Ring[num_uarts] rx_ring; +private __gshared Ring[num_uarts] tx_ring; +private __gshared bool[num_uarts] uart_open_flag; private __gshared IrqHandler prev_irq_handler; private __gshared bool irq_handler_installed; // Open a UART: configure baud rate, frame format, clear FIFOs, enable TX+RX. // UART3 gets interrupt-driven I/O. UART0/1/2 require uart_poll(). // Returns false if id is out of range. -bool uart_open(uint id, UartConfig cfg) +bool uart_hw_open(uint id, UartConfig cfg) { - if (id >= NUM_UARTS) + if (id >= num_uarts) return false; immutable base = uart_base[id]; @@ -206,20 +200,20 @@ bool uart_open(uint id, UartConfig cfg) tx_cfg |= cast(uint)(cfg.data_bits - 4) << CR_UTX_BIT_CNT_D_SHIFT; tx_cfg |= cast(uint)cfg.stop_bits << CR_UTX_BIT_CNT_P_SHIFT; tx_cfg |= CR_UTX_FRM_EN; - if (cfg.parity != UartParity.none) + if (cfg.parity != Parity.none) { tx_cfg |= CR_UTX_PRT_EN; - if (cfg.parity == UartParity.odd) + if (cfg.parity == Parity.odd) tx_cfg |= CR_UTX_PRT_SEL; } // RX config: data bits, parity (stop bits are TX-only in hardware) rx_cfg &= ~(CR_URX_BIT_CNT_D_MASK | CR_URX_PRT_EN | CR_URX_PRT_SEL); rx_cfg |= cast(uint)(cfg.data_bits - 4) << CR_URX_BIT_CNT_D_SHIFT; - if (cfg.parity != UartParity.none) + if (cfg.parity != Parity.none) { rx_cfg |= CR_URX_PRT_EN; - if (cfg.parity == UartParity.odd) + if (cfg.parity == Parity.odd) rx_cfg |= CR_URX_PRT_SEL; } @@ -279,10 +273,9 @@ bool uart_open(uint id, UartConfig cfg) } // Disable TX and RX, mask interrupts. -void uart_close(uint id) +void uart_hw_close(uint id) { - if (id >= NUM_UARTS) - return; + assert(id < num_uarts); immutable base = uart_base[id]; @@ -304,18 +297,18 @@ void uart_close(uint id) // Poll hardware FIFOs and transfer to/from ring buffers. // Required for UART0/1/2 (no D0 interrupt). Harmless for UART3. -void uart_poll(uint id) +void uart_hw_poll(uint id) { - if (id >= NUM_UARTS || !uart_open_flag[id]) + if (id >= num_uarts || !uart_open_flag[id]) return; drain_rx_fifo(id); fill_tx_fifo(id); } // Non-blocking read: pull from RX ring buffer, return bytes read. -ptrdiff_t uart_read(uint id, void[] buffer) +ptrdiff_t uart_hw_read(uint id, void[] buffer) { - if (id >= NUM_UARTS) + if (id >= num_uarts) return -1; immutable prev = disable_interrupts(); @@ -326,9 +319,9 @@ ptrdiff_t uart_read(uint id, void[] buffer) // Non-blocking write: push into TX ring buffer, kick TX if needed. // Returns bytes accepted (may be less than data.length if ring is full). -ptrdiff_t uart_write(uint id, const(void)[] data) +ptrdiff_t uart_hw_write(uint id, const(void)[] data) { - if (id >= NUM_UARTS) + if (id >= num_uarts) return -1; immutable prev = disable_interrupts(); @@ -351,9 +344,9 @@ ptrdiff_t uart_write(uint id, const(void)[] data) } // Return number of bytes available to read from RX ring. -ptrdiff_t uart_rx_pending(uint id) +ptrdiff_t uart_hw_rx_pending(uint id) { - if (id >= NUM_UARTS) + if (id >= num_uarts) return -1; immutable prev = disable_interrupts(); @@ -363,9 +356,9 @@ ptrdiff_t uart_rx_pending(uint id) } // Clear RX ring buffer and hardware FIFO. Returns bytes discarded. -ptrdiff_t uart_flush(uint id) +ptrdiff_t uart_hw_flush(uint id) { - if (id >= NUM_UARTS) + if (id >= num_uarts) return -1; immutable prev = disable_interrupts(); @@ -384,9 +377,9 @@ ptrdiff_t uart_flush(uint id) // Check and clear FIFO error flags (overflow/underflow). // Returns true if any error was detected. -bool uart_check_errors(uint id) +bool uart_hw_check_errors(uint id) { - if (id >= NUM_UARTS) + if (id >= num_uarts) return true; immutable base = uart_base[id]; @@ -498,7 +491,7 @@ void uart0_putc(char c) volatileStore(u0_wr, cast(uint)c); } -void uart0_puts(const(char)[] s) +void uart0_hw_puts(const(char)[] s) { foreach (c; s) { diff --git a/src/sys/esp32/can.d b/src/sys/esp32/can.d new file mode 100644 index 0000000..c3aa3cb --- /dev/null +++ b/src/sys/esp32/can.d @@ -0,0 +1,222 @@ +// ESP32 CAN (TWAI) driver -- thin D layer over ESP-IDF _v2 handle API +// +// Only ow_can_open is a C shim (bitrate table + config construction). +// All other calls go directly to the ESP-IDF twai_*_v2 functions. +// +// Controller count per chip (ESP-IDF v6.0): +// ESP32, S3, C3, H2: 1 C5, C6: 2 P4: 3 S2, C2, C61: 0 +module sys.esp32.can; + +import sys.baremetal.can : CanConfig, CanFrame, CanError, CanBusState; + +nothrow @nogc: + + +// SOC_TWAI_CONTROLLER_NUM per chip variant (ESP-IDF v6.0 soc_caps.h) +version (ESP32) enum uint num_can = 1; +else version (ESP32_S3) enum uint num_can = 1; +else version (ESP32_P4) enum uint num_can = 3; +else version (ESP32_C3) enum uint num_can = 1; +else version (ESP32_C5) enum uint num_can = 2; +else version (ESP32_C6) enum uint num_can = 2; +else version (ESP32_H2) enum uint num_can = 1; +else enum uint num_can = 0; // S2, C2, C61 + +// SOC_TWAI_FD_SUPPORTED (ESP-IDF v6.0 soc_caps.h) +version (ESP32_C5) enum bool has_can_fd = true; +else enum bool has_can_fd = false; + + +static if (num_can > 0): + + +bool can_open(uint port, ref const CanConfig cfg) +{ + if (port >= num_can) + return false; + if (_handles[port] !is null) + return true; + int tx = cfg.tx_gpio == ubyte.max ? -1 : cast(int)cfg.tx_gpio; + int rx = cfg.rx_gpio == ubyte.max ? -1 : cast(int)cfg.rx_gpio; + _handles[port] = ow_can_open(port, cfg.bitrate, tx, rx, + cfg.sjw, cfg.tseg1, cfg.tseg2, cfg.brp); + return _handles[port] !is null; +} + +void can_close(uint port) +{ + if (port >= num_can || _handles[port] is null) + return; + twai_stop_v2(_handles[port]); + twai_driver_uninstall_v2(_handles[port]); + _handles[port] = null; +} + +int can_transmit(uint port, ref const CanFrame frame) +{ + if (port >= num_can || _handles[port] is null) + return -1; + twai_message_t msg; + msg.flags = cast(uint)frame.extended | (cast(uint)frame.rtr << 1); + msg.identifier = frame.id; + msg.data_length_code = frame.dlc; + if (frame.dlc > 0) + msg.data[0 .. frame.dlc] = frame.data[0 .. frame.dlc]; + return twai_transmit_v2(_handles[port], &msg, 0) == ESP_OK ? 0 : -1; +} + +bool can_receive(uint port, out CanFrame frame) +{ + if (port >= num_can || _handles[port] is null) + return false; + twai_message_t msg; + if (twai_receive_v2(_handles[port], &msg, 0) != ESP_OK) + return false; + frame.id = msg.identifier; + frame.extended = (msg.flags & 1) != 0; + frame.rtr = (msg.flags & 2) != 0; + frame.dlc = msg.data_length_code; + if (msg.data_length_code > 0) + frame.data[0 .. msg.data_length_code] = msg.data[0 .. msg.data_length_code]; + return true; +} + +CanError can_check_errors(uint port) +{ + if (port >= num_can || _handles[port] is null) + return CanError.none; + uint alerts; + twai_read_alerts_v2(_handles[port], &alerts, 0); + CanError err = CanError.none; + if (alerts & 0x0200) // TWAI_ALERT_BUS_ERROR + err |= CanError.bit; + if (alerts & 0x4800) // TWAI_ALERT_RX_FIFO_OVERRUN | RX_QUEUE_FULL + err |= CanError.overrun; + if (alerts & 0x0400) // TWAI_ALERT_TX_FAILED + err |= CanError.ack; + return err; +} + +CanBusState can_bus_state(uint port) +{ + if (port >= num_can || _handles[port] is null) + return CanBusState.bus_off; + twai_status_info_t info; + if (twai_get_status_info_v2(_handles[port], &info) != ESP_OK) + return CanBusState.bus_off; + if (info.state >= 2) // BUS_OFF or RECOVERING + return CanBusState.bus_off; + if (info.tx_error_counter >= 128 || info.rx_error_counter >= 128) + return CanBusState.error_passive; + if (info.tx_error_counter >= 96 || info.rx_error_counter >= 96) + return CanBusState.error_warning; + return CanBusState.error_active; +} + +ubyte can_tx_error_count(uint port) +{ + if (port >= num_can || _handles[port] is null) + return 0; + twai_status_info_t info; + if (twai_get_status_info_v2(_handles[port], &info) != ESP_OK) + return 0; + return info.tx_error_counter > 255 ? 255 : cast(ubyte)info.tx_error_counter; +} + +ubyte can_rx_error_count(uint port) +{ + if (port >= num_can || _handles[port] is null) + return 0; + twai_status_info_t info; + if (twai_get_status_info_v2(_handles[port], &info) != ESP_OK) + return 0; + return info.rx_error_counter > 255 ? 255 : cast(ubyte)info.rx_error_counter; +} + +size_t can_rx_available(uint port) +{ + if (port >= num_can || _handles[port] is null) + return 0; + twai_status_info_t info; + if (twai_get_status_info_v2(_handles[port], &info) != ESP_OK) + return 0; + return cast(size_t)info.msgs_to_rx; +} + +void can_rx_flush(uint port) +{ + if (port >= num_can || _handles[port] is null) + return; + twai_clear_receive_queue_v2(_handles[port]); +} + +void can_tx_abort(uint port) +{ + if (port >= num_can || _handles[port] is null) + return; + twai_clear_transmit_queue_v2(_handles[port]); +} + +bool can_bus_recover(uint port) +{ + if (port >= num_can || _handles[port] is null) + return false; + return twai_initiate_recovery_v2(_handles[port]) == ESP_OK; +} + +void can_poll(uint port) +{ + // TWAI driver buffers RX internally +} + + +private: + +enum int ESP_OK = 0; + +// Opaque ESP-IDF TWAI handle +struct twai_obj_t {} +alias twai_handle_t = twai_obj_t*; + +// ESP-IDF twai_message_t (flags union flattened to uint) +struct twai_message_t +{ + uint flags; // bit 0: extd, bit 1: rtr, bit 2: ss, bit 3: self + uint identifier; + ubyte data_length_code; + ubyte[8] data; +} + +// ESP-IDF twai_status_info_t +struct twai_status_info_t +{ + int state; // 0=STOPPED, 1=RUNNING, 2=BUS_OFF, 3=RECOVERING + uint msgs_to_tx; + uint msgs_to_rx; + uint tx_error_counter; + uint rx_error_counter; + uint tx_failed_count; + uint rx_missed_count; + uint rx_overrun_count; + uint arb_lost_count; + uint bus_error_count; +} + +__gshared twai_handle_t[num_can] _handles; + +extern(C) nothrow @nogc +{ + // Shim -- bitrate table + config construction + install + start + twai_handle_t ow_can_open(uint port, uint bitrate, int tx_gpio, int rx_gpio, ubyte sjw, ubyte tseg1, ubyte tseg2, ushort brp); + + // Direct ESP-IDF v2 calls + int twai_stop_v2(twai_handle_t handle); + int twai_driver_uninstall_v2(twai_handle_t handle); + int twai_transmit_v2(twai_handle_t handle, const(twai_message_t)* message, uint ticks_to_wait); + int twai_receive_v2(twai_handle_t handle, twai_message_t* message, uint ticks_to_wait); + int twai_read_alerts_v2(twai_handle_t handle, uint* alerts, uint ticks_to_wait); + int twai_get_status_info_v2(twai_handle_t handle, twai_status_info_t* status_info); + int twai_initiate_recovery_v2(twai_handle_t handle); + int twai_clear_receive_queue_v2(twai_handle_t handle); + int twai_clear_transmit_queue_v2(twai_handle_t handle); +} diff --git a/src/sys/esp32/irq.d b/src/sys/esp32/irq.d new file mode 100644 index 0000000..c2bac3b --- /dev/null +++ b/src/sys/esp32/irq.d @@ -0,0 +1,59 @@ +// ESP32 interrupt controller driver +// +// ESP-IDF manages interrupts via esp_intr_alloc(). Direct vector table +// access is discouraged since FreeRTOS owns it. Global disable/enable +// uses FreeRTOS critical section primitives. +module sys.esp32.irq; + +nothrow @nogc: + + +enum bool has_plic = false; +enum bool has_nvic = false; +enum bool has_per_irq_control = false; // TODO: wire up esp_intr_alloc +enum bool has_irq_priority = false; // TODO: wire up esp_intr_alloc priority flags +enum bool has_wait_for_interrupt = true; +enum bool has_irq_diagnostics = false; +enum uint irq_max = 32; + +void irq_disable() +{ + _saved_state = ow_irq_disable(); +} + +void irq_enable() +{ + ow_irq_enable(_saved_state); +} + +void irq_set_enable(uint irq) +{ + assert(false, "TODO: use esp_intr_alloc"); +} + +void irq_clear_enable(uint irq) +{ + assert(false, "TODO: use esp_intr_free"); +} + +void irq_set_priority(uint irq, ubyte priority) +{ + assert(false, "TODO: use esp_intr_set_in_iram / priority flags"); +} + +void wait_for_interrupt() +{ + ow_irq_wait(); +} + + +private: + +__gshared uint _saved_state; + +extern(C) nothrow @nogc +{ + uint ow_irq_disable(); + void ow_irq_enable(uint prev); + void ow_irq_wait(); +} diff --git a/src/sys/esp32/ow_shim.c b/src/sys/esp32/ow_shim.c index 7915150..9220cab 100644 --- a/src/sys/esp32/ow_shim.c +++ b/src/sys/esp32/ow_shim.c @@ -8,8 +8,15 @@ #include "hal/uart_types.h" #include "hal/uart_periph.h" #include "esp_rom_gpio.h" +#include "esp_rom_serial_output.h" #include "lwip/netdb.h" +// ESP32-C3 ROM exports uart_tx_one_char but not esp_rom_uart_putc. +// Provide the missing symbol so the D object links. +#if defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(esp_rom_uart_putc) +void esp_rom_uart_putc(char c) { esp_rom_output_tx_one_char(c); } +#endif + // -- errno accessor (picolibc uses _Thread_local errno, incompatible with emulated-TLS) -- #include @@ -19,6 +26,31 @@ int *ow_errno_location(void) return &errno; } +// -- IRQ wrappers -- + +static portMUX_TYPE ow_irq_mux = portMUX_INITIALIZER_UNLOCKED; + +uint32_t ow_irq_disable(void) +{ + portENTER_CRITICAL(&ow_irq_mux); + return 1; +} + +void ow_irq_enable(uint32_t prev) +{ + (void)prev; + portEXIT_CRITICAL(&ow_irq_mux); +} + +void ow_irq_wait(void) +{ +#if CONFIG_IDF_TARGET_ARCH_XTENSA + __asm__ volatile("waiti 0"); +#elif CONFIG_IDF_TARGET_ARCH_RISCV + __asm__ volatile("wfi"); +#endif +} + // -- FreeRTOS task wrappers -- typedef void (*ow_task_func_t)(void *); @@ -57,33 +89,46 @@ uint32_t ow_task_priority_get(void *handle) // -- UART HAL wrappers -- -#define NUM_UARTS 3 +#include "soc/soc_caps.h" +#include "hal/uart_ll.h" +#include "esp_private/periph_ctrl.h" +#define NUM_UARTS SOC_UART_NUM static uart_hal_context_t uart_ctx[NUM_UARTS]; static bool uart_initialized[NUM_UARTS]; -// D enums: StopBits { one=0, one_point_five=1, two=2 } +// D enums: StopBits { half=0, one=1, one_point_five=2, two=3 } // Parity { none=0, even=1, odd=2, mark=3, space=4 } // HAL enums: UART_STOP_BITS_1=1, _1_5=2, _2=3 // UART_PARITY_DISABLE=0, _EVEN=2, _ODD=3 static const uart_stop_bits_t stop_bits_map[] = { - UART_STOP_BITS_1, UART_STOP_BITS_1_5, UART_STOP_BITS_2 + UART_STOP_BITS_1, UART_STOP_BITS_1, UART_STOP_BITS_1_5, UART_STOP_BITS_2 }; static const uart_parity_t parity_map[] = { UART_PARITY_DISABLE, UART_PARITY_EVEN, UART_PARITY_ODD, UART_PARITY_DISABLE, UART_PARITY_DISABLE }; -int ow_uart_open(int port, uint32_t baud_rate, uint8_t data_bits, +int ow_uart_open(unsigned port, uint32_t baud_rate, uint8_t data_bits, uint8_t stop_bits, uint8_t parity, int8_t tx_gpio, int8_t rx_gpio) { - if (port < 0 || port >= NUM_UARTS) + if (port >= NUM_UARTS) return 0; + // Enable peripheral clock before touching any registers + PERIPH_RCC_ATOMIC() + { + uart_ll_enable_bus_clock(port, true); + uart_ll_reset_register(port); + } + + // Set device pointer before calling hal_init (v6 API expects it pre-set) + uart_ctx[port].dev = UART_LL_GET_HW(port); uart_hal_init(&uart_ctx[port], port); - uart_ll_set_sclk(uart_ctx[port].dev, UART_SCLK_APB); + int __DECLARE_RCC_ATOMIC_ENV __attribute__((unused)); + uart_ll_set_sclk(uart_ctx[port].dev, UART_SCLK_DEFAULT); uart_ll_set_baudrate(uart_ctx[port].dev, baud_rate, 80000000); uart_ll_set_data_bit_num(uart_ctx[port].dev, data_bits - 5); uart_ll_set_stop_bits(uart_ctx[port].dev, stop_bits < sizeof(stop_bits_map) ? stop_bits_map[stop_bits] : 1); @@ -112,58 +157,63 @@ int ow_uart_open(int port, uint32_t baud_rate, uint8_t data_bits, return 1; } -void ow_uart_close(int port) +void ow_uart_close(unsigned port) { - if (port < 0 || port >= NUM_UARTS || !uart_initialized[port]) + if (port >= NUM_UARTS || !uart_initialized[port]) return; uart_hal_txfifo_rst(&uart_ctx[port]); uart_hal_rxfifo_rst(&uart_ctx[port]); + PERIPH_RCC_ATOMIC() + { + uart_ll_enable_bus_clock(port, false); + } uart_initialized[port] = false; } -int32_t ow_uart_read(int port, uint8_t *buf, int32_t len) +int32_t ow_uart_read(unsigned port, uint8_t *buf, int32_t len) { - if (port < 0 || port >= NUM_UARTS || !uart_initialized[port]) + if (port >= NUM_UARTS || !uart_initialized[port]) return 0; int rd_len = (int)len; uart_hal_read_rxfifo(&uart_ctx[port], buf, &rd_len); return rd_len; } -int32_t ow_uart_write(int port, const uint8_t *buf, int32_t len) +int32_t ow_uart_write(unsigned port, const uint8_t *buf, int32_t len) { - if (port < 0 || port >= NUM_UARTS || !uart_initialized[port]) + if (port >= NUM_UARTS || !uart_initialized[port]) return 0; uint32_t written = 0; uart_hal_write_txfifo(&uart_ctx[port], buf, (uint32_t)len, &written); return (int32_t)written; } -int32_t ow_uart_rx_pending(int port) +int32_t ow_uart_rx_pending(unsigned port) { - if (port < 0 || port >= NUM_UARTS || !uart_initialized[port]) + if (port >= NUM_UARTS || !uart_initialized[port]) return 0; return (int32_t)uart_ll_get_rxfifo_len(uart_ctx[port].dev); } -int ow_uart_tx_idle(int port) +int ow_uart_tx_idle(unsigned port) { - if (port < 0 || port >= NUM_UARTS || !uart_initialized[port]) + if (port >= NUM_UARTS || !uart_initialized[port]) return 1; return uart_ll_is_tx_idle(uart_ctx[port].dev) ? 1 : 0; } -int32_t ow_uart_flush(int port) +int32_t ow_uart_flush(unsigned port) { - if (port < 0 || port >= NUM_UARTS || !uart_initialized[port]) + if (port >= NUM_UARTS || !uart_initialized[port]) return 0; while (!uart_ll_is_tx_idle(uart_ctx[port].dev)) - ; + {} return 0; } // -- WiFi wrappers -- +#if CONFIG_ESP_WIFI_ENABLED #include "esp_wifi.h" #include "esp_private/wifi.h" #include "esp_netif.h" @@ -258,22 +308,6 @@ void ow_wifi_deinit(void) } } -int ow_wifi_set_mode(int mode) -{ - // mode: 0=none, 1=sta, 2=ap, 3=apsta - return esp_wifi_set_mode((wifi_mode_t)mode) == ESP_OK ? 1 : 0; -} - -int ow_wifi_start(void) -{ - return esp_wifi_start() == ESP_OK ? 1 : 0; -} - -int ow_wifi_stop(void) -{ - return esp_wifi_stop() == ESP_OK ? 1 : 0; -} - int ow_wifi_sta_config(const char *ssid, const char *password, const uint8_t *bssid) { wifi_config_t cfg = {0}; @@ -298,16 +332,6 @@ int ow_wifi_sta_config(const char *ssid, const char *password, const uint8_t *bs return esp_wifi_set_config(WIFI_IF_STA, &cfg) == ESP_OK ? 1 : 0; } -int ow_wifi_sta_connect(void) -{ - return esp_wifi_connect() == ESP_OK ? 1 : 0; -} - -int ow_wifi_sta_disconnect(void) -{ - return esp_wifi_disconnect() == ESP_OK ? 1 : 0; -} - int ow_wifi_ap_config(const char *ssid, const char *password, uint8_t channel, uint8_t max_conn, uint8_t hidden) { @@ -336,27 +360,6 @@ int ow_wifi_ap_config(const char *ssid, const char *password, return esp_wifi_set_config(WIFI_IF_AP, &cfg) == ESP_OK ? 1 : 0; } -int ow_wifi_set_tx_power(int8_t power) -{ - return esp_wifi_set_max_tx_power(power) == ESP_OK ? 1 : 0; -} - -int ow_wifi_get_channel(uint8_t *channel) -{ - uint8_t primary; - wifi_second_chan_t second; - if (esp_wifi_get_channel(&primary, &second) != ESP_OK) - return 0; - *channel = primary; - return 1; -} - -int ow_wifi_get_mac(int iface, uint8_t *mac) -{ - // iface: 0=sta, 1=ap - return esp_read_mac(mac, iface == 0 ? ESP_MAC_WIFI_STA : ESP_MAC_WIFI_SOFTAP) == ESP_OK ? 1 : 0; -} - // Ethernet frame RX callbacks -- one per netif (STA=0, AP=1). // esp_wifi_internal_reg_rxcb gives us raw Ethernet frames before lwIP, // so the bridge/routing layer sees all traffic. @@ -390,11 +393,6 @@ int ow_wifi_set_rx_callback(ow_wifi_rx_cb_t cb) return err == ESP_OK ? 1 : 0; } -int ow_wifi_tx(int iface, const uint8_t *data, int len) -{ - return esp_wifi_internal_tx((wifi_interface_t)iface, (void *)data, (uint16_t)len) == ESP_OK ? 1 : 0; -} - void ow_wifi_set_sta_callback(ow_wifi_event_cb_t cb) { ow_wifi_sta_cb = cb; @@ -404,103 +402,109 @@ void ow_wifi_set_ap_callback(ow_wifi_event_cb_t cb) { ow_wifi_ap_cb = cb; } +#else // !CONFIG_ESP_WIFI_ENABLED + +typedef void (*ow_wifi_event_cb_t)(int, void *, int); +typedef void (*ow_wifi_rx_cb_t)(const uint8_t *, int, int); -// -- TWAI (CAN) wrappers -- +int ow_wifi_init(void) { return -1; } +void ow_wifi_deinit(void) {} +int ow_wifi_sta_config(const char *s, const char *p, const uint8_t *b) { (void)s;(void)p;(void)b; return 0; } +int ow_wifi_ap_config(const char *s, const char *p, uint8_t c, uint8_t m, uint8_t h) { (void)s;(void)p;(void)c;(void)m;(void)h; return 0; } +int ow_wifi_set_rx_callback(ow_wifi_rx_cb_t cb) { (void)cb; return 0; } +void ow_wifi_set_sta_callback(ow_wifi_event_cb_t cb) { (void)cb; } +void ow_wifi_set_ap_callback(ow_wifi_event_cb_t cb) { (void)cb; } -// Legacy TWAI driver -- TODO: port to esp_twai.h node-handle API +#endif // CONFIG_ESP_WIFI_ENABLED + +// -- CAN (TWAI) driver -- +// +// Only ow_can_open lives here -- it builds the timing/general/filter config +// structs and does install+start. Everything else is called directly from D +// via the ESP-IDF _v2 handle API. + +#include "soc/soc_caps.h" +#if SOC_TWAI_SUPPORTED #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcpp" #include "driver/twai.h" #pragma GCC diagnostic pop -static bool ow_twai_initialized; - -int ow_twai_init(uint32_t baud_rate, int tx_io, int rx_io) +twai_handle_t ow_can_open(unsigned port, uint32_t bitrate, int tx_gpio, int rx_gpio, uint8_t sjw, uint8_t tseg1, uint8_t tseg2, uint16_t brp) { - if (ow_twai_initialized) - return 1; + if (port >= SOC_TWAI_CONTROLLER_NUM) + return NULL; twai_timing_config_t timing; - switch (baud_rate) + if (brp > 0) { - case 25000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_25KBITS(); break; - case 50000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_50KBITS(); break; - case 100000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_100KBITS(); break; - case 125000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_125KBITS(); break; - case 250000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_250KBITS(); break; - case 500000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_500KBITS(); break; - case 800000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_800KBITS(); break; - case 1000000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_1MBITS(); break; - default: return 0; + memset(&timing, 0, sizeof(timing)); + timing.clk_src = TWAI_CLK_SRC_DEFAULT; + timing.brp = brp; + timing.tseg_1 = tseg1; + timing.tseg_2 = tseg2; + timing.sjw = sjw; + } + else + { + switch (bitrate) + { + case 1000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_1KBITS(); break; + case 5000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_5KBITS(); break; + case 10000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_10KBITS(); break; + case 12500: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_12_5KBITS(); break; + case 16000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_16KBITS(); break; + case 20000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_20KBITS(); break; + case 25000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_25KBITS(); break; + case 50000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_50KBITS(); break; + case 100000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_100KBITS(); break; + case 125000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_125KBITS(); break; + case 250000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_250KBITS(); break; + case 500000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_500KBITS(); break; + case 800000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_800KBITS(); break; + case 1000000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_1MBITS(); break; + default: return NULL; + } } - twai_general_config_t general = TWAI_GENERAL_CONFIG_DEFAULT(tx_io, rx_io, TWAI_MODE_NORMAL); + twai_general_config_t general = TWAI_GENERAL_CONFIG_DEFAULT_V2( + port, tx_gpio, rx_gpio, TWAI_MODE_NORMAL); + general.alerts_enabled = TWAI_ALERT_BUS_ERROR | TWAI_ALERT_ERR_PASS + | TWAI_ALERT_ERR_ACTIVE | TWAI_ALERT_BUS_OFF | TWAI_ALERT_ABOVE_ERR_WARN + | TWAI_ALERT_BELOW_ERR_WARN | TWAI_ALERT_RX_FIFO_OVERRUN + | TWAI_ALERT_RX_QUEUE_FULL | TWAI_ALERT_ARB_LOST | TWAI_ALERT_TX_FAILED; twai_filter_config_t filter = TWAI_FILTER_CONFIG_ACCEPT_ALL(); - if (twai_driver_install(&general, &timing, &filter) != ESP_OK) - return 0; + twai_handle_t handle = NULL; + if (twai_driver_install_v2(&general, &timing, &filter, &handle) != ESP_OK) + return NULL; - if (twai_start() != ESP_OK) + if (twai_start_v2(handle) != ESP_OK) { - twai_driver_uninstall(); - return 0; + twai_driver_uninstall_v2(handle); + return NULL; } - ow_twai_initialized = true; - return 1; + return handle; } -void ow_twai_deinit(void) -{ - if (!ow_twai_initialized) - return; - twai_stop(); - twai_driver_uninstall(); - ow_twai_initialized = false; -} - -int ow_twai_transmit(uint32_t id, int extended, int rtr, const uint8_t *data, uint8_t len) -{ - if (!ow_twai_initialized) - return 0; - - twai_message_t msg = {0}; - msg.identifier = id; - msg.extd = extended ? 1 : 0; - msg.rtr = rtr ? 1 : 0; - msg.data_length_code = len; - if (len > 0 && data) - memcpy(msg.data, data, len > 8 ? 8 : len); +#else // !SOC_TWAI_SUPPORTED - return twai_transmit(&msg, 0) == ESP_OK ? 1 : 0; -} +typedef struct twai_obj_t *twai_handle_t; -int ow_twai_receive(uint32_t *id, int *extended, int *rtr, uint8_t *data, uint8_t *len) +twai_handle_t ow_can_open(unsigned, uint32_t, int, int, uint8_t, uint8_t, uint8_t, uint16_t) { - if (!ow_twai_initialized) - return 0; - - twai_message_t msg; - if (twai_receive(&msg, 0) != ESP_OK) - return 0; - - *id = msg.identifier; - *extended = msg.extd; - *rtr = msg.rtr; - *len = msg.data_length_code; - if (msg.data_length_code > 0) - memcpy(data, msg.data, msg.data_length_code > 8 ? 8 : msg.data_length_code); - - return 1; + return (twai_handle_t)0; } +#endif // SOC_TWAI_SUPPORTED + // -- lwIP netdb wrappers (link-order fix) -- // D object references lwip_getaddrinfo/lwip_freeaddrinfo but the D object // appears after liblwip.a in the link. These wrappers are in libmain.a // which is linked with --whole-archive, ensuring they're always present. -int ow_lwip_getaddrinfo(const char *nodename, const char *servname, - const struct addrinfo *hints, struct addrinfo **res) +int ow_lwip_getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res) { return lwip_getaddrinfo(nodename, servname, hints, res); } diff --git a/src/sys/esp32/timer.d b/src/sys/esp32/timer.d new file mode 100644 index 0000000..fe36e83 --- /dev/null +++ b/src/sys/esp32/timer.d @@ -0,0 +1,38 @@ +// ESP32 timer driver -- D wrapper over ESP-IDF timer APIs +// +// Uses esp_timer_get_time() for monotonic microsecond clock. +// Periodic tick uses FreeRTOS tick or esp_timer under the hood. +module sys.esp32.timer; + +nothrow @nogc: + + +enum uint mtime_freq_hz = 1_000_000; // esp_timer_get_time returns microseconds +enum bool has_mtime = true; +enum bool has_rtc = false; +enum bool has_mcycle = false; +enum bool has_timer_stop = false; +enum bool has_wfi_sleep = false; + +ulong mtime_read() +{ + return cast(ulong)esp_timer_get_time(); +} + +alias TimerCallback = void function() nothrow @nogc; + +void timer_set_periodic(uint period_us, TimerCallback cb) +{ + tick_callback = cb; + // TODO: configure esp_timer or FreeRTOS tick for periodic callback +} + + +private: + +private __gshared TimerCallback tick_callback; + +extern(C) nothrow @nogc +{ + long esp_timer_get_time(); +} diff --git a/src/sys/esp32/uart.d b/src/sys/esp32/uart.d new file mode 100644 index 0000000..225466d --- /dev/null +++ b/src/sys/esp32/uart.d @@ -0,0 +1,100 @@ +// ESP32 UART driver -- D wrapper over the C shim (ow_shim.c) +// +// The C shim calls ESP-IDF UART HAL directly (no FreeRTOS UART driver). +// GPIO pin routing is done via the ROM GPIO matrix. +// +// 3 UART ports: UART0 (console), UART1, UART2. +// UART0 TX/RX defaults set by bootloader (typically GPIO43/44 on S3). +module sys.esp32.uart; + +import sys.baremetal.uart : Parity, StopBits, UartConfig; + +nothrow @nogc: + + +// SOC_UART_NUM per chip variant +version (ESP32) enum num_uarts = 3; +else version (ESP32_S3) enum num_uarts = 3; +else version (ESP32_P4) enum num_uarts = 6; +else version (ESP32_S2) enum num_uarts = 2; +else version (ESP32_C2) enum num_uarts = 2; +else version (ESP32_C3) enum num_uarts = 2; +else version (ESP32_C5) enum num_uarts = 2; +else version (ESP32_C6) enum num_uarts = 3; +else version (ESP32_H2) enum num_uarts = 2; +else static assert(false, "unknown Espressif chip -- add num_uarts"); + +enum uint uart_clock_hz = 80_000_000; +enum bool has_irq_driven_uart = false; +enum bool has_dma_driven_uart = false; + +bool uart_hw_open(uint id, UartConfig cfg) +{ + if (id >= num_uarts) + return false; + byte tx = cfg.tx_gpio == ubyte.max ? -1 : cast(byte)cfg.tx_gpio; + byte rx = cfg.tx_gpio == ubyte.max ? -1 : cast(byte)cfg.tx_gpio; + return ow_uart_open(id, cfg.baud_rate, cfg.data_bits, cast(ubyte)cfg.stop_bits, cast(ubyte)cfg.parity, tx, rx) != 0; +} + +void uart_hw_close(uint id) +{ + ow_uart_close(id); +} + +ptrdiff_t uart_hw_read(uint id, void[] buffer) +{ + return ow_uart_read(id, cast(ubyte*)buffer.ptr, cast(int)buffer.length); +} + +ptrdiff_t uart_hw_write(uint id, const(void)[] data) +{ + return ow_uart_write(id, cast(const(ubyte)*)data.ptr, cast(int)data.length); +} + +void uart_hw_poll(uint id) +{ + // ESP32 UART HAL is polled via read/rx_pending -- no separate poll needed +} + +bool uart_hw_check_errors(uint id) +{ + // TODO: check UART error status register via HAL + return false; +} + +ptrdiff_t uart_hw_rx_pending(uint id) +{ + return ow_uart_rx_pending(id); +} + +ptrdiff_t uart_hw_flush(uint id) +{ + return ow_uart_flush(id); +} + +bool uart_tx_idle(uint id) +{ + return ow_uart_tx_idle(id) != 0; +} + +void uart0_hw_puts(const(char)[] s) +{ + foreach (ch; s) + esp_rom_uart_putc(ch); +} + +private: + +extern(C) nothrow @nogc +{ + void esp_rom_uart_putc(char c) nothrow @nogc; + + int ow_uart_open(uint port, uint baud_rate, ubyte data_bits, ubyte stop_bits, ubyte parity, byte tx_gpio, byte rx_gpio); + void ow_uart_close(uint port); + int ow_uart_read(uint port, ubyte* buf, int len); + int ow_uart_write(uint port, const(ubyte)* buf, int len); + int ow_uart_rx_pending(uint port); + int ow_uart_tx_idle(uint port); + int ow_uart_flush(uint port); +} diff --git a/src/sys/esp32/wifi.d b/src/sys/esp32/wifi.d new file mode 100644 index 0000000..2644dd4 --- /dev/null +++ b/src/sys/esp32/wifi.d @@ -0,0 +1,302 @@ +// ESP32 WiFi driver -- D wrapper over C shim + direct ESP-IDF calls +// +// The C shim (ow_shim.c) handles: +// - ow_wifi_init/deinit: WIFI_INIT_CONFIG_DEFAULT macro, netif creation, +// event handler registration +// - ow_wifi_sta_config/ap_config: wifi_config_t struct construction +// - ow_wifi_set_rx_callback: RX trampolines that forward to D AND to +// esp_netif_receive (so lwIP still works) +// +// Everything else (mode, connect, disconnect, tx, mac, channel, power) +// calls ESP-IDF directly. +// +// ESP32 has one WiFi port (port 0). +module sys.esp32.wifi; + +import sys.baremetal.wifi; + +nothrow @nogc: + + +enum uint num_wifi = 1; + + +bool wifi_open(uint port, ref const WifiConfig cfg) +{ + if (_opened) + return false; + + if (ow_wifi_init() != 0) + return false; + + ow_wifi_set_sta_callback(&sta_event_trampoline); + ow_wifi_set_ap_callback(&ap_event_trampoline); + + if (cfg.tx_power != 0) + esp_wifi_set_max_tx_power(cfg.tx_power); + + if (esp_wifi_start() != ESP_OK) + { + ow_wifi_deinit(); + return false; + } + + _opened = true; + return true; +} + +void wifi_close(uint port) +{ + if (!_opened) + return; + ow_wifi_set_rx_callback(null); + ow_wifi_set_sta_callback(null); + ow_wifi_set_ap_callback(null); + esp_wifi_stop(); + ow_wifi_deinit(); + _opened = false; + _event_cb = null; + _rx_cb = null; + _evt_sta_connected = false; + _evt_sta_disconnected = false; + _evt_ap_started = false; + _evt_ap_stopped = false; +} + +bool wifi_set_mode(uint port, WifiMode mode) +{ + // WifiMode enum values match ESP-IDF wifi_mode_t: 0=none,1=sta,2=ap,3=apsta + return esp_wifi_set_mode(cast(int)mode) == ESP_OK; +} + +bool wifi_sta_configure(uint port, ref const WifiStaConfig cfg) +{ + // Stack buffers for null-termination (SSID max 32, password max 64) + char[33] ssid_z = 0; + char[65] pw_z = 0; + + if (cfg.ssid.length > 0 && cfg.ssid.length <= 32) + ssid_z[0 .. cfg.ssid.length] = cfg.ssid[]; + if (cfg.password.length > 0 && cfg.password.length <= 64) + pw_z[0 .. cfg.password.length] = cfg.password[]; + + bool has_bssid = cfg.bssid != typeof(cfg.bssid).init; + + return ow_wifi_sta_config( + cfg.ssid.length > 0 ? ssid_z.ptr : null, + cfg.password.length > 0 ? pw_z.ptr : null, + has_bssid ? cfg.bssid.ptr : null) != 0; +} + +bool wifi_sta_connect(uint port) +{ + _evt_sta_connected = false; + _evt_sta_disconnected = false; + return esp_wifi_connect() == ESP_OK; +} + +bool wifi_sta_disconnect(uint port) +{ + return esp_wifi_disconnect() == ESP_OK; +} + +bool wifi_ap_configure(uint port, ref const WifiApConfig cfg) +{ + char[33] ssid_z = 0; + char[65] pw_z = 0; + + if (cfg.ssid.length > 0 && cfg.ssid.length <= 32) + ssid_z[0 .. cfg.ssid.length] = cfg.ssid[]; + if (cfg.password.length > 0 && cfg.password.length <= 64) + pw_z[0 .. cfg.password.length] = cfg.password[]; + + return ow_wifi_ap_config( + cfg.ssid.length > 0 ? ssid_z.ptr : null, + cfg.password.length > 0 ? pw_z.ptr : null, + cfg.channel, cfg.max_clients, cfg.hidden ? 1 : 0) != 0; +} + +size_t wifi_ap_get_clients(uint port, WifiStaInfo[] buf) +{ + // TODO: esp_wifi_ap_get_sta_list + return 0; +} + +// Scanning + +bool wifi_scan_start(uint port, ref const WifiScanConfig cfg) +{ + // TODO: esp_wifi_scan_start + return false; +} + +void wifi_scan_stop(uint port) +{ + // TODO: esp_wifi_scan_stop +} + +size_t wifi_scan_get_results(uint port, WifiScanResult[] buf) +{ + // TODO: esp_wifi_scan_get_ap_records + return 0; +} + +// Frame TX/RX + +int wifi_tx(uint port, WifiVif vif, const(ubyte)[] data) +{ + if (data.length == 0) + return -1; + return esp_wifi_internal_tx(cast(int)vif, cast(void*)data.ptr, cast(ushort)data.length) == ESP_OK ? 0 : -1; +} + +void wifi_set_rx_callback(uint port, WifiRxCallback cb) +{ + _rx_cb = cb; + ow_wifi_set_rx_callback(cb !is null ? &rx_trampoline : null); +} + +// Queries + +bool wifi_get_mac(uint port, WifiVif vif, ref ubyte[6] mac) +{ + // ESP_MAC_WIFI_STA=0, ESP_MAC_WIFI_SOFTAP=1 + return esp_read_mac(mac.ptr, cast(int)vif) == ESP_OK; +} + +ubyte wifi_get_channel(uint port) +{ + ubyte primary = void; + int second = void; + if (esp_wifi_get_channel(&primary, &second) != ESP_OK) + return 0; + return primary; +} + +byte wifi_get_rssi(uint port) +{ + // TODO: esp_wifi_sta_get_ap_info -> rssi + return -127; +} + +bool wifi_set_tx_power(uint port, byte power_dbm) +{ + return esp_wifi_set_max_tx_power(power_dbm) == ESP_OK; +} + +// Events + +void wifi_set_event_callback(uint port, WifiEventCallback cb) +{ + _event_cb = cb; +} + +// Poll -- check ISR-set flags and deliver events to D callback. +// Called from main loop since ESP events arrive on the event task. +void wifi_poll(uint port) +{ + if (_event_cb is null) + return; + + Wifi w = Wifi(0); + + if (_evt_sta_connected) + { + _evt_sta_connected = false; + _event_cb(w, WifiEvent.sta_connected, null); + } + if (_evt_sta_disconnected) + { + _evt_sta_disconnected = false; + _event_cb(w, WifiEvent.sta_disconnected, null); + } + if (_evt_ap_started) + { + _evt_ap_started = false; + _event_cb(w, WifiEvent.ap_started, null); + } + if (_evt_ap_stopped) + { + _evt_ap_stopped = false; + _event_cb(w, WifiEvent.ap_stopped, null); + } +} + + +private: + +enum int ESP_OK = 0; + +// ESP-IDF event IDs (from esp_wifi_types.h) +enum : int +{ + WIFI_EVENT_STA_START = 2, + WIFI_EVENT_STA_STOP = 3, + WIFI_EVENT_STA_CONNECTED = 4, + WIFI_EVENT_STA_DISCONNECTED = 5, + WIFI_EVENT_AP_START = 12, + WIFI_EVENT_AP_STOP = 13, + WIFI_EVENT_AP_STACONNECTED = 14, + WIFI_EVENT_AP_STADISCONNECTED = 15, +} + +__gshared bool _opened; +__gshared WifiEventCallback _event_cb; +__gshared WifiRxCallback _rx_cb; + +// Flags set from ESP event task, polled from main loop +__gshared bool _evt_sta_connected; +__gshared bool _evt_sta_disconnected; +__gshared bool _evt_ap_started; +__gshared bool _evt_ap_stopped; + +// Event trampolines -- called from ESP event task via C shim +extern(C) void sta_event_trampoline(int event_id, void*, int) nothrow @nogc +{ + if (event_id == WIFI_EVENT_STA_CONNECTED) + _evt_sta_connected = true; + else if (event_id == WIFI_EVENT_STA_DISCONNECTED) + _evt_sta_disconnected = true; +} + +extern(C) void ap_event_trampoline(int event_id, void*, int) nothrow @nogc +{ + if (event_id == WIFI_EVENT_AP_START) + _evt_ap_started = true; + else if (event_id == WIFI_EVENT_AP_STOP) + _evt_ap_stopped = true; +} + +// RX trampoline -- called from C shim's esp_wifi_internal_reg_rxcb handler. +// The C shim also forwards to esp_netif_receive so lwIP still gets frames. +extern(C) void rx_trampoline(const(ubyte)* data, int len, int iface) nothrow @nogc +{ + if (_rx_cb !is null && len > 0) + _rx_cb(Wifi(0), cast(WifiVif)iface, data[0 .. len]); +} + +// C shim functions (ow_shim.c) -- needed for macros, complex structs, netif +extern(C) nothrow @nogc +{ + int ow_wifi_init(); + void ow_wifi_deinit(); + int ow_wifi_sta_config(const(char)* ssid, const(char)* password, const(ubyte)* bssid); + int ow_wifi_ap_config(const(char)* ssid, const(char)* password, ubyte channel, ubyte max_conn, ubyte hidden); + int ow_wifi_set_rx_callback(void function(const(ubyte)*, int, int) nothrow @nogc cb); + void ow_wifi_set_sta_callback(void function(int, void*, int) nothrow @nogc); + void ow_wifi_set_ap_callback(void function(int, void*, int) nothrow @nogc); +} + +// Direct ESP-IDF calls +extern(C) nothrow @nogc +{ + int esp_wifi_set_mode(int mode); + int esp_wifi_start(); + int esp_wifi_stop(); + int esp_wifi_connect(); + int esp_wifi_disconnect(); + int esp_wifi_set_max_tx_power(byte power); + int esp_wifi_get_channel(ubyte* primary, int* second); + int esp_read_mac(ubyte* mac, int type); + int esp_wifi_internal_tx(int ifx, void* buffer, ushort len); +} diff --git a/src/sys/rp2350/boot2.S b/src/sys/rp2350/boot2.S new file mode 100644 index 0000000..4d9514b --- /dev/null +++ b/src/sys/rp2350/boot2.S @@ -0,0 +1,69 @@ +/* RP2350 second-stage bootloader (boot2) + * + * The RP2350 ROM reads this 256-byte block from the start of QSPI flash, + * copies it to SRAM, and executes it. boot2's job is to configure the + * QSPI controller for XIP (execute-in-place), then return to the ROM + * which will then load the vector table from flash. + * + * This is a minimal stub that configures generic 03h SPI read mode. + * It works with any standard SPI flash but is slow (single-bit, no XIP cache). + * For production, replace with the Pico SDK's boot2_w25q080.S (or similar) + * which enables QSPI and continuous read mode for much better performance. + * + * The block must be exactly 256 bytes. The last 4 bytes are a CRC32 + * checksum of the first 252 bytes (verified by the ROM before execution). + */ + + .syntax unified + .cpu cortex-m33 + .thumb + + .section .boot2, "ax" + .global __boot2_entry + .type __boot2_entry, %function +__boot2_entry: + +/* SSI (Synopsys SSI / QSPI controller) registers */ +.equ SSI_BASE, 0x18000000 +.equ SSI_CTRLR0, 0x00 +.equ SSI_SSIENR, 0x08 /* SSI enable */ +.equ SSI_SPI_CTRLR0, 0xF4 /* SPI control register */ + +/* Configure SSI for standard SPI 03h read, 32-bit address+data frames */ + + /* Disable SSI while configuring */ + ldr r3, =SSI_BASE + movs r0, #0 + str r0, [r3, #SSI_SSIENR] + + /* CTRLR0: 8-bit data frames, SPI mode 0 (CPOL=0, CPHA=0) */ + movs r0, #0x07 /* DFS = 8-1 = 7 (8-bit frames) */ + movs r1, #0 + orr r0, r0, r1 /* TMOD=0 (TX+RX), FRF=0 (Motorola SPI) */ + str r0, [r3, #SSI_CTRLR0] + + /* SPI_CTRLR0: standard 03h read, 24-bit address, 8 wait cycles */ + movs r0, #0x03 /* INST_L=8bit (0x03), ADDR_L=24bit */ + lsls r1, r0, #0 + movs r0, #0x06 /* ADDR_L = 24 bits (6 * 4) */ + lsls r0, r0, #2 + orr r1, r1, r0 + /* TRANS_TYPE = 0 (instruction + address both in standard SPI) */ + str r1, [r3, #SSI_SPI_CTRLR0] + + /* Re-enable SSI */ + movs r0, #1 + str r0, [r3, #SSI_SSIENR] + + /* Return to ROM -- it will now use XIP to read the vector table */ + bx lr + + .size __boot2_entry, . - __boot2_entry + + /* Pad to 252 bytes (256 - 4 byte CRC32 at end) */ + .balign 256, 0x00 + /* CRC32 placeholder -- must be computed by the build tooling or + * flash programmer. The Pico SDK's pad_checksum tool does this. + * For now, set to zero -- will need post-processing before flashing. */ + + .size __boot2_entry, . - __boot2_entry diff --git a/src/sys/rp2350/irq.d b/src/sys/rp2350/irq.d new file mode 100644 index 0000000..fe24fd8 --- /dev/null +++ b/src/sys/rp2350/irq.d @@ -0,0 +1,59 @@ +// RP2350 interrupt controller driver +// +// Cortex-M33 uses the standard ARM NVIC (Nested Vectored Interrupt Controller). +// RP2350 has 52 peripheral interrupts (IRQ 0-51). +module sys.rp2350.irq; + +@nogc nothrow: + +enum bool has_plic = false; +enum bool has_nvic = true; +enum bool has_per_irq_control = true; +enum bool has_irq_priority = true; +enum bool has_wait_for_interrupt = false; +enum bool has_irq_diagnostics = false; +enum uint irq_max = 52; + +import core.volatile; + +// NVIC registers (ARM standard) +private enum ulong NVIC_ISER0 = 0xE000E100; // Interrupt Set Enable (2 words for 52 IRQs) +private enum ulong NVIC_ICER0 = 0xE000E180; // Interrupt Clear Enable +private enum ulong NVIC_ISPR0 = 0xE000E200; // Interrupt Set Pending +private enum ulong NVIC_ICPR0 = 0xE000E280; // Interrupt Clear Pending +private enum ulong NVIC_IPR0 = 0xE000E400; // Interrupt Priority (byte-accessible) + +// Globally disable interrupts (set PRIMASK) +void irq_disable() +{ + asm @nogc nothrow { "cpsid i"; } +} + +// Globally enable interrupts (clear PRIMASK) +void irq_enable() +{ + asm @nogc nothrow { "cpsie i"; } +} + +// Enable a specific peripheral IRQ (0-51) +void irq_set_enable(uint irq_num) +{ + immutable reg = irq_num / 32; + immutable bit = irq_num % 32; + volatileStore(cast(uint*)(NVIC_ISER0 + reg * 4), 1u << bit); +} + +// Disable a specific peripheral IRQ +void irq_clear_enable(uint irq_num) +{ + immutable reg = irq_num / 32; + immutable bit = irq_num % 32; + volatileStore(cast(uint*)(NVIC_ICER0 + reg * 4), 1u << bit); +} + +// Set priority for a peripheral IRQ (0 = highest, 255 = lowest) +// Cortex-M33 on RP2350 implements 4 priority bits (top 4 of 8) +void irq_set_priority(uint irq_num, ubyte priority) +{ + volatileStore(cast(ubyte*)(NVIC_IPR0 + irq_num), priority); +} diff --git a/src/sys/rp2350/package.d b/src/sys/rp2350/package.d new file mode 100644 index 0000000..3b4186a --- /dev/null +++ b/src/sys/rp2350/package.d @@ -0,0 +1,96 @@ +// RP2350 platform package (ARM Cortex-M33) +// +// Provides sys_init() as the single entry point for all +// hardware initialization. Called from start.S before main(). +module sys.rp2350; + +public import sys.rp2350.uart; +public import sys.rp2350.irq; +public import sys.rp2350.timer; + +import sys.baremetal.uart : UartConfig; +import core.volatile; + +@nogc nothrow: + +private extern(C) void __register_frame_info(const void*, void*); +private extern(C) extern const ubyte __eh_frame_start; +private ubyte[48] __eh_frame_object; // storage for libgcc (no-op on ARM EHABI) + +// RP2350 peripheral base addresses +enum ulong RESETS_BASE = 0x40020000; +enum ulong CLOCKS_BASE = 0x40010000; +enum ulong XOSC_BASE = 0x40048000; +enum ulong PLL_SYS_BASE = 0x40060000; +enum ulong SIO_BASE = 0xD0000000; +enum ulong IO_BANK0_BASE = 0x40028000; +enum ulong PADS_BANK0_BASE = 0x40038000; + +// Atomic set/clear/xor aliases (RP2350 address alias trick) +enum ulong REG_ALIAS_SET = 0x00002000; +enum ulong REG_ALIAS_CLR = 0x00003000; + +// RESETS register offsets +enum ulong RESETS_RESET = 0x00; +enum ulong RESETS_DONE = 0x08; + +// Reset bits for peripherals we need early +enum uint RESET_UART0 = 1 << 26; +enum uint RESET_UART1 = 1 << 27; +enum uint RESET_IO_BANK0 = 1 << 8; +enum uint RESET_PADS_BANK0 = 1 << 9; + +private void mmio_write(ulong addr, uint val) @nogc nothrow +{ + volatileStore(cast(uint*)addr, val); +} + +private uint mmio_read(ulong addr) @nogc nothrow +{ + return volatileLoad(cast(uint*)addr); +} + +// Take peripherals out of reset and wait for them to be ready +private void unreset_wait(uint bits) +{ + // Clear reset bits (take out of reset) + mmio_write(RESETS_BASE + RESETS_RESET + REG_ALIAS_CLR, bits); + // Wait for reset done + while ((mmio_read(RESETS_BASE + RESETS_DONE) & bits) != bits) + {} +} + +// Initialize all clocks, peripherals, and I/O needed at boot. +// Order matters: +// 1. Unreset GPIO and UART pads +// 2. Configure GPIO pins for UART0 +// 3. Initialize UART0 for console output +// 4. Set up SysTick timer +extern(C) void sys_init() +{ + __register_frame_info(&__eh_frame_start, &__eh_frame_object); + + // Bring up IO bank and pads, then UART0 + unreset_wait(RESET_IO_BANK0 | RESET_PADS_BANK0 | RESET_UART0); + + // GPIO0 = UART0 TX, GPIO1 = UART0 RX (function 2 on RP2350) + // IO_BANK0 GPIO_CTRL registers are at offset 0x04 + n*0x08 + enum ulong GPIO0_CTRL = IO_BANK0_BASE + 0x04; + enum ulong GPIO1_CTRL = IO_BANK0_BASE + 0x0C; + mmio_write(GPIO0_CTRL, 2); // FUNCSEL = UART + mmio_write(GPIO1_CTRL, 2); // FUNCSEL = UART + + // Init UART0 at default baud for early console + uart_hw_init(0, UartConfig.init); + + uart0_hw_puts("RP2350: sys_init\r\n"); + + // SysTick: 20Hz tick (50ms) for the main loop + // Default clock is ~150MHz after PLL init, but we're running on the + // ring oscillator (~6MHz) until clock init is implemented. + // SysTick reload = clock_hz / desired_hz - 1 + // At 6MHz ring osc: 6_000_000 / 20 - 1 = 299_999 + timer_init(299_999); + + uart0_hw_puts("RP2350: ready\r\n"); +} diff --git a/src/sys/rp2350/start.S b/src/sys/rp2350/start.S new file mode 100644 index 0000000..606aaa9 --- /dev/null +++ b/src/sys/rp2350/start.S @@ -0,0 +1,244 @@ +/* RP2350 (ARM Cortex-M33) startup + * + * Boot flow: + * 1. ROM bootloader loads 256-byte boot2 from flash, runs it from SRAM + * 2. boot2 configures QSPI flash for XIP, then returns to ROM + * 3. ROM reads vector table from flash: initial SP + Reset_Handler + * 4. Reset_Handler runs: + * a. Copy .got from flash to SRAM + * b. Copy .data from flash to SRAM + * c. Copy .tdata from flash to SRAM + * d. Zero .tbss + .bss + * e. Enable FPU (Cortex-M33 has optional FPv5-SP) + * f. Call sys_init (platform init) + * g. Run .init_array (D module constructors) + * h. Call main + * + * RP2350B register addresses: + * RESETS: 0x40020000 (peripheral reset control) + * CLOCKS: 0x40010000 + * SIO: 0xD0000000 (single-cycle I/O for GPIO, spinlocks) + * UART0: 0x40070000 + * UART1: 0x40078000 + */ + + .syntax unified + .cpu cortex-m33 + .fpu fpv5-sp-d16 + .thumb + +/* ================================================================ + * Vector table -- placed at start of FLASH (after boot2) + * Cortex-M reads entry 0 as initial SP, entry 1 as Reset_Handler + * ================================================================ */ + .section .vector_table, "a" + .global __vector_table + .type __vector_table, %object +__vector_table: + .word _stack_top /* 0: Initial stack pointer */ + .word Reset_Handler /* 1: Reset */ + .word NMI_Handler /* 2: NMI */ + .word HardFault_Handler /* 3: Hard fault */ + .word MemManage_Handler /* 4: MPU fault */ + .word BusFault_Handler /* 5: Bus fault */ + .word UsageFault_Handler /* 6: Usage fault */ + .word SecureFault_Handler /* 7: Secure fault (ARMv8-M) */ + .word 0 /* 8: Reserved */ + .word 0 /* 9: Reserved */ + .word 0 /* 10: Reserved */ + .word SVCall_Handler /* 11: SVCall */ + .word DebugMon_Handler /* 12: Debug monitor */ + .word 0 /* 13: Reserved */ + .word PendSV_Handler /* 14: PendSV */ + .word SysTick_Handler /* 15: SysTick */ + + /* IRQ 0-51 (RP2350 has 52 peripheral interrupts) */ + .rept 52 + .word Default_Handler + .endr + + .size __vector_table, . - __vector_table + +/* ================================================================ + * Reset handler + * ================================================================ */ + .section .text, "ax" + .global Reset_Handler + .type Reset_Handler, %function + .thumb_func +Reset_Handler: + + /* -- Copy .got from flash (LMA) to SRAM (VMA) -- */ + ldr r0, =_got_start + ldr r1, =_got_load + ldr r2, =_got_end + cmp r0, r1 /* skip if VMA == LMA (not relocated) */ + beq .Lgot_done + b .Lgot_check +.Lgot_copy: + ldm r1!, {r3} + stm r0!, {r3} +.Lgot_check: + cmp r0, r2 + blo .Lgot_copy +.Lgot_done: + + /* -- Copy .data from flash to SRAM -- */ + ldr r0, =_data_start + ldr r1, =_data_load + ldr r2, =_data_end + b .Ldata_check +.Ldata_copy: + ldm r1!, {r3} + stm r0!, {r3} +.Ldata_check: + cmp r0, r2 + blo .Ldata_copy + + /* -- Copy .tdata from flash to SRAM -- */ + ldr r0, =_tdata_start + ldr r1, =_tdata_load + ldr r2, =_tdata_end + b .Ltdata_check +.Ltdata_copy: + ldm r1!, {r3} + stm r0!, {r3} +.Ltdata_check: + cmp r0, r2 + blo .Ltdata_copy + + /* -- Zero .tbss -- */ + ldr r0, =_tbss_start + ldr r2, =_tbss_end + movs r3, #0 + b .Ltbss_check +.Ltbss_zero: + stm r0!, {r3} +.Ltbss_check: + cmp r0, r2 + blo .Ltbss_zero + + /* -- Zero .bss -- */ + ldr r0, =_bss_start + ldr r2, =_bss_end + movs r3, #0 + b .Lbss_check +.Lbss_zero: + stm r0!, {r3} +.Lbss_check: + cmp r0, r2 + blo .Lbss_zero + + /* -- Enable FPU (CP10 + CP11 full access) -- */ + ldr r0, =0xE000ED88 /* CPACR */ + ldr r1, [r0] + orr r1, r1, #(0xF << 20) + str r1, [r0] + dsb + isb + + /* -- Platform init -- */ + bl sys_init + + /* -- Run .init_array (D module constructors) -- */ + ldr r4, =__init_array_start + ldr r5, =__init_array_end + b .Linit_check +.Linit_loop: + ldr r0, [r4] + blx r0 + adds r4, r4, #4 +.Linit_check: + cmp r4, r5 + blo .Linit_loop + + /* -- Enter main (should never return) -- */ + bl main + + /* If main returns, spin */ +.Lhalt: + wfi + b .Lhalt + + .size Reset_Handler, . - Reset_Handler + +/* ================================================================ + * Default exception/interrupt handlers (weak, overridable) + * ================================================================ */ + .thumb_func + .weak NMI_Handler + .type NMI_Handler, %function +NMI_Handler: + b . + + .thumb_func + .weak HardFault_Handler + .type HardFault_Handler, %function +HardFault_Handler: + b . + + .thumb_func + .weak MemManage_Handler + .type MemManage_Handler, %function +MemManage_Handler: + b . + + .thumb_func + .weak BusFault_Handler + .type BusFault_Handler, %function +BusFault_Handler: + b . + + .thumb_func + .weak UsageFault_Handler + .type UsageFault_Handler, %function +UsageFault_Handler: + b . + + .thumb_func + .weak SecureFault_Handler + .type SecureFault_Handler, %function +SecureFault_Handler: + b . + + .thumb_func + .weak SVCall_Handler + .type SVCall_Handler, %function +SVCall_Handler: + b . + + .thumb_func + .weak DebugMon_Handler + .type DebugMon_Handler, %function +DebugMon_Handler: + b . + + .thumb_func + .weak PendSV_Handler + .type PendSV_Handler, %function +PendSV_Handler: + b . + + .thumb_func + .weak SysTick_Handler + .type SysTick_Handler, %function +SysTick_Handler: + b . + + .thumb_func + .weak Default_Handler + .type Default_Handler, %function +Default_Handler: + b . + +/* ================================================================ + * __aeabi_read_tp -- ARM EABI thread pointer for TLS access + * Single-threaded bare-metal: return fixed pointer to .tdata + * ================================================================ */ + .global __aeabi_read_tp + .type __aeabi_read_tp, %function + .thumb_func +__aeabi_read_tp: + ldr r0, =_tdata_start + bx lr + .size __aeabi_read_tp, . - __aeabi_read_tp diff --git a/src/sys/rp2350/syscalls.d b/src/sys/rp2350/syscalls.d new file mode 100644 index 0000000..1614115 --- /dev/null +++ b/src/sys/rp2350/syscalls.d @@ -0,0 +1,52 @@ +// RP2350 newlib/picolibc syscall stubs +// +// Minimal stubs to satisfy picolibc's syscall requirements. +// Same pattern as BL618 -- most are no-ops for baremetal. +module sys.rp2350.syscalls; + +@nogc nothrow: + +private extern(C) extern const void* __heap_start; +private extern(C) extern const void* __heap_end; + +private __gshared void* _heap_ptr; + +extern(C) void* _sbrk(ptrdiff_t incr) +{ + if (_heap_ptr is null) + _heap_ptr = cast(void*)&__heap_start; + + void* prev = _heap_ptr; + void* next = _heap_ptr + incr; + + if (next > cast(void*)&__heap_end) + return cast(void*)-1; + + _heap_ptr = next; + return prev; +} + +extern(C) int _write(int fd, const void* buf, size_t count) +{ + import sys.rp2350.uart : uart0_hw_puts; + if (fd == 1 || fd == 2) + uart0_hw_puts((cast(const(char)*)buf)[0 .. count]); + return cast(int)count; +} + +extern(C) int _read(int, void*, size_t) { return 0; } +extern(C) int _close(int) { return -1; } +extern(C) int _lseek(int, int, int) { return 0; } +extern(C) int _fstat(int, void*) { return 0; } +extern(C) int _isatty(int) { return 1; } +extern(C) void _exit(int) { while (true) {} } +extern(C) int _kill(int, int) { return -1; } +extern(C) int _getpid() { return 1; } + +// DWARF unwinder stubs -- ARM Cortex-M uses EHABI, not DWARF. +// These satisfy link-time references from the D runtime's exception +// personality code which assumes DWARF on all platforms. +extern(C) void __register_frame_info(const void*, void*) {} +extern(C) size_t _Unwind_GetIPInfo(void*, int*) { return 0; } +extern(C) void _Unwind_SetGR(void*, int, size_t) {} +extern(C) void _Unwind_SetIP(void*, size_t) {} diff --git a/src/sys/rp2350/timer.d b/src/sys/rp2350/timer.d new file mode 100644 index 0000000..1bd4b64 --- /dev/null +++ b/src/sys/rp2350/timer.d @@ -0,0 +1,80 @@ +// RP2350 timer driver +// +// Uses the ARM Cortex-M33 SysTick timer for the periodic main loop tick. +// SysTick is a 24-bit down-counter clocked from the processor clock. +// +// RP2350 also has a 64-bit microsecond timer at 0x400B_0000 (TIMER0) +// which can be used for wall-clock time -- not yet implemented here. +module sys.rp2350.timer; + +import core.volatile; + +@nogc nothrow: + +enum uint mtime_freq_hz = 1_000_000; // TIMER0 runs at 1MHz (microsecond counter) +enum bool has_mtime = true; +enum bool has_rtc = false; +enum bool has_mcycle = false; +enum bool has_timer_stop = false; +enum bool has_wfi_sleep = false; + +// RP2350 TIMER0: 64-bit free-running microsecond counter at 0x400B_0000 +// Always enabled, always 1MHz. Read TIMELR first (latches TIMEHR). +private enum uint TIMER0_BASE = 0x400B_0000; +private enum uint TIMEHR = TIMER0_BASE + 0x08; // Time read high (latched on TIMELR read) +private enum uint TIMELR = TIMER0_BASE + 0x0C; // Time read low (triggers latch) + +// SysTick registers (ARM standard, part of the System Control Block) +private enum uint SYST_CSR = 0xE000_E010; +private enum uint SYST_RVR = 0xE000_E014; +private enum uint SYST_CVR = 0xE000_E018; + +private enum uint CSR_ENABLE = 1 << 0; +private enum uint CSR_TICKINT = 1 << 1; +private enum uint CSR_CLKSOURCE = 1 << 2; + +alias TimerCallback = void function() @nogc nothrow; + +private __gshared TimerCallback tick_callback; + +void timer_init(uint reload_value) +{ + volatileStore(cast(uint*)(cast(size_t)SYST_RVR), reload_value & 0x00FFFFFF); + volatileStore(cast(uint*)(cast(size_t)SYST_CVR), 0); + volatileStore(cast(uint*)(cast(size_t)SYST_CSR), CSR_ENABLE | CSR_TICKINT | CSR_CLKSOURCE); +} + +void timer_hw_init() +{ + // TIMER0 is always running at 1MHz on RP2350 -- nothing to init. +} + +// Read 64-bit monotonic microsecond counter. +// Must read TIMELR first -- this latches TIMEHR atomically. +ulong mtime_read() +{ + uint lo = volatileLoad(cast(uint*)(cast(size_t)TIMELR)); + uint hi = volatileLoad(cast(uint*)(cast(size_t)TIMEHR)); + return (cast(ulong)hi << 32) | lo; +} + +void timer_set_periodic(uint period_ticks, TimerCallback cb) +{ + tick_callback = cb; + // Use SysTick for periodic interrupts. + // period_ticks is in timer ticks (microseconds at 1MHz). + // SysTick runs from processor clock -- assume 150MHz after PLL init. + // Convert: systick_reload = period_us * 150 + uint reload = period_ticks * 150; + if (reload > 0x00FF_FFFF) + reload = 0x00FF_FFFF; // SysTick is 24-bit + volatileStore(cast(uint*)(cast(size_t)SYST_RVR), reload); + volatileStore(cast(uint*)(cast(size_t)SYST_CVR), 0); + volatileStore(cast(uint*)(cast(size_t)SYST_CSR), CSR_ENABLE | CSR_TICKINT | CSR_CLKSOURCE); +} + +extern(C) void SysTick_Handler() @nogc nothrow +{ + if (tick_callback !is null) + tick_callback(); +} diff --git a/src/sys/rp2350/uart.d b/src/sys/rp2350/uart.d new file mode 100644 index 0000000..4807ca0 --- /dev/null +++ b/src/sys/rp2350/uart.d @@ -0,0 +1,195 @@ +// RP2350 UART driver +// +// RP2350 has 2x PL011 UARTs: +// UART0 0x4007_0000 Default console (GPIO0=TX, GPIO1=RX) +// UART1 0x4007_8000 General purpose (GPIO4=TX, GPIO5=RX typical) +// +// PL011 register layout (ARM PrimeCell UART). +module sys.rp2350.uart; + +import core.volatile; + +import sys.baremetal.uart : Parity, StopBits, UartConfig; + +nothrow @nogc: + +enum num_uarts = 2; +enum uint uart_clock_hz = 6_000_000; +enum bool has_irq_driven_uart = false; +enum bool has_dma_driven_uart = false; + +// PL011 register offsets +private enum +{ + UARTDR = 0x000, // Data register + UARTRSR = 0x004, // Receive status / error clear + UARTFR = 0x018, // Flag register + UARTILPR = 0x020, // IrDA low-power counter + UARTIBRD = 0x024, // Integer baud rate divisor + UARTFBRD = 0x028, // Fractional baud rate divisor + UARTLCR_H = 0x02C, // Line control + UARTCR = 0x030, // Control register + UARTIFLS = 0x034, // Interrupt FIFO level select + UARTIMSC = 0x038, // Interrupt mask set/clear + UARTRIS = 0x03C, // Raw interrupt status + UARTMIS = 0x040, // Masked interrupt status + UARTICR = 0x044, // Interrupt clear + UARTDMACR = 0x048, // DMA control +} + +// Flag register bits +private enum +{ + FR_TXFF = 1 << 5, // TX FIFO full + FR_RXFE = 1 << 4, // RX FIFO empty + FR_BUSY = 1 << 3, // UART busy transmitting +} + +// Control register bits +private enum +{ + CR_UARTEN = 1 << 0, // UART enable + CR_TXE = 1 << 8, // TX enable + CR_RXE = 1 << 9, // RX enable +} + +private ulong uart_base(uint id) +{ + return (id == 0) ? 0x40070000 : 0x40078000; +} + +private void uart_write_reg(ulong base, uint offset, uint val) +{ + volatileStore(cast(uint*)(base + offset), val); +} + +private uint uart_read_reg(ulong base, uint offset) +{ + return volatileLoad(cast(uint*)(base + offset)); +} + +// Assumed peripheral clock -- ring oscillator ~6MHz at boot. +// After PLL init this should be updated to the actual peri_clk frequency. +private __gshared uint peri_clk_hz = 6_000_000; + +bool uart_hw_init(uint id, UartConfig cfg) +{ + immutable base = uart_base(id); + + // Disable UART while configuring + uart_write_reg(base, UARTCR, 0); + + // Baud rate: BAUDDIV = peri_clk / (16 * baud) + // Integer part = BAUDDIV + // Fractional part = (frac * 64 + 0.5) + immutable uint bauddiv_x64 = (peri_clk_hz * 4) / cfg.baud_rate; + immutable uint ibrd = bauddiv_x64 / 64; + immutable uint fbrd = bauddiv_x64 % 64; + uart_write_reg(base, UARTIBRD, ibrd); + uart_write_reg(base, UARTFBRD, fbrd); + + // Line control: 8N1, FIFO enable + uint lcr = 0; + lcr |= (cfg.data_bits - 5) << 5; // WLEN: 5=0b00, 6=0b01, 7=0b10, 8=0b11 + lcr |= 1 << 4; // FEN: enable FIFOs + if (cfg.parity != Parity.none) + { + lcr |= 1 << 1; // PEN: parity enable + if (cfg.parity == Parity.even) + lcr |= 1 << 2; // EPS: even parity + } + if (cfg.stop_bits != StopBits.one) + lcr |= 1 << 3; // STP2: 2 stop bits + uart_write_reg(base, UARTLCR_H, lcr); + + // Enable UART, TX, RX + uart_write_reg(base, UARTCR, CR_UARTEN | CR_TXE | CR_RXE); + + return true; +} + +bool uart_hw_open(uint id, UartConfig cfg) +{ + return uart_hw_init(id, cfg); +} + +void uart_hw_close(uint id) +{ + uart_write_reg(uart_base(id), UARTCR, 0); +} + +ptrdiff_t uart_hw_read(uint id, void[] buffer) +{ + immutable base = uart_base(id); + auto buf = cast(ubyte[])buffer; + ptrdiff_t n = 0; + while (n < buf.length) + { + if (uart_read_reg(base, UARTFR) & FR_RXFE) + break; + buf[n] = cast(ubyte)(uart_read_reg(base, UARTDR) & 0xFF); + ++n; + } + return n; +} + +ptrdiff_t uart_hw_write(uint id, const(void)[] data) +{ + immutable base = uart_base(id); + auto buf = cast(const(ubyte)[])data; + ptrdiff_t n = 0; + while (n < buf.length) + { + if (uart_read_reg(base, UARTFR) & FR_TXFF) + break; + uart_write_reg(base, UARTDR, buf[n]); + ++n; + } + return n; +} + +void uart_hw_poll(uint id) +{ + // PL011 FIFOs handle buffering -- nothing to poll +} + +bool uart_hw_check_errors(uint id) +{ + immutable base = uart_base(id); + immutable rsr = uart_read_reg(base, UARTRSR); + if (rsr != 0) + { + uart_write_reg(base, UARTRSR, 0); // Clear errors + return true; + } + return false; +} + +ptrdiff_t uart_hw_rx_pending(uint id) +{ + // PL011 doesn't expose FIFO level directly in a simple way. + // Return 1 if data available, 0 otherwise. + if (uart_read_reg(uart_base(id), UARTFR) & FR_RXFE) + return 0; + return 1; +} + +ptrdiff_t uart_hw_flush(uint id) +{ + immutable base = uart_base(id); + while (uart_read_reg(base, UARTFR) & FR_BUSY) + {} + return 0; +} + +// Blocking puts for early boot (before the serial stream is up) +void uart0_hw_puts(const(char)[] s) +{ + enum ulong base = 0x40070000; + foreach (c; s) + { + while (volatileLoad(cast(uint*)(base + UARTFR)) & FR_TXFF) + {} + volatileStore(cast(uint*)(base + UARTDR), cast(uint)c); + } +} diff --git a/src/sys/stm32/irq.d b/src/sys/stm32/irq.d new file mode 100644 index 0000000..397e6e6 --- /dev/null +++ b/src/sys/stm32/irq.d @@ -0,0 +1,59 @@ +// STM32 interrupt controller driver +// +// Cortex-M4/M7 use the standard ARM NVIC (Nested Vectored Interrupt Controller). +// STM32F4xx has up to 82 peripheral interrupts, STM32F7xx up to 98. +module sys.stm32.irq; + +@nogc nothrow: + +enum bool has_plic = false; +enum bool has_nvic = true; +enum bool has_per_irq_control = true; +enum bool has_irq_priority = true; +enum bool has_wait_for_interrupt = false; +enum bool has_irq_diagnostics = false; + +version (STM32F7) + enum uint irq_max = 98; +else + enum uint irq_max = 82; + +import core.volatile; + +// NVIC registers (ARM standard) +private enum ulong NVIC_ISER0 = 0xE000E100; +private enum ulong NVIC_ICER0 = 0xE000E180; +private enum ulong NVIC_ISPR0 = 0xE000E200; +private enum ulong NVIC_ICPR0 = 0xE000E280; +private enum ulong NVIC_IPR0 = 0xE000E400; + +void irq_disable() +{ + asm @nogc nothrow { "cpsid i"; } +} + +void irq_enable() +{ + asm @nogc nothrow { "cpsie i"; } +} + +void irq_set_enable(uint irq_num) +{ + immutable reg = irq_num / 32; + immutable bit = irq_num % 32; + volatileStore(cast(uint*)(NVIC_ISER0 + reg * 4), 1u << bit); +} + +void irq_clear_enable(uint irq_num) +{ + immutable reg = irq_num / 32; + immutable bit = irq_num % 32; + volatileStore(cast(uint*)(NVIC_ICER0 + reg * 4), 1u << bit); +} + +// Set priority for a peripheral IRQ (0 = highest, 255 = lowest) +// STM32F4/F7 implement 4 priority bits (top 4 of 8) +void irq_set_priority(uint irq_num, ubyte priority) +{ + volatileStore(cast(ubyte*)(NVIC_IPR0 + irq_num), priority); +} diff --git a/src/sys/stm32/package.d b/src/sys/stm32/package.d new file mode 100644 index 0000000..d7e914f --- /dev/null +++ b/src/sys/stm32/package.d @@ -0,0 +1,108 @@ +// STM32 platform package (ARM Cortex-M4/M7) +// +// Provides sys_init() as the single entry point for all +// hardware initialization. Called from start.S before main(). +// +// At reset, STM32 runs on HSI at 16 MHz (no PLL). +// sys_init brings up USART1 for console output and SysTick. +// PLL configuration for full-speed operation is not yet implemented. +module sys.stm32; + +public import sys.stm32.uart; +public import sys.stm32.irq; +public import sys.stm32.timer; + +import sys.baremetal.uart : UartConfig; +import core.volatile; + +@nogc nothrow: + +private extern(C) void __register_frame_info(const void*, void*); +private extern(C) extern const ubyte __eh_frame_start; +private ubyte[48] __eh_frame_object; + +// RCC base and clock enable registers (same for F4 and F7) +enum ulong RCC_BASE = 0x40023800; +enum ulong RCC_AHB1ENR = 0x30; +enum ulong RCC_APB2ENR = 0x44; + +// GPIO base addresses +enum ulong GPIOA_BASE = 0x40020000; + +// GPIO register offsets +enum ulong GPIO_MODER = 0x00; +enum ulong GPIO_OSPEEDR = 0x08; +enum ulong GPIO_AFRH = 0x24; + +// SCB registers for cache control (F7 only) +enum ulong SCB_CCR = 0xE000ED14; +enum ulong ICIALLU = 0xE000EF50; + +private void mmio_write(ulong addr, uint val) +{ + volatileStore(cast(uint*)addr, val); +} + +private uint mmio_read(ulong addr) +{ + return volatileLoad(cast(uint*)addr); +} + +private void mmio_set(ulong addr, uint bits) +{ + volatileStore(cast(uint*)addr, volatileLoad(cast(uint*)addr) | bits); +} + +private void mmio_rmw(ulong addr, uint clear_mask, uint set_bits) +{ + immutable val = volatileLoad(cast(uint*)addr); + volatileStore(cast(uint*)addr, (val & ~clear_mask) | set_bits); +} + +extern(C) void sys_init() +{ + __register_frame_info(&__eh_frame_start, &__eh_frame_object); + + // Enable GPIOA clock (AHB1ENR bit 0) + mmio_set(RCC_BASE + RCC_AHB1ENR, 1 << 0); + + // Enable USART1 clock (APB2ENR bit 4) + mmio_set(RCC_BASE + RCC_APB2ENR, 1 << 4); + + // Configure PA9 (USART1_TX) and PA10 (USART1_RX) as AF7 + // MODER: bits 19:18 = 0b10 (PA9 AF), bits 21:20 = 0b10 (PA10 AF) + mmio_rmw(GPIOA_BASE + GPIO_MODER, + (3u << 18) | (3u << 20), + (2u << 18) | (2u << 20)); + + // OSPEEDR: high speed for PA9/PA10 + mmio_set(GPIOA_BASE + GPIO_OSPEEDR, (3u << 18) | (3u << 20)); + + // AFRH: PA9 bits 7:4 = 7 (AF7), PA10 bits 11:8 = 7 (AF7) + mmio_rmw(GPIOA_BASE + GPIO_AFRH, + (0xFu << 4) | (0xFu << 8), + (7u << 4) | (7u << 8)); + + // Init UART0 (USART1) at default baud for early console + uart_hw_init(0, UartConfig.init); + + uart0_hw_puts("STM32: sys_init\r\n"); + + version (STM32F7) + { + // Enable instruction cache (D-cache needs DMA coherence handling) + asm @nogc nothrow { "dsb sy"; "isb"; } + mmio_write(ICIALLU, 0); + asm @nogc nothrow { "dsb sy"; "isb"; } + mmio_set(SCB_CCR, 1 << 17); + asm @nogc nothrow { "dsb sy"; "isb"; } + uart0_hw_puts("STM32F7: I-cache enabled\r\n"); + } + + // SysTick: 20 Hz tick (50ms) + // HSI = 16 MHz, AHB prescaler = 1 at reset + // Reload = 16_000_000 / 20 - 1 = 799_999 + timer_init(799_999); + + uart0_hw_puts("STM32: ready\r\n"); +} diff --git a/src/sys/stm32/start.S b/src/sys/stm32/start.S new file mode 100644 index 0000000..daeec69 --- /dev/null +++ b/src/sys/stm32/start.S @@ -0,0 +1,195 @@ +/* STM32 (ARM Cortex-M4/M7) startup + * + * Shared between STM32F4xx and STM32F7xx. The assembler receives + * -mcpu=cortex-m4 or -mcpu=cortex-m7 from the Makefile, so no + * .cpu directive here. + * + * Boot flow: + * 1. Cortex-M reads vector table from flash: initial SP + Reset_Handler + * 2. Reset_Handler runs: + * a. Copy .got from flash to RAM + * b. Copy .data from flash to RAM + * c. Zero .bss + * d. Enable FPU (CP10 + CP11) + * e. Call sys_init (clock, GPIO, UART, cache) + * f. Run .init_array (D module constructors) + * g. Call main + */ + + .syntax unified + .thumb + +/* ================================================================ + * Vector table -- placed at start of FLASH + * Cortex-M reads entry 0 as initial SP, entry 1 as Reset_Handler + * ================================================================ */ + .section .vector_table, "a" + .global __vector_table + .type __vector_table, %object +__vector_table: + .word _stack_top /* 0: Initial stack pointer */ + .word Reset_Handler /* 1: Reset */ + .word NMI_Handler /* 2: NMI */ + .word HardFault_Handler /* 3: Hard fault */ + .word MemManage_Handler /* 4: MPU fault */ + .word BusFault_Handler /* 5: Bus fault */ + .word UsageFault_Handler /* 6: Usage fault */ + .word 0 /* 7: Reserved */ + .word 0 /* 8: Reserved */ + .word 0 /* 9: Reserved */ + .word 0 /* 10: Reserved */ + .word SVCall_Handler /* 11: SVCall */ + .word DebugMon_Handler /* 12: Debug monitor */ + .word 0 /* 13: Reserved */ + .word PendSV_Handler /* 14: PendSV */ + .word SysTick_Handler /* 15: SysTick */ + + /* IRQ 0-97 (98 entries covers both F4 82 and F7 98 IRQs) */ + .rept 98 + .word Default_Handler + .endr + + .size __vector_table, . - __vector_table + +/* ================================================================ + * Reset handler + * ================================================================ */ + .section .text, "ax" + .global Reset_Handler + .type Reset_Handler, %function + .thumb_func +Reset_Handler: + + /* -- Copy .got from flash (LMA) to RAM (VMA) -- */ + ldr r0, =_got_start + ldr r1, =_got_load + ldr r2, =_got_end + cmp r0, r1 + beq .Lgot_done + b .Lgot_check +.Lgot_copy: + ldm r1!, {r3} + stm r0!, {r3} +.Lgot_check: + cmp r0, r2 + blo .Lgot_copy +.Lgot_done: + + /* -- Copy .data from flash to RAM -- */ + ldr r0, =_data_start + ldr r1, =_data_load + ldr r2, =_data_end + b .Ldata_check +.Ldata_copy: + ldm r1!, {r3} + stm r0!, {r3} +.Ldata_check: + cmp r0, r2 + blo .Ldata_copy + + /* -- Zero .bss -- */ + ldr r0, =_bss_start + ldr r2, =_bss_end + movs r3, #0 + b .Lbss_check +.Lbss_zero: + stm r0!, {r3} +.Lbss_check: + cmp r0, r2 + blo .Lbss_zero + + /* -- Enable FPU (CP10 + CP11 full access) -- */ + ldr r0, =0xE000ED88 /* CPACR */ + ldr r1, [r0] + orr r1, r1, #(0xF << 20) + str r1, [r0] + dsb + isb + + /* -- Platform init (clock, GPIO, UART, cache) -- */ + bl sys_init + + /* -- Run .init_array (D module constructors) -- */ + ldr r4, =__init_array_start + ldr r5, =__init_array_end + b .Linit_check +.Linit_loop: + ldr r0, [r4] + blx r0 + adds r4, r4, #4 +.Linit_check: + cmp r4, r5 + blo .Linit_loop + + /* -- Enter main (should never return) -- */ + bl main + + /* If main returns, spin */ +.Lhalt: + wfi + b .Lhalt + + .size Reset_Handler, . - Reset_Handler + +/* ================================================================ + * Default exception/interrupt handlers (weak, overridable) + * ================================================================ */ + .thumb_func + .weak NMI_Handler + .type NMI_Handler, %function +NMI_Handler: + b . + + .thumb_func + .weak HardFault_Handler + .type HardFault_Handler, %function +HardFault_Handler: + b . + + .thumb_func + .weak MemManage_Handler + .type MemManage_Handler, %function +MemManage_Handler: + b . + + .thumb_func + .weak BusFault_Handler + .type BusFault_Handler, %function +BusFault_Handler: + b . + + .thumb_func + .weak UsageFault_Handler + .type UsageFault_Handler, %function +UsageFault_Handler: + b . + + .thumb_func + .weak SVCall_Handler + .type SVCall_Handler, %function +SVCall_Handler: + b . + + .thumb_func + .weak DebugMon_Handler + .type DebugMon_Handler, %function +DebugMon_Handler: + b . + + .thumb_func + .weak PendSV_Handler + .type PendSV_Handler, %function +PendSV_Handler: + b . + + .thumb_func + .weak SysTick_Handler + .type SysTick_Handler, %function +SysTick_Handler: + b . + + .thumb_func + .weak Default_Handler + .type Default_Handler, %function +Default_Handler: + b . diff --git a/src/sys/stm32/syscalls.d b/src/sys/stm32/syscalls.d new file mode 100644 index 0000000..ab4bba5 --- /dev/null +++ b/src/sys/stm32/syscalls.d @@ -0,0 +1,50 @@ +// STM32 newlib/picolibc syscall stubs +// +// Minimal stubs to satisfy picolibc's syscall requirements. +// Same pattern as RP2350 -- most are no-ops for baremetal. +module sys.stm32.syscalls; + +@nogc nothrow: + +private extern(C) extern const void* __heap_start; +private extern(C) extern const void* __heap_end; + +private __gshared void* _heap_ptr; + +extern(C) void* _sbrk(ptrdiff_t incr) +{ + if (_heap_ptr is null) + _heap_ptr = cast(void*)&__heap_start; + + void* prev = _heap_ptr; + void* next = _heap_ptr + incr; + + if (next > cast(void*)&__heap_end) + return cast(void*)-1; + + _heap_ptr = next; + return prev; +} + +extern(C) int _write(int fd, const void* buf, size_t count) +{ + import sys.stm32.uart : uart0_hw_puts; + if (fd == 1 || fd == 2) + uart0_hw_puts((cast(const(char)*)buf)[0 .. count]); + return cast(int)count; +} + +extern(C) int _read(int, void*, size_t) { return 0; } +extern(C) int _close(int) { return -1; } +extern(C) int _lseek(int, int, int) { return 0; } +extern(C) int _fstat(int, void*) { return 0; } +extern(C) int _isatty(int) { return 1; } +extern(C) void _exit(int) { while (true) {} } +extern(C) int _kill(int, int) { return -1; } +extern(C) int _getpid() { return 1; } + +// DWARF unwinder stubs -- ARM Cortex-M uses EHABI, not DWARF. +extern(C) void __register_frame_info(const void*, void*) {} +extern(C) size_t _Unwind_GetIPInfo(void*, int*) { return 0; } +extern(C) void _Unwind_SetGR(void*, int, size_t) {} +extern(C) void _Unwind_SetIP(void*, size_t) {} diff --git a/src/sys/stm32/timer.d b/src/sys/stm32/timer.d new file mode 100644 index 0000000..52a1a14 --- /dev/null +++ b/src/sys/stm32/timer.d @@ -0,0 +1,73 @@ +// STM32 timer driver +// +// Monotonic time via SysTick interpolation: the SysTick handler increments +// a tick counter, and mtime_read() combines the tick count with the current +// SysTick value for full 16 MHz resolution. +// +// SysTick keeps running during WFI (SLEEP mode) since AHB stays active, +// unlike DWT CYCCNT which stops on sleep. +module sys.stm32.timer; + +import core.volatile; + +@nogc nothrow: + +enum uint mtime_freq_hz = 16_000_000; +enum bool has_mtime = true; +enum bool has_rtc = false; +enum bool has_mcycle = false; +enum bool has_timer_stop = false; +enum bool has_wfi_sleep = false; + +// SysTick registers (ARM standard) +private enum ulong SYST_CSR = 0xE000E010; +private enum ulong SYST_RVR = 0xE000E014; +private enum ulong SYST_CVR = 0xE000E018; + +private enum uint CSR_ENABLE = 1 << 0; +private enum uint CSR_TICKINT = 1 << 1; +private enum uint CSR_CLKSOURCE = 1 << 2; + +// 20 Hz tick: reload = 16_000_000 / 20 - 1 = 799_999 +private enum uint SYSTICK_RELOAD = 799_999; + +alias TimerCallback = void function() @nogc nothrow; + +private __gshared TimerCallback tick_callback; +private __gshared uint tick_count; + +void timer_init(uint reload_value) +{ + volatileStore(cast(uint*)SYST_RVR, reload_value & 0x00FFFFFF); + volatileStore(cast(uint*)SYST_CVR, 0); + volatileStore(cast(uint*)SYST_CSR, CSR_ENABLE | CSR_TICKINT | CSR_CLKSOURCE); +} + +void timer_set_periodic(uint period_us, TimerCallback cb) +{ + tick_callback = cb; + timer_init(period_us * 16); +} + +// Full-resolution monotonic time by combining SysTick overflow count with +// the current down-counter value. Retry loop handles the race where +// SysTick fires between reading tick_count and CVR. +ulong mtime_read() +{ + uint t1, cvr, t2; + do + { + t1 = volatileLoad(&tick_count); + cvr = volatileLoad(cast(uint*)SYST_CVR); + t2 = volatileLoad(&tick_count); + } + while (t1 != t2); + return cast(ulong)t1 * (SYSTICK_RELOAD + 1) + (SYSTICK_RELOAD - cvr); +} + +extern(C) void SysTick_Handler() @nogc nothrow +{ + volatileStore(&tick_count, volatileLoad(&tick_count) + 1); + if (tick_callback !is null) + tick_callback(); +} diff --git a/src/sys/stm32/uart.d b/src/sys/stm32/uart.d new file mode 100644 index 0000000..dfe346f --- /dev/null +++ b/src/sys/stm32/uart.d @@ -0,0 +1,260 @@ +// STM32 UART driver +// +// Supports both F4 (legacy SR/DR register layout) and F7 (ISR/RDR/TDR). +// USART1-6 on F4, USART1-6 + UART7-8 on F7. +// +// Default console: USART1 on PA9 (TX) / PA10 (RX), configured in sys_init. +module sys.stm32.uart; + +import core.volatile; + +import sys.baremetal.uart : Parity, StopBits, UartConfig; + +nothrow @nogc: + +version (STM32F7) + enum uint num_uarts = 8; +else + enum uint num_uarts = 6; + +enum uint uart_clock_hz = 16_000_000; +enum bool has_irq_driven_uart = false; +enum bool has_dma_driven_uart = false; + +// F4 uses legacy register layout (SR/DR), F7 uses new layout (ISR/RDR/TDR). +// Register offsets and UE bit position differ; status bit positions are the same. +version (STM32F4) enum legacy_usart = true; +else enum legacy_usart = false; + +// Status bits (same positions in F4 SR and F7 ISR) +private enum +{ + ST_TXE = 1 << 7, + ST_RXNE = 1 << 5, + ST_TC = 1 << 6, + ST_ORE = 1 << 3, + ST_FE = 1 << 1, + ST_PE = 1 << 0, +} + +// CR1 bits shared between F4 and F7 +private enum +{ + CR1_TE = 1 << 3, + CR1_RE = 1 << 2, +} + +// F4: UE is bit 13 in CR1 at offset 0x0C +// F7: UE is bit 0 in CR1 at offset 0x00 +static if (legacy_usart) + private enum uint CR1_UE = 1 << 13; +else + private enum uint CR1_UE = 1 << 0; + +// Register offsets +static if (legacy_usart) +{ + private enum { SR = 0x00, DR = 0x04, BRR = 0x08, CR1 = 0x0C, CR2 = 0x10, CR3 = 0x14 } +} +else +{ + private enum { CR1 = 0x00, CR2 = 0x04, CR3 = 0x08, BRR = 0x0C, ISR = 0x1C, ICR = 0x20, RDR = 0x24, TDR = 0x28 } +} + +// USART base addresses (F4 and F7 share the same addresses for USART1-6) +private ulong uart_base(uint id) +{ + immutable ulong[8] bases = [ + 0x40011000, // USART1 (APB2) + 0x40004400, // USART2 (APB1) + 0x40004800, // USART3 (APB1) + 0x40004C00, // UART4 (APB1) + 0x40005000, // UART5 (APB1) + 0x40011400, // USART6 (APB2) + 0x40007800, // UART7 (APB1, F7 only) + 0x40007C00, // UART8 (APB1, F7 only) + ]; + return bases[id]; +} + +private void reg_write(ulong base, uint offset, uint val) +{ + volatileStore(cast(uint*)(base + offset), val); +} + +private uint reg_read(ulong base, uint offset) +{ + return volatileLoad(cast(uint*)(base + offset)); +} + +private uint read_status(ulong base) +{ + static if (legacy_usart) + return reg_read(base, SR); + else + return reg_read(base, ISR); +} + +private ubyte read_data(ulong base) +{ + static if (legacy_usart) + return cast(ubyte)(reg_read(base, DR) & 0xFF); + else + return cast(ubyte)(reg_read(base, RDR) & 0xFF); +} + +private void write_data(ulong base, ubyte val) +{ + static if (legacy_usart) + reg_write(base, DR, val); + else + reg_write(base, TDR, val); +} + +bool uart_hw_init(uint id, UartConfig cfg) +{ + immutable base = uart_base(id); + + // Disable UART while configuring + reg_write(base, CR1, 0); + + // Baud rate: BRR = fck / baud (16x oversampling) + immutable uint brr = uart_clock_hz / cfg.baud_rate; + reg_write(base, BRR, brr); + + // Line control: word length, parity, stop bits + uint cr1 = CR1_UE | CR1_TE | CR1_RE; + uint cr2 = 0; + + if (cfg.parity != Parity.none) + { + static if (legacy_usart) + cr1 |= 1 << 10; // PCE: parity enable + else + cr1 |= 1 << 10; // PCE: parity enable + if (cfg.parity == Parity.odd) + { + static if (legacy_usart) + cr1 |= 1 << 9; // PS: odd parity + else + cr1 |= 1 << 9; // PS: odd parity + } + // With parity, need M=1 (9 data bits) to keep 8 data + 1 parity + static if (legacy_usart) + cr1 |= 1 << 12; // M: word length 9 + else + cr1 |= 1 << 12; // M0: word length 9 + } + + if (cfg.stop_bits == StopBits.two) + cr2 |= 2 << 12; // STOP: 2 stop bits + else if (cfg.stop_bits == StopBits.half) + cr2 |= 1 << 12; // STOP: 0.5 stop bits + else if (cfg.stop_bits == StopBits.one_point_five) + cr2 |= 3 << 12; // STOP: 1.5 stop bits + + reg_write(base, CR2, cr2); + reg_write(base, CR3, 0); + reg_write(base, CR1, cr1); + + return true; +} + +bool uart_hw_open(uint id, UartConfig cfg) +{ + return uart_hw_init(id, cfg); +} + +void uart_hw_close(uint id) +{ + reg_write(uart_base(id), CR1, 0); +} + +ptrdiff_t uart_hw_read(uint id, void[] buffer) +{ + immutable base = uart_base(id); + auto buf = cast(ubyte[])buffer; + ptrdiff_t n = 0; + while (n < buf.length) + { + if (!(read_status(base) & ST_RXNE)) + break; + buf[n] = read_data(base); + ++n; + } + return n; +} + +ptrdiff_t uart_hw_write(uint id, const(void)[] data) +{ + immutable base = uart_base(id); + auto buf = cast(const(ubyte)[])data; + ptrdiff_t n = 0; + while (n < buf.length) + { + if (!(read_status(base) & ST_TXE)) + break; + write_data(base, buf[n]); + ++n; + } + return n; +} + +void uart_hw_poll(uint id) {} + +bool uart_hw_check_errors(uint id) +{ + immutable base = uart_base(id); + immutable st = read_status(base); + if (st & (ST_ORE | ST_FE | ST_PE)) + { + static if (legacy_usart) + { + // F4: read SR then DR to clear error flags + reg_read(base, DR); + } + else + { + // F7: write ICR to clear error flags + reg_write(base, ICR, ST_ORE | ST_FE | ST_PE); + } + return true; + } + return false; +} + +ptrdiff_t uart_hw_rx_pending(uint id) +{ + if (read_status(uart_base(id)) & ST_RXNE) + return 1; + return 0; +} + +ptrdiff_t uart_hw_flush(uint id) +{ + immutable base = uart_base(id); + while (!(read_status(base) & ST_TC)) + {} + return 0; +} + +// Blocking puts for early boot (before the serial stream is up) +void uart0_hw_puts(const(char)[] s) +{ + enum ulong base = 0x40011000; // USART1 + foreach (c; s) + { + static if (legacy_usart) + { + while (!(volatileLoad(cast(uint*)(base + SR)) & ST_TXE)) + {} + volatileStore(cast(uint*)(base + DR), cast(uint)c); + } + else + { + while (!(volatileLoad(cast(uint*)(base + ISR)) & ST_TXE)) + {} + volatileStore(cast(uint*)(base + TDR), cast(uint)c); + } + } +} diff --git a/src/urt/digest/sha.d b/src/urt/digest/sha.d index ca36a8c..53c619f 100644 --- a/src/urt/digest/sha.d +++ b/src/urt/digest/sha.d @@ -2,90 +2,24 @@ module urt.digest.sha; import urt.endian; -nothrow @nogc: // pure - - version (Espressif) { - // Uses the hardware SHA accelerator via mask ROM functions. - // The ROM API is consistent across S2/S3/C3/C6/H2 but differs on original ESP32. - version (ESP32) { static assert(false, "Original ESP32 has a different SHA ROM API; not yet supported"); } - - private extern(C) nothrow @nogc - { - void ets_sha_enable(); - void ets_sha_disable(); - int ets_sha_init(SHA_CTX* ctx, SHA_TYPE type); - int ets_sha_starts(SHA_CTX* ctx, ushort sha512_t); - void ets_sha_update(SHA_CTX* ctx, const ubyte* input, uint input_bytes, bool update_ctx); - int ets_sha_finish(SHA_CTX* ctx, ubyte* output); - } - - private enum SHA_TYPE : int { SHA1 = 0, SHA2_224, SHA2_256 } + version (ESP32) {} else + version = Espressif_Modern; +} - private struct SHA_CTX - { - bool start; - bool in_hardware; - SHA_TYPE type; - uint[16] state; - ubyte[128] buffer; - uint[4] total_bits; - } +nothrow @nogc: // pure - struct SHA1Context - { - enum DigestBits = 160; - enum DigestLen = DigestBits / 8; - private SHA_CTX ctx; - } - struct SHA224Context - { - enum DigestBits = 224; - enum DigestLen = DigestBits / 8; - private SHA_CTX ctx; - } +struct SHA1Context +{ + enum DigestBits = 160; + enum DigestLen = DigestBits / 8; - struct SHA256Context - { - enum DigestBits = 256; - enum DigestLen = DigestBits / 8; + version (Espressif) private SHA_CTX ctx; - } - - void sha_init(Context)(ref Context ctx) - { - ets_sha_enable(); - static if (is(Context == SHA1Context)) - ets_sha_init(&ctx.ctx, SHA_TYPE.SHA1); - else static if (is(Context == SHA224Context)) - ets_sha_init(&ctx.ctx, SHA_TYPE.SHA2_224); - else - ets_sha_init(&ctx.ctx, SHA_TYPE.SHA2_256); - ets_sha_starts(&ctx.ctx, 0); - } - - void sha_update(Context)(ref Context ctx, const void[] input) - { - ets_sha_update(&ctx.ctx, cast(const ubyte*)input.ptr, cast(uint)input.length, true); - } - - ubyte[Context.DigestLen] sha_finalise(Context)(ref Context ctx) - { - ubyte[Context.DigestLen] digest; - ets_sha_finish(&ctx.ctx, digest.ptr); - ets_sha_disable(); - return digest; - } -} -else -{ - struct SHA1Context + else { - enum DigestBits = 160; - - enum DigestLen = DigestBits / 8; enum DigestElements = DigestBits / 32; ubyte[64] data; @@ -99,12 +33,19 @@ else __gshared immutable uint[4] K = [ 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 ]; } +} - struct SHA224Context - { - enum DigestBits = 224; +struct SHA224Context +{ + enum DigestBits = 224; + enum DigestLen = DigestBits / 8; - enum DigestLen = DigestBits / 8; + // TODO: ESP32 hardware SHA-224 should be possible by writing the SHA-224 IV to SHA_TEXT_BASE, + // triggering SHA_256_LOAD_REG, then running as SHA2_256 with truncated output. + version (Espressif_Modern) + private SHA_CTX ctx; + else + { enum DigestElements = DigestBits / 32; enum StateElements = 256 / 32; @@ -118,12 +59,17 @@ else enum uint[StateElements] initState = [ 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 ]; } +} - struct SHA256Context - { - enum DigestBits = 256; +struct SHA256Context +{ + enum DigestBits = 256; + enum DigestLen = DigestBits / 8; - enum DigestLen = DigestBits / 8; + version (Espressif) + private SHA_CTX ctx; + else + { enum DigestElements = DigestBits / 32; enum StateElements = DigestBits / 32; @@ -148,15 +94,53 @@ else 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 ]; } +} - void sha_init(Context)(ref Context ctx) +void sha_init(Context)(ref Context ctx) +{ + static if (esp_hardware) + { + version (ESP32) + { + ets_sha_enable(); + ets_sha_init(&ctx.ctx); + } + else + { + ets_sha_enable(); + static if (is(Context == SHA1Context)) + ets_sha_init(&ctx.ctx, SHA_TYPE.SHA1); + else static if (is(Context == SHA224Context)) + ets_sha_init(&ctx.ctx, SHA_TYPE.SHA2_224); + else + ets_sha_init(&ctx.ctx, SHA_TYPE.SHA2_256); + ets_sha_starts(&ctx.ctx, 0); + } + } + else { ctx.datalen = 0; ctx.bitlen = 0; ctx.state = Context.initState; } +} - void sha_update(Context)(ref Context ctx, const void[] input) +void sha_update(Context)(ref Context ctx, const void[] input) +{ + static if (esp_hardware) + { + version (ESP32) + { + static if (is(Context == SHA1Context)) + enum sha_type = SHA_TYPE.SHA1; + else + enum sha_type = SHA_TYPE.SHA2_256; + ets_sha_update(&ctx.ctx, sha_type, cast(const ubyte*)input.ptr, input.length * 8); + } + else + ets_sha_update(&ctx.ctx, cast(const ubyte*)input.ptr, cast(uint)input.length, true); + } + else { const(ubyte)[] data = cast(ubyte[])input; @@ -176,8 +160,32 @@ else Context.transform(ctx, ctx.data); } } +} - ubyte[Context.DigestLen] sha_finalise(Context)(ref Context ctx) +ubyte[Context.DigestLen] sha_finalise(Context)(ref Context ctx) +{ + static if (esp_hardware) + { + version (ESP32) + { + static if (is(Context == SHA1Context)) + enum sha_type = SHA_TYPE.SHA1; + else + enum sha_type = SHA_TYPE.SHA2_256; + ubyte[Context.DigestLen] digest; + ets_sha_finish(&ctx.ctx, sha_type, digest.ptr); + ets_sha_disable(); + return digest; + } + else + { + ubyte[Context.DigestLen] digest; + ets_sha_finish(&ctx.ctx, digest.ptr); + ets_sha_disable(); + return digest; + } + } + else { uint i = ctx.datalen; @@ -306,11 +314,18 @@ unittest private: -uint ROTLEFT(uint a, uint b) => (a << b) | (a >> (32 - b)); -uint ROTRIGHT(uint a, uint b) => (a >> b) | (a << (32 - b)); +version (Espressif_Modern) + enum esp_hardware = true; +else version (Espressif) + enum esp_hardware = !is(Context == SHA224Context); +else + enum esp_hardware = false; +version (Espressif) {} else void sha1_transform(Context)(ref Context ctx, const ubyte[] data) { + static uint ROTLEFT(uint a, uint b) => (a << b) | (a >> (32 - b)); + uint a, b, c, d, e, i, j, t; uint[80] m = void; @@ -372,8 +387,11 @@ void sha1_transform(Context)(ref Context ctx, const ubyte[] data) ctx.state[4] += e; } +version (Espressif_Modern) {} else void sha256_transform(Context)(ref Context ctx, const ubyte[] data) { + static uint ROTRIGHT(uint a, uint b) => (a >> b) | (a << (32 - b)); + static uint CH(uint x, uint y, uint z) => (x & y) ^ (~x & z); static uint MAJ(uint x, uint y, uint z) => (x & y) ^ (x & z) ^ (y & z); static uint EP0(uint x) => ROTRIGHT(x, 2) ^ ROTRIGHT(x, 13) ^ ROTRIGHT(x, 22); @@ -421,3 +439,56 @@ void sha256_transform(Context)(ref Context ctx, const ubyte[] data) ctx.state[6] += g; ctx.state[7] += h; } + + +// ESP32 (original) has a different ROM API from all later chips: +// - ets_sha_init takes no type; type passed to update/finish instead +// - ets_sha_update size is in bits, not bytes; no ets_sha_starts +// - smaller SHA_CTX struct +// - no SHA224 in ROM (only SHA1=0, SHA2_256=1) +version (Espressif): + +private extern(C) nothrow @nogc +{ + void ets_sha_enable(); + void ets_sha_disable(); + + version (Espressif_Modern) + { + int ets_sha_init(SHA_CTX* ctx, SHA_TYPE type); + int ets_sha_starts(SHA_CTX* ctx, ushort sha512_t); + void ets_sha_update(SHA_CTX* ctx, const ubyte* input, uint input_bytes, bool update_ctx); + int ets_sha_finish(SHA_CTX* ctx, ubyte* output); + } + else + { + void ets_sha_init(SHA_CTX* ctx); + void ets_sha_update(SHA_CTX* ctx, SHA_TYPE type, const ubyte* input, size_t input_bits); + void ets_sha_finish(SHA_CTX* ctx, SHA_TYPE type, ubyte* output); + } +} + +version (Espressif_Modern) +{ + private enum SHA_TYPE : int { SHA1 = 0, SHA2_224, SHA2_256 } + + private struct SHA_CTX + { + bool start; + bool in_hardware; + SHA_TYPE type; + uint[16] state; + ubyte[128] buffer; + uint[4] total_bits; + } +} +else +{ + private enum SHA_TYPE : int { SHA1 = 0, SHA2_256 } + + private struct SHA_CTX + { + bool start; + uint[4] total_input_bits; + } +} diff --git a/src/urt/exception.d b/src/urt/exception.d index a5d251c..d1ebc3f 100644 --- a/src/urt/exception.d +++ b/src/urt/exception.d @@ -27,10 +27,9 @@ void urt_assert(string file, size_t line, string msg) nothrow @nogc if (msg.length == 0) msg = "Assertion failed"; - version (Bouffalo) + version (BareMetal) { - version (BL808) import sys.bl808.uart : uart0_puts; - else import sys.bl618.uart : uart0_puts; + import sys.baremetal.uart : uart0_puts; import urt.mem.temp : tconcat; uart0_puts(tconcat("\n*** ASSERT: ", msg, " at ", file, ':', line, '\n')); while (true) {} diff --git a/src/urt/internal/exception.d b/src/urt/internal/exception.d index cf62a6c..cc51feb 100644 --- a/src/urt/internal/exception.d +++ b/src/urt/internal/exception.d @@ -3560,7 +3560,9 @@ _Unwind_Reason_Code dwarfeh_personality_common(_Unwind_Action actions, _Unwind_E // FreeStanding ARM (bare-metal) uses DWARF EH, not ARM EHABI, so use the standard personality. version (ARM) { - version (FreeStanding) + version (Beken) + enum UseArmEhabi = true; + else version (FreeStanding) enum UseArmEhabi = false; else version (LDC) enum UseArmEhabi = true; diff --git a/src/urt/internal/sys/posix/package.d b/src/urt/internal/sys/posix/package.d index e12cda2..c9b2ef8 100644 --- a/src/urt/internal/sys/posix/package.d +++ b/src/urt/internal/sys/posix/package.d @@ -221,7 +221,8 @@ version (OldStatLayout) } } -bool S_ISREG(mode_t mode) { return (mode & 0xF000) == 0x8000; } +bool S_ISREG(mode_t mode) + => (mode & 0xF000) == 0x8000; version (X86) { diff --git a/src/urt/io.d b/src/urt/io.d index 154da0c..add2de3 100644 --- a/src/urt/io.d +++ b/src/urt/io.d @@ -25,7 +25,7 @@ template write_to(WriteTarget target, bool newline = false) } else version (FreeStanding) { - import sys.bl808.uart : uart0_puts; + import sys.baremetal.uart : uart0_puts; uart0_puts(str); static if (newline) uart0_puts("\n"); diff --git a/src/urt/package.d b/src/urt/package.d index a4f8101..67b1000 100644 --- a/src/urt/package.d +++ b/src/urt/package.d @@ -46,6 +46,9 @@ extern(C) int main(int argc, char** argv) nothrow @nogc @trusted import urt.time : init_clock; init_clock(); + import sys.baremetal.uart : uart_init, uart_deinit; + uart_init(); + import urt.rand; init_rand(); @@ -105,6 +108,8 @@ extern(C) int main(int argc, char** argv) nothrow @nogc @trusted run_module_dtors(modules); + uart_deinit(); + deinit_string_heap(); return result; diff --git a/src/urt/platform.d b/src/urt/platform.d index 917dbc2..23454bf 100644 --- a/src/urt/platform.d +++ b/src/urt/platform.d @@ -17,15 +17,31 @@ version (visionOS) version = Darwin; -version (Windows) - enum string Platform = "Windows"; -else version (linux) - enum string Platform = "Linux"; -else version (Darwin) - enum string Platform = "Darwin"; -else version (FreeBSD) - enum string Platform = "FreeBSD"; -else version (FreeStanding) - enum string Platform = "bare-metal"; -else - static assert(0, "Unsupported platform"); +// Platform identity -- specific chip name where known, generic fallback otherwise + +version (ESP8266) enum string Platform = "ESP8266"; +else version (ESP32) enum string Platform = "ESP32"; +else version (ESP32_S2) enum string Platform = "ESP32-S2"; +else version (ESP32_S3) enum string Platform = "ESP32-S3"; +else version (ESP32_C2) enum string Platform = "ESP32-C2"; +else version (ESP32_C3) enum string Platform = "ESP32-C3"; +else version (ESP32_C5) enum string Platform = "ESP32-C5"; +else version (ESP32_C6) enum string Platform = "ESP32-C6"; +else version (ESP32_H2) enum string Platform = "ESP32-H2"; +else version (ESP32_P4) enum string Platform = "ESP32-P4"; +else version (BL808) enum string Platform = "BL808"; +else version (BL808_M0) enum string Platform = "BL808-M0"; +else version (BL618) enum string Platform = "BL618"; +else version (BK7231N) enum string Platform = "BK7231N"; +else version (BK7231T) enum string Platform = "BK7231T"; +else version (RP2350) enum string Platform = "RP2350"; +else version (STM32F4) enum string Platform = "STM32F4"; +else version (STM32F7) enum string Platform = "STM32F7"; +else version (Windows) enum string Platform = "Windows"; +else version (linux) enum string Platform = "Linux"; +else version (Darwin) enum string Platform = "macOS"; +else version (FreeBSD) enum string Platform = "FreeBSD"; +else version (FreeRTOS) enum string Platform = "FreeRTOS"; +else version (BareMetal) enum string Platform = "bare-metal"; +else version (FreeStanding) enum string Platform = "bare-metal"; +else enum string Platform = "unknown"; diff --git a/src/urt/socket.d b/src/urt/socket.d index b731b58..6e2a218 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -6,7 +6,7 @@ public import urt.mem; public import urt.result; public import urt.time; -version (BL808) +version (BareMetal) version = SocketCallbacks; version (SocketCallbacks) diff --git a/src/urt/system.d b/src/urt/system.d index 2e0e302..f3febc2 100644 --- a/src/urt/system.d +++ b/src/urt/system.d @@ -40,25 +40,28 @@ void sleep(Duration duration) import urt.internal.sys.windows.winbase : Sleep; Sleep(cast(uint)duration.as!"msecs"); } - else version (BL808) + else version (Embedded) { - import sys.bl808.irq : IrqClass, enable_irq, disable_irq, wait_for_interrupt; - import sys.bl808.timer : mtime_read, mtimecmp_write_oneshot; - - ulong deadline = mtime_read() + duration.as!"usecs"; - mtimecmp_write_oneshot(deadline); - auto was_enabled = enable_irq(IrqClass.timer); - while (mtime_read() < deadline) - wait_for_interrupt(); - if (!was_enabled) - disable_irq(IrqClass.timer); - } - else version (BL618) - { - // TODO: use mtimecmp + WFI once IRQ driver is implemented - import sys.bl618.timer : mtime_read; - ulong deadline = mtime_read() + duration.as!"usecs"; - while (mtime_read() < deadline) {} + import sys.baremetal.timer; + import sys.baremetal.irq; + + static if (has_mtime) + { + ulong deadline = mtime_read() + duration.as!"usecs"; + static if (has_wfi_sleep) + { + mtimecmp_write_oneshot(deadline); + auto was_enabled = enable_irq(IrqClass.timer); + while (mtime_read() < deadline) + wait_for_interrupt(); + if (!was_enabled) + disable_irq(IrqClass.timer); + } + else + { + while (mtime_read() < deadline) {} + } + } } else { @@ -193,14 +196,17 @@ unittest info.reserved_memory / 1024, info.avail_memory / 1024, info.peak_memory / 1024); - version (BL808) + version (Embedded) { - import sys.bl808.irq : irq_count, irq_histogram; - writelnf(" IRQ total: {0}", irq_count); - foreach (i; 0 .. irq_histogram.length) + import sys.baremetal.irq; + static if (has_irq_diagnostics) { - if (irq_histogram[i] > 0) - writelnf(" IRQ {0}: {1}", i, irq_histogram[i]); + writelnf(" IRQ total: {0}", irq_count); + foreach (i; 0 .. irq_histogram.length) + { + if (irq_histogram[i] > 0) + writelnf(" IRQ {0}: {1}", i, irq_histogram[i]); + } } } } diff --git a/src/urt/time.d b/src/urt/time.d index c9461e8..5e9f2b1 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -12,14 +12,9 @@ else version (Posix) { import urt.internal.sys.posix; } -else version (Bouffalo) +else version (Embedded) { - version (BL808) import sys.bl808.timer; - else import sys.bl618.timer; -} -else version (Espressif) -{ - extern(C) long esp_timer_get_time() nothrow @nogc; + import sys.baremetal.timer; } nothrow @nogc: @@ -725,17 +720,12 @@ MonoTime getTime() clock_gettime(CLOCK_MONOTONIC, &ts); return MonoTime(ts.tv_sec * 1_000_000_000 + ts.tv_nsec); } - else version (Bouffalo) - { - return MonoTime(mtime_read()); - } - else version (Espressif) - { - return MonoTime(esp_timer_get_time()); - } - else version (FreeStanding) + else version (Embedded) { - assert(0, "getTime: not yet implemented for bare-metal"); + static if (has_mtime) + return MonoTime(mtime_read()); + else + static assert(false, "getTime: no monotonic timer for this platform"); } else { @@ -757,17 +747,12 @@ SysTime getSysTime() clock_gettime(CLOCK_REALTIME, &ts); return SysTime(ts.tv_sec * 1_000_000_000 + ts.tv_nsec); } - else version (Bouffalo) + else version (Embedded) { - return SysTime(mtime_read() + sys_time_offset); - } - else version (Espressif) - { - return SysTime(esp_timer_get_time() + sys_time_offset); - } - else version (FreeStanding) - { - assert(0, "getSysTime: not yet implemented for bare-metal"); + static if (has_mtime) + return SysTime(mtime_read() + sys_time_offset); + else + static assert(false, "getSysTime: no monotonic timer for this platform"); } else { @@ -804,12 +789,8 @@ ulong unixTimeNs(SysTime t) pure return (t.ticks - unix_epoch_as_filetime) * 100UL; else version (Posix) return t.ticks; - else version (Bouffalo) - return t.ticks * nsec_multiplier; - else version (Espressif) + else version (Embedded) return t.ticks * nsec_multiplier; - else version (FreeStanding) - return t.ticks; else static assert(false, "TODO"); } @@ -820,12 +801,8 @@ SysTime from_unix_time_ns(ulong ns) pure return SysTime(ns / 100UL + unix_epoch_as_filetime); else version (Posix) return SysTime(ns); - else version (Bouffalo) - return SysTime(ns / nsec_multiplier); - else version (Espressif) + else version (Embedded) return SysTime(ns / nsec_multiplier); - else version (FreeStanding) - return SysTime(ns); else static assert(false, "TODO"); } @@ -859,20 +836,18 @@ void set_utc_time(ulong unix_ns) ts.tv_nsec = cast(uint)(unix_ns % 1_000_000_000); clock_settime(CLOCK_REALTIME, &ts); } - else version (BL808) - { - auto p = hbn_persist(); - ulong mtime_ticks = unix_ns / nsec_multiplier; - ulong sec = mtime_ticks / mtime_freq_hz; - ulong frac = mtime_ticks % mtime_freq_hz; - ulong hbn_ticks = sec * rtc_freq_hz + frac * rtc_freq_hz / mtime_freq_hz; - p.utc_offset = cast(long)hbn_ticks - cast(long)rtc_read(); - p.magic = HbnPersist.HBN_MAGIC; - } - else version (Espressif) + else version (Embedded) { - // Offset stored in RAM; lost on reboot. - // TODO: persist to NVS or RTC memory for deep-sleep survival + static if (has_rtc) + { + auto p = hbn_persist(); + ulong mtime_ticks = unix_ns / nsec_multiplier; + ulong sec = mtime_ticks / mtime_freq_hz; + ulong frac = mtime_ticks % mtime_freq_hz; + ulong hbn_ticks = sec * rtc_freq_hz + frac * rtc_freq_hz / mtime_freq_hz; + p.utc_offset = cast(long)hbn_ticks - cast(long)rtc_read(); + p.magic = HbnPersist.HBN_MAGIC; + } } } @@ -896,21 +871,11 @@ else version (Posix) enum uint ticks_per_second = 1_000_000_000; enum uint nsec_multiplier = 1; } -else version (Bouffalo) +else version (Embedded) { enum uint ticks_per_second = mtime_freq_hz; enum uint nsec_multiplier = 1_000_000_000 / mtime_freq_hz; } -else version (Espressif) -{ - enum uint ticks_per_second = 1_000_000; - enum uint nsec_multiplier = 1_000; -} -else version (FreeStanding) -{ - enum uint ticks_per_second = 1_000_000_000; - enum uint nsec_multiplier = 1; -} __gshared immutable ulong sys_time_offset; __gshared bool has_wall_time; @@ -958,20 +923,19 @@ package(urt) void init_clock() cast()sys_time_offset = boot_time; has_wall_time = true; } - else version (BL808) - { - rtc_enable(); - recalc_sys_time_offset(); - } - else version (Espressif) - { - // No wall-clock reference until set_utc_time() is called (e.g. via NTP/SNTP) - cast()sys_time_offset = 0; - } - else version (FreeStanding) + else version (Embedded) { - // Bare-metal: no wall-clock reference until set_utc_time() is called. - cast()sys_time_offset = 0; + timer_init(); + + static if (has_rtc) + { + rtc_enable(); + recalc_sys_time_offset(); + } + else + { + cast()sys_time_offset = 0; + } } else static assert(false, "TODO"); @@ -1213,12 +1177,10 @@ ulong datetime_to_unix_ns(DateTime dt) pure return total_sec * 1_000_000_000 + dt.ns; } -version (BL808) +version (Embedded) static if (has_rtc) { __gshared ulong last_hbn; - /// call periodically to correct HBN drift against mtime - /// also detects 40-bit HBN counter wrap (~388 days) and compensate void correct_drift() { auto p = hbn_persist(); @@ -1237,7 +1199,7 @@ version (BL808) // What does HBN + offset think it is (converted to mtime ticks)? ulong hbn_total = now_hbn + p.utc_offset; ulong sys_hbn = hbn_total / rtc_freq_hz * mtime_freq_hz - + hbn_total % rtc_freq_hz * mtime_freq_hz / rtc_freq_hz; + + hbn_total % rtc_freq_hz * mtime_freq_hz / rtc_freq_hz; // difference is accumulated drift; fold into utc_offset long drift_mtime = sys_mtime - sys_hbn; From bb9d04b350ae930fcf06f5a91d8c219ff370972f Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sat, 11 Apr 2026 00:42:02 +1000 Subject: [PATCH 121/138] Actually do atomic ops. --- src/urt/atomic.d | 868 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 815 insertions(+), 53 deletions(-) diff --git a/src/urt/atomic.d b/src/urt/atomic.d index a93cc45..d29b495 100644 --- a/src/urt/atomic.d +++ b/src/urt/atomic.d @@ -1,9 +1,5 @@ module urt.atomic; -// TODO: these are all just stubs, but we can flesh it out as we need it... - -nothrow @nogc @safe: - enum MemoryOrder { relaxed = 0, @@ -15,7 +11,6 @@ enum MemoryOrder seq = 5, } - // DMD lowers to these names... alias atomicLoad = atomic_load; alias atomicStore = atomic_store; @@ -25,69 +20,730 @@ alias atomicFetchAdd = atomic_fetch_add; alias atomicFetchSub = atomic_fetch_sub; -T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(ref const T val) pure @trusted +version (LDC) { - return val; -} + // ----------------------------------------------------------------------- + // LDC: LLVM intrinsics — architecture-generic + // ----------------------------------------------------------------------- -// Overload for shared -TailShared!T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(auto ref shared const T val) pure @trusted -{ - return *cast(TailShared!T*)&val; -} + nothrow @nogc @safe: + pragma(inline, true): -void atomic_store(MemoryOrder ms = MemoryOrder.seq, T, V)(ref shared T val, V newval) pure @trusted - if (__traits(compiles, { *cast(T*)&val = newval; })) -{ - *cast(T*)&val = newval; -} + T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(ref const T val) pure @trusted + if (!is(T == shared)) + { + alias A = _AtomicType!T; + A result = llvm_atomic_load!A(cast(shared A*)&val, _ordering!ms); + return *cast(inout(T)*)&result; + } -TailShared!T atomic_op(string op, T, V1)(ref shared T val, V1 mod) pure @trusted - if (__traits(compiles, mixin("*cast(T*)&val" ~ op ~ "mod"))) -{ - auto ptr = cast(T*)&val; - mixin("*ptr " ~ op ~ " mod;"); - return *cast(TailShared!T*)ptr; -} + TailShared!T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(auto ref shared const T val) pure @trusted + { + alias A = _AtomicType!T; + A result = llvm_atomic_load!A(cast(shared A*)&val, _ordering!ms); + return *cast(TailShared!T*)&result; + } -bool cas(T, V1, V2)(shared(T)* here, V1 ifThis, V2 writeThis) pure @trusted -{ - auto ptr = cast(T*)here; - if (*ptr == ifThis) + void atomic_store(MemoryOrder ms = MemoryOrder.seq, T, V)(ref shared T val, V newval) pure @trusted + if (__traits(compiles, { *cast(T*)&val = newval; })) { - *ptr = writeThis; - return true; + alias A = _AtomicType!T; + T tmp = newval; + llvm_atomic_store!A(*cast(A*)&tmp, cast(shared A*)&val, _ordering!ms); } - return false; -} -T atomic_exchange(MemoryOrder ms = MemoryOrder.seq, T, V)(shared(T)* here, V exchangeWith) pure @trusted -{ - auto ptr = cast(T*)here; - T old = *ptr; - *ptr = exchangeWith; - return old; -} + TailShared!T atomic_op(string op, T, V1)(ref shared T val, V1 mod) pure @trusted + if (__traits(compiles, mixin("*cast(T*)&val" ~ op ~ "mod"))) + { + // use LLVM atomic RMW where possible + static if (__traits(isIntegral, T) && T.sizeof <= size_t.sizeof) + { + alias A = _AtomicType!T; + static if (op == "+=") + { + auto old = llvm_atomic_rmw_add!A(cast(shared A*)&val, cast(A)mod, _ordering!(MemoryOrder.seq)); + auto result = cast(T)(old + cast(A)mod); + return *cast(TailShared!T*)&result; + } + else static if (op == "-=") + { + auto old = llvm_atomic_rmw_sub!A(cast(shared A*)&val, cast(A)mod, _ordering!(MemoryOrder.seq)); + auto result = cast(T)(old - cast(A)mod); + return *cast(TailShared!T*)&result; + } + else + { + // CAS loop for other ops + return _cas_op_loop!(op, T, V1)(val, mod); + } + } + else + return _cas_op_loop!(op, T, V1)(val, mod); + } + + bool cas(T, V1, V2)(shared(T)* here, V1 ifThis, V2 writeThis) pure @trusted + { + alias A = _AtomicType!T; + T cmp = cast(T)ifThis; + T desired = cast(T)writeThis; + auto result = llvm_atomic_cmp_xchg!A(cast(shared A*)here, *cast(A*)&cmp, *cast(A*)&desired, + _ordering!(MemoryOrder.seq), _ordering!(MemoryOrder.seq), false); + return result.exchanged; + } + + T atomic_exchange(MemoryOrder ms = MemoryOrder.seq, T, V)(shared(T)* here, V exchangeWith) pure @trusted + { + alias A = _AtomicType!T; + T tmp = cast(T)exchangeWith; + A result = llvm_atomic_rmw_xchg!A(cast(shared A*)here, *cast(A*)&tmp, _ordering!ms); + return *cast(T*)&result; + } + + T atomic_fetch_add(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) + { + alias A = _AtomicType!T; + return cast(T)llvm_atomic_rmw_add!A(cast(shared A*)&val, cast(A)mod, _ordering!ms); + } -T atomic_fetch_add(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted - if (__traits(isIntegral, T)) + T atomic_fetch_sub(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) + { + alias A = _AtomicType!T; + return cast(T)llvm_atomic_rmw_sub!A(cast(shared A*)&val, cast(A)mod, _ordering!ms); + } + + void atomicFence(MemoryOrder ms = MemoryOrder.seq)() pure @trusted + { + llvm_memory_fence(_ordering!ms); + } + + void pause() pure @trusted + { + version (X86) + enum inst = "pause"; + else version (X86_64) + enum inst = "pause"; + else version (ARM) + { + static if (__traits(targetHasFeature, "v6k")) + enum inst = "yield"; + else + enum inst = null; + } + else version (AArch64) + enum inst = "yield"; + else + enum inst = null; + + static if (inst !is null) + asm pure nothrow @nogc @trusted { (inst); } + } + + // --- LDC private helpers --- + + private TailShared!T _cas_op_loop(string op, T, V1)(ref shared T val, V1 mod) pure @trusted + { + alias A = _AtomicType!T; + A current = llvm_atomic_load!A(cast(shared A*)&val, _ordering!(MemoryOrder.seq)); + while (true) + { + auto tmp = *cast(T*)¤t; + mixin("tmp " ~ op ~ " mod;"); + auto result = llvm_atomic_cmp_xchg!A(cast(shared A*)&val, current, *cast(A*)&tmp, + _ordering!(MemoryOrder.seq), _ordering!(MemoryOrder.seq), false); + if (result.exchanged) + return *cast(TailShared!T*)&tmp; + current = result.previousValue; + } + } + + private template _ordering(MemoryOrder ms) + { + static if (ms == MemoryOrder.acquire) + enum _ordering = AtomicOrdering.Acquire; + else static if (ms == MemoryOrder.release) + enum _ordering = AtomicOrdering.Release; + else static if (ms == MemoryOrder.acq_rel) + enum _ordering = AtomicOrdering.AcquireRelease; + else static if (ms == MemoryOrder.seq) + enum _ordering = AtomicOrdering.SequentiallyConsistent; + else // raw/relaxed/consume + enum _ordering = AtomicOrdering.Monotonic; + } + + private template _AtomicType(T) + { + static if (T.sizeof == ubyte.sizeof) + alias _AtomicType = ubyte; + else static if (T.sizeof == ushort.sizeof) + alias _AtomicType = ushort; + else static if (T.sizeof == uint.sizeof) + alias _AtomicType = uint; + else static if (T.sizeof == ulong.sizeof) + alias _AtomicType = ulong; + else + static assert(false, "Cannot atomically load/store type of size " ~ T.sizeof.stringof); + } + + // LLVM atomic intrinsic declarations + private: + + enum AtomicOrdering + { + Monotonic = 2, + Acquire = 4, + Release = 5, + AcquireRelease = 6, + SequentiallyConsistent = 7, + } + + enum SynchronizationScope + { + SingleThread = 0, + CrossThread = 1, + } + + struct CmpXchgResult(T) + { + T previousValue; + bool exchanged; + } + + pragma(LDC_atomic_load) + T llvm_atomic_load(T)(in shared T* ptr, AtomicOrdering ordering) pure @trusted; + + pragma(LDC_atomic_store) + void llvm_atomic_store(T)(T val, shared T* ptr, AtomicOrdering ordering) pure @trusted; + + pragma(LDC_atomic_rmw, "xchg") + T llvm_atomic_rmw_xchg(T)(shared T* ptr, T val, AtomicOrdering ordering) pure @trusted; + + pragma(LDC_atomic_rmw, "add") + T llvm_atomic_rmw_add(T)(in shared T* ptr, T val, AtomicOrdering ordering) pure @trusted; + + pragma(LDC_atomic_rmw, "sub") + T llvm_atomic_rmw_sub(T)(in shared T* ptr, T val, AtomicOrdering ordering) pure @trusted; + + pragma(LDC_atomic_cmp_xchg) + CmpXchgResult!T llvm_atomic_cmp_xchg(T)(shared T* ptr, T cmp, T val, + AtomicOrdering successOrdering, AtomicOrdering failureOrdering, bool weak) pure @trusted; + + pragma(LDC_fence) + void llvm_memory_fence(AtomicOrdering ordering) pure @trusted; +} +else version (D_InlineAsm_X86_64) { - auto ptr = cast(T*)&val; - T old = *ptr; - *ptr += mod; - return old; + // ----------------------------------------------------------------------- + // DMD x86_64: inline assembly + // ----------------------------------------------------------------------- + + nothrow @nogc @safe: + + T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(ref const T val) pure @trusted + if (!is(T == shared)) + { + static if (ms == MemoryOrder.seq) + { + // seq_cst load: use lock cmpxchg to get full barrier + size_t storage = void; + auto srcPtr = cast(size_t)&val; + + enum ValReg = SizedReg!(DX, T); + enum ResReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov RCX, srcPtr; + mov %0, 0; + mov %1, 0; + lock; cmpxchg [RCX], %0; + lea RCX, storage; + mov [RCX], %1; + } + }, [ValReg, ResReg])); + + return *cast(T*)&storage; + } + else + return val; + } + + TailShared!T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(auto ref shared const T val) pure @trusted + { + return atomic_load!ms(*cast(const T*)&val); + } + + void atomic_store(MemoryOrder ms = MemoryOrder.seq, T, V)(ref shared T val, V newval) pure @trusted + if (__traits(compiles, { *cast(T*)&val = newval; })) + { + static if (ms == MemoryOrder.seq) + { + // seq_cst store: use xchg (has implicit lock) + auto destPtr = cast(size_t)cast(T*)&val; + T tmp = newval; + + enum ValReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov %0, tmp; + mov RCX, destPtr; + lock; xchg [RCX], %0; + } + }, [ValReg])); + } + else + *cast(T*)&val = newval; + } + + TailShared!T atomic_op(string op, T, V1)(ref shared T val, V1 mod) pure @trusted + if (__traits(compiles, mixin("*cast(T*)&val" ~ op ~ "mod"))) + { + static if ((op == "+=" || op == "-=") && __traits(isIntegral, T) && T.sizeof <= size_t.sizeof) + { + auto ptr = cast(T*)&val; + static if (op == "+=") + { + T old = _asm_fetch_add!T(ptr, cast(T)mod); + return cast(TailShared!T)(old + cast(T)mod); + } + else + { + T old = _asm_fetch_add!T(ptr, cast(T)-cast(IntOrLong!T)mod); + return cast(TailShared!T)(old - cast(T)mod); + } + } + else + { + // CAS loop + auto ptr = cast(T*)&val; + while (true) + { + T current = *ptr; + T desired = current; + mixin("desired " ~ op ~ " mod;"); + if (_asm_cas!T(ptr, ¤t, desired)) + return *cast(TailShared!T*)&desired; + } + } + } + + bool cas(T, V1, V2)(shared(T)* here, V1 ifThis, V2 writeThis) pure @trusted + { + T cmp = cast(T)ifThis; + return _asm_cas!T(cast(T*)here, &cmp, cast(T)writeThis); + } + + T atomic_exchange(MemoryOrder ms = MemoryOrder.seq, T, V)(shared(T)* here, V exchangeWith) pure @trusted + { + auto ptr = cast(T*)here; + T tmp = cast(T)exchangeWith; + size_t storage = void; + auto destPtr = cast(size_t)ptr; + + enum DestReg = SizedReg!CX; + enum ValReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov %1, tmp; + mov %0, destPtr; + lock; xchg [%0], %1; + lea %0, storage; + mov [%0], %1; + } + }, [DestReg, ValReg])); + + return *cast(T*)&storage; + } + + T atomic_fetch_add(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) + { + return _asm_fetch_add!T(cast(T*)&val, mod); + } + + T atomic_fetch_sub(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) + { + return _asm_fetch_add!T(cast(T*)&val, cast(T)-cast(IntOrLong!T)mod); + } + + void atomicFence(MemoryOrder ms = MemoryOrder.seq)() pure @trusted + { + static if (ms != MemoryOrder.relaxed) + { + asm pure nothrow @nogc @trusted + { + mfence; + } + } + } + + void pause() pure @trusted + { + asm pure nothrow @nogc @trusted + { + pause; + } + } + + // --- DMD x86_64 private helpers --- + private: + + T _asm_fetch_add(T)(T* dest, T value) pure @trusted + { + size_t storage = void; + auto destPtr = cast(size_t)dest; + + enum DestReg = SizedReg!DX; + enum ValReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov %1, value; + mov %0, destPtr; + lock; xadd [%0], %1; + lea %0, storage; + mov [%0], %1; + } + }, [DestReg, ValReg])); + + return *cast(T*)&storage; + } + + bool _asm_cas(T)(T* dest, T* compare, T value) pure @trusted + { + bool success; + auto destPtr = cast(size_t)dest; + auto cmpPtr = cast(size_t)compare; + + enum SrcReg = SizedReg!CX; + enum ValueReg = SizedReg!(DX, T); + enum CompareReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov %1, value; + mov %0, cmpPtr; + mov %2, [%0]; + + mov %0, destPtr; + lock; cmpxchg [%0], %1; + + setz success; + mov %0, cmpPtr; + mov [%0], %2; + } + }, [SrcReg, ValueReg, CompareReg])); + + return success; + } } +else version (D_InlineAsm_X86) +{ + // ----------------------------------------------------------------------- + // DMD x86 (32-bit): inline assembly + // ----------------------------------------------------------------------- + + nothrow @nogc @safe: + + T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(ref const T val) pure @trusted + if (!is(T == shared)) + { + static assert(T.sizeof <= 4, "64-bit atomicLoad not supported on 32-bit target"); + + static if (ms == MemoryOrder.seq) + { + size_t storage = void; + + enum ValReg = SizedReg!(DX, T); + enum ResReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov ECX, val; + mov %0, 0; + mov %1, 0; + lock; cmpxchg [ECX], %0; + lea ECX, storage; + mov [ECX], %1; + } + }, [ValReg, ResReg])); + + return *cast(T*)&storage; + } + else + return val; + } + + TailShared!T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(auto ref shared const T val) pure @trusted + { + return atomic_load!ms(*cast(const T*)&val); + } + + void atomic_store(MemoryOrder ms = MemoryOrder.seq, T, V)(ref shared T val, V newval) pure @trusted + if (__traits(compiles, { *cast(T*)&val = newval; })) + { + static assert(T.sizeof <= 4, "64-bit atomicStore not supported on 32-bit target"); + + static if (ms == MemoryOrder.seq) + { + T tmp = newval; + enum ValReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov %0, tmp; + mov ECX, val; + lock; xchg [ECX], %0; + } + }, [ValReg])); + } + else + *cast(T*)&val = newval; + } + + TailShared!T atomic_op(string op, T, V1)(ref shared T val, V1 mod) pure @trusted + if (__traits(compiles, mixin("*cast(T*)&val" ~ op ~ "mod"))) + { + static assert(T.sizeof <= 4, "64-bit atomicOp not supported on 32-bit target"); + + static if ((op == "+=" || op == "-=") && __traits(isIntegral, T) && T.sizeof <= 4) + { + auto ptr = cast(T*)&val; + static if (op == "+=") + { + T old = _asm_fetch_add!T(ptr, cast(T)mod); + return cast(TailShared!T)(old + cast(T)mod); + } + else + { + T old = _asm_fetch_add!T(ptr, cast(T)-cast(IntOrLong!T)mod); + return cast(TailShared!T)(old - cast(T)mod); + } + } + else + { + auto ptr = cast(T*)&val; + while (true) + { + T current = *ptr; + T desired = current; + mixin("desired " ~ op ~ " mod;"); + if (_asm_cas!T(ptr, ¤t, desired)) + return *cast(TailShared!T*)&desired; + } + } + } + + bool cas(T, V1, V2)(shared(T)* here, V1 ifThis, V2 writeThis) pure @trusted + { + static assert(T.sizeof <= 4, "64-bit cas not supported on 32-bit target"); + T cmp = cast(T)ifThis; + return _asm_cas!T(cast(T*)here, &cmp, cast(T)writeThis); + } + + T atomic_exchange(MemoryOrder ms = MemoryOrder.seq, T, V)(shared(T)* here, V exchangeWith) pure @trusted + { + static assert(T.sizeof <= 4, "64-bit atomicExchange not supported on 32-bit target"); + + auto ptr = cast(T*)here; + T tmp = cast(T)exchangeWith; + size_t storage = void; + + enum ValReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov %0, tmp; + mov ECX, ptr; + lock; xchg [ECX], %0; + lea ECX, storage; + mov [ECX], %0; + } + }, [ValReg])); + + return *cast(T*)&storage; + } + + T atomic_fetch_add(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) + { + static assert(T.sizeof <= 4, "64-bit atomicFetchAdd not supported on 32-bit target"); + return _asm_fetch_add!T(cast(T*)&val, mod); + } + + T atomic_fetch_sub(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) + { + static assert(T.sizeof <= 4, "64-bit atomicFetchSub not supported on 32-bit target"); + return _asm_fetch_add!T(cast(T*)&val, cast(T)-cast(IntOrLong!T)mod); + } + + void atomicFence(MemoryOrder ms = MemoryOrder.seq)() pure @trusted + { + static if (ms != MemoryOrder.relaxed) + { + // x86 without guaranteed SSE2 — mfence may not exist. + // lock; add is a full barrier on all x86. + asm pure nothrow @nogc @trusted + { + push EAX; + lock; add [ESP], 0; + pop EAX; + } + } + } + + void pause() pure @trusted + { + asm pure nothrow @nogc @trusted + { + pause; + } + } + + // --- DMD x86 private helpers --- + private: + + T _asm_fetch_add(T)(T* dest, T value) pure @trusted + { + size_t storage = void; + + enum DestReg = SizedReg!DX; + enum ValReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov %1, value; + mov %0, dest; + lock; xadd [%0], %1; + lea %0, storage; + mov [%0], %1; + } + }, [DestReg, ValReg])); + + return *cast(T*)&storage; + } -T atomic_fetch_sub(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted - if (__traits(isIntegral, T)) + bool _asm_cas(T)(T* dest, T* compare, T value) pure @trusted + { + bool success; + + enum SrcReg = SizedReg!CX; + enum ValueReg = SizedReg!(DX, T); + enum CompareReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov %1, value; + mov %0, compare; + mov %2, [%0]; + + mov %0, dest; + lock; cmpxchg [%0], %1; + + setz success; + mov %0, compare; + mov [%0], %2; + } + }, [SrcReg, ValueReg, CompareReg])); + + return success; + } +} +else { - auto ptr = cast(T*)&val; - T old = *ptr; - *ptr -= mod; - return old; + // ----------------------------------------------------------------------- + // Fallback: plain load/store for single-core / cooperative-multitasking + // targets (e.g. Xtensa, bare-metal RISC-V) + // ----------------------------------------------------------------------- + + nothrow @nogc @safe: + + T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(ref const T val) pure @trusted + if (!is(T == shared)) + { + return val; + } + + TailShared!T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(auto ref shared const T val) pure @trusted + { + return *cast(TailShared!T*)&val; + } + + void atomic_store(MemoryOrder ms = MemoryOrder.seq, T, V)(ref shared T val, V newval) pure @trusted + if (__traits(compiles, { *cast(T*)&val = newval; })) + { + *cast(T*)&val = newval; + } + + TailShared!T atomic_op(string op, T, V1)(ref shared T val, V1 mod) pure @trusted + if (__traits(compiles, mixin("*cast(T*)&val" ~ op ~ "mod"))) + { + auto ptr = cast(T*)&val; + mixin("*ptr " ~ op ~ " mod;"); + return *cast(TailShared!T*)ptr; + } + + bool cas(T, V1, V2)(shared(T)* here, V1 ifThis, V2 writeThis) pure @trusted + { + auto ptr = cast(T*)here; + if (*ptr == ifThis) + { + *ptr = writeThis; + return true; + } + return false; + } + + T atomic_exchange(MemoryOrder ms = MemoryOrder.seq, T, V)(shared(T)* here, V exchangeWith) pure @trusted + { + auto ptr = cast(T*)here; + T old = *ptr; + *ptr = exchangeWith; + return old; + } + + T atomic_fetch_add(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) + { + auto ptr = cast(T*)&val; + T old = *ptr; + *ptr += mod; + return old; + } + + T atomic_fetch_sub(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) + { + auto ptr = cast(T*)&val; + T old = *ptr; + *ptr -= mod; + return old; + } + + void atomicFence(MemoryOrder ms = MemoryOrder.seq)() pure @trusted {} + void pause() pure @trusted {} } -/// Simplified TailShared — strips shared qualifier for noruntime builds. + +// ----------------------------------------------------------------------- +// Shared helpers +// ----------------------------------------------------------------------- + template TailShared(U) if (!is(U == shared)) { alias TailShared = .TailShared!(shared U); @@ -105,3 +761,109 @@ template TailShared(S) if (is(S == shared)) else static assert(false); } + +private: + +template IntOrLong(T) +{ + static if (T.sizeof > 4) + alias IntOrLong = long; + else + alias IntOrLong = int; +} + +// DMD inline-asm helpers (shared between x86 and x86_64) +version (D_InlineAsm_X86) + enum _have_dmd_asm = true; +else version (D_InlineAsm_X86_64) + enum _have_dmd_asm = true; +else + enum _have_dmd_asm = false; + +static if (_have_dmd_asm) +{ + enum : int + { + AX, BX, CX, DX, DI, SI, R8, R9 + } + + immutable string[4][8] registerNames = [ + ["AL", "AX", "EAX", "RAX"], + ["BL", "BX", "EBX", "RBX"], + ["CL", "CX", "ECX", "RCX"], + ["DL", "DX", "EDX", "RDX"], + ["DIL", "DI", "EDI", "RDI"], + ["SIL", "SI", "ESI", "RSI"], + ["R8B", "R8W", "R8D", "R8"], + ["R9B", "R9W", "R9D", "R9"], + ]; + + template RegIndex(T) + { + static if (T.sizeof == 1) + enum RegIndex = 0; + else static if (T.sizeof == 2) + enum RegIndex = 1; + else static if (T.sizeof == 4) + enum RegIndex = 2; + else static if (T.sizeof == 8) + enum RegIndex = 3; + else + static assert(false, "Invalid type"); + } + + enum SizedReg(int reg, T = size_t) = registerNames[reg][RegIndex!T]; + + // CTFE-only helper for building asm strings. Templated so it doesn't + // inherit the module-level @nogc (string concat is fine at compile time). + template simpleFormat(string format, string[] args) + { + enum simpleFormat = _simpleFormatImpl(format, args); + } + + string _simpleFormatImpl()(string format, const(string)[] args) pure @safe + { + string result; + outer: while (format.length) + { + foreach (i; 0 .. format.length) + { + if (format[i] == '%' || format[i] == '?') + { + bool isQ = format[i] == '?'; + result ~= format[0 .. i++]; + assert(i < format.length, "Invalid format string"); + if (format[i] == '%' || format[i] == '?') + { + assert(!isQ, "Invalid format string"); + result ~= format[i++]; + } + else + { + int index = 0; + assert(format[i] >= '0' && format[i] <= '9', "Invalid format string"); + while (i < format.length && format[i] >= '0' && format[i] <= '9') + index = index * 10 + (ubyte(format[i++]) - ubyte('0')); + if (!isQ) + result ~= args[index]; + else if (!args[index]) + { + size_t j = i; + for (; j < format.length;) + { + if (format[j++] == '\n') + break; + } + i = j; + } + } + format = format[i .. $]; + continue outer; + } + } + result ~= format; + break; + } + return result; + } +} From b5a70d41cca02e404a659c1c3f84a6643e133780 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 10 Apr 2026 16:13:14 +1000 Subject: [PATCH 122/138] Add BLE driver. --- Makefile | 3 + src/sys/baremetal/ble.d | 537 ++++++++++ src/sys/baremetal/can.d | 38 +- src/sys/baremetal/wifi.d | 103 +- src/sys/esp32/ble.d | 980 +++++++++++++++++++ src/sys/esp32/can.d | 30 +- src/sys/esp32/uart.d | 2 +- src/sys/esp32/wifi.d | 66 +- src/sys/windows/ble.d | 1987 ++++++++++++++++++++++++++++++++++++++ src/urt/thread.d | 71 ++ 10 files changed, 3726 insertions(+), 91 deletions(-) create mode 100644 src/sys/baremetal/ble.d create mode 100644 src/sys/esp32/ble.d create mode 100644 src/sys/windows/ble.d create mode 100644 src/urt/thread.d diff --git a/Makefile b/Makefile index 324c6be..704f3da 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,9 @@ DFLAGS := $(DFLAGS) -preview=bitfields -preview=rvaluerefparam -preview=noshared SOURCES := $(shell find "$(SRCDIR)" -type f -name '*.d' -not -path '$(SRCDIR)/sys/*') SOURCES := $(SOURCES) $(shell find "$(SRCDIR)/sys/baremetal" -type f -name '*.d') +ifeq ($(OS),windows) + SOURCES := $(SOURCES) $(shell find "$(SRCDIR)/sys/windows" -type f -name '*.d') +endif SOURCES := $(SOURCES) $(SRCDIR)/urt/internal/mbedtls.c ifeq ($(PLATFORM),riscv64) diff --git a/src/sys/baremetal/ble.d b/src/sys/baremetal/ble.d new file mode 100644 index 0000000..8c93ea2 --- /dev/null +++ b/src/sys/baremetal/ble.d @@ -0,0 +1,537 @@ +module sys.baremetal.ble; + +import urt.result : Result, InternalResult; +import urt.uuid : GUID; + +version (Windows) + public import sys.windows.ble; +else version (Espressif) + public import sys.esp32.ble; +else + enum uint num_ble = 0; + +nothrow @nogc: + + +// ════════════════════════════════════════════════════════════════════ +// Types +// ════════════════════════════════════════════════════════════════════ + +enum BLEError : ubyte +{ + none, + not_found, // device not found / unreachable + auth_failed, // pairing or encryption failure + timeout, // operation timed out + protocol, // ATT/GATT protocol error + access_denied, // insufficient permissions + internal, // platform-specific internal error +} + +enum BLERole : ubyte +{ + central, // scan + connect to peripherals + peripheral, // advertise + accept connections + observer, // scan only (no connections) + broadcaster, // advertise only (no connections) +} + +enum BLEAdvType : ubyte +{ + connectable, // ADV_IND: connectable, scannable, undirected + connectable_direct, // ADV_DIRECT_IND: connectable, directed + scannable, // ADV_SCAN_IND: scannable, not connectable + nonconnectable, // ADV_NONCONN_IND: not connectable, not scannable +} + +enum BLEAddrType : ubyte +{ + public_ = 0x00, + random_static = 0x01, + rpa_public = 0x02, // resolvable private, public identity + rpa_random = 0x03, // resolvable private, random identity +} + +enum BLEPhy : ubyte +{ + phy_1m = 0x01, // mandatory, 1 Mbit/s + phy_2m = 0x02, // optional, 2 Mbit/s + phy_coded = 0x03, // long range +} + +enum GattCharProps : ushort +{ + none = 0x0000, + broadcast = 0x0001, + read = 0x0002, + write_without_response = 0x0004, + write = 0x0008, + notify = 0x0010, + indicate = 0x0020, + authenticated_writes = 0x0040, + extended_properties = 0x0080, + reliable_write = 0x0100, +} + + +// --- Configuration structs --- + +struct BLEConfig +{ + byte tx_power; // dBm (0 = platform default) + BLEPhy preferred_phy; // preferred PHY (0 = platform default) +} + +struct BLEScanConfig +{ + bool active = true; // active scan (send SCAN_REQ for extra data) + ushort interval_ms = 100; // scan interval + ushort window_ms = 50; // scan window, <= interval + bool filter_duplicates; // suppress repeated advertisements +} + +struct BLEAdvConfig +{ + BLEAdvType adv_type; + ushort interval_ms = 100; // advertising interval + byte tx_power; // dBm (0 = default) + const(ubyte)[] adv_data; // raw AD structures (max 31 bytes) + const(ubyte)[] scan_rsp; // scan response data (max 31 bytes) +} + +struct BLEConnConfig +{ + ushort interval_min_ms = 7; // connection interval range + ushort interval_max_ms = 30; + ushort latency; // slave latency (number of skippable events) + ushort timeout_ms = 5000; // supervision timeout +} + +// Discovered advertisement report from scanning. +struct BLEAdvReport +{ + ubyte[6] addr; + BLEAddrType addr_type; + BLEAdvType adv_type; + byte rssi; // dBm (-128 = unknown) + byte tx_power; // dBm (-128 = not present) + ubyte data_len; + ubyte[62] data_buf; // adv_data + scan_rsp combined (max 31+31) + + const(ubyte)[] data() const pure nothrow @nogc + => data_buf[0 .. data_len]; +} + +// A discovered GATT characteristic on a connected device. +struct BLEGattChar +{ + ushort handle; // ATT handle for read/write + ushort cccd_handle; // Client Characteristic Config descriptor (0 = none) + GUID service_uuid; // owning service UUID + GUID char_uuid; // characteristic UUID + GattCharProps properties; +} + + +// --- Handles --- + +struct BLE +{ + ubyte port = ubyte.max; +} + +bool is_open(ref const BLE ble) +{ + return ble.port != ubyte.max; +} + +// Opaque connection handle. Lifetime: from connect callback (success) +// until disconnect callback fires. Do not use after disconnect. +struct BLEConn +{ + ubyte id = ubyte.max; +} + +bool is_valid(ref const BLEConn conn) +{ + return conn.id != ubyte.max; +} + +// Opaque advertising handle. Returned by ble_adv_start, used to stop +// a specific advertisement. Invalid after ble_adv_stop. +struct BLEAdv +{ + ubyte id = ubyte.max; +} + +bool is_valid(ref const BLEAdv adv) +{ + return adv.id != ubyte.max; +} + + +// --- Callbacks --- + +// Scan result received. Called once per advertisement/scan response. +// The report is only valid for the duration of the callback. +alias BLEScanCallback = void function(BLE ble, ref const BLEAdvReport report) nothrow @nogc; + +// Connection state change. On success: conn is valid, error is .none. +// On failure or disconnect: conn becomes invalid after callback returns. +alias BLEConnCallback = void function(BLE ble, BLEConn conn, bool connected, BLEError error) nothrow @nogc; + +// GATT service discovery complete. chars is only valid during callback. +// On error: chars.length == 0 and error != .none. +alias BLEDiscoverCallback = void function(BLE ble, BLEConn conn, const(BLEGattChar)[] chars, BLEError error) nothrow @nogc; + +// GATT read complete. data is only valid during callback. +alias BLEReadCallback = void function(BLE ble, BLEConn conn, ushort handle, const(ubyte)[] data, BLEError error) nothrow @nogc; + +// GATT write complete. +alias BLEWriteCallback = void function(BLE ble, BLEConn conn, ushort handle, BLEError error) nothrow @nogc; + +// Notification or indication received from a connected peripheral. +// data is only valid during callback. +alias BLENotifyCallback = void function(BLE ble, BLEConn conn, ushort handle, const(ubyte)[] data) nothrow @nogc; + + +// ════════════════════════════════════════════════════════════════════ +// Error type +// ════════════════════════════════════════════════════════════════════ + +BLEError ble_result(Result result) +{ + return cast(BLEError)result.system_code; +} + + +// ════════════════════════════════════════════════════════════════════ +// Implementation +// ════════════════════════════════════════════════════════════════════ + +// --- Lifecycle --- + +void ble_init() +{ + if (_init_refcount++ == 0) + { + // TODO: enable clocks/power for BLE peripheral block + } +} + +void ble_deinit() +{ + assert(_init_refcount > 0); + if (--_init_refcount == 0) + { + // TODO: disable clocks/power for BLE peripheral block + } +} + +Result ble_open(ref BLE ble, ubyte port, ref const BLEConfig cfg) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (port >= num_ble) + return InternalResult.invalid_parameter; + + if (!ble_hw_open(port, cfg)) + return InternalResult.failed; + + ble.port = port; + return Result.success; + } +} + +void ble_close(ref BLE ble) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_close(ble.port); + ble.port = ubyte.max; +} + +// --- Scanning (central/observer role) --- + +// Start scanning for advertisements. Reports are delivered via the +// scan callback set with ble_set_scan_callback(). Scanning continues +// until ble_scan_stop() is called or the radio is closed. +Result ble_scan_start(ref BLE ble, ref const BLEScanConfig cfg) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (!ble_hw_scan_start(ble.port, cfg)) + return InternalResult.failed; + return Result.success; + } +} + +void ble_scan_stop(ref BLE ble) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_scan_stop(ble.port); +} + +// --- Advertising (peripheral/broadcaster role) --- + +// Start advertising. Returns an opaque handle on success that can be +// passed to ble_adv_stop. Multiple advertisements may be active +// concurrently (platform permitting). For connectable advertisements, +// incoming connections are delivered via the connection callback. +BLEAdv ble_adv_start(ref BLE ble, ref const BLEAdvConfig cfg) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + return ble_hw_adv_start(ble.port, cfg); +} + +// Stop a specific advertisement. Pass the handle returned by +// ble_adv_start. The handle is invalid after this call. +void ble_adv_stop(ref BLE ble, BLEAdv adv) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_adv_stop(ble.port, adv); +} + +// --- Connection (central role) --- + +// Initiate a connection to a peripheral. Completion (success or failure) +// is delivered via the connection callback. Only one connect may be +// pending at a time per radio. +Result ble_connect(ref BLE ble, ref const ubyte[6] peer_addr, BLEAddrType addr_type, ref const BLEConnConfig cfg) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (!ble_hw_connect(ble.port, peer_addr, addr_type, cfg)) + return InternalResult.failed; + return Result.success; + } +} + +// Cancel a pending connection attempt. +void ble_connect_cancel(ref BLE ble) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_connect_cancel(ble.port); +} + +// Disconnect an established connection. The disconnect callback fires +// when teardown is complete. +Result ble_disconnect(ref BLE ble, BLEConn conn) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (!ble_hw_disconnect(ble.port, conn)) + return InternalResult.failed; + return Result.success; + } +} + +// --- GATT discovery --- + +// Discover all services and characteristics on a connected device. +// Results are delivered via the discover callback. Only one discovery +// may be active per connection at a time. +Result ble_gatt_discover(ref BLE ble, BLEConn conn) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (!ble_hw_gatt_discover(ble.port, conn)) + return InternalResult.failed; + return Result.success; + } +} + +// --- GATT read/write --- + +// Read a characteristic value by handle. Result is delivered via the +// read callback. Multiple reads may be in flight concurrently (up to +// a platform-defined limit). +Result ble_gatt_read(ref BLE ble, BLEConn conn, ushort handle) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (!ble_hw_gatt_read(ble.port, conn, handle)) + return InternalResult.failed; + return Result.success; + } +} + +// Write a characteristic value. If with_response is true, the write +// callback fires on completion; otherwise the write is fire-and-forget +// (Write Without Response / Write Command). +Result ble_gatt_write(ref BLE ble, BLEConn conn, ushort handle, const(ubyte)[] data, bool with_response = true) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (!ble_hw_gatt_write(ble.port, conn, handle, data, with_response)) + return InternalResult.failed; + return Result.success; + } +} + +// --- Notifications / Indications --- + +// Enable or disable notifications/indications on a characteristic. +// Writes the CCCD descriptor on the remote device. Incoming notifications +// are delivered via the notify callback. +Result ble_gatt_subscribe(ref BLE ble, BLEConn conn, ushort handle, bool enable) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (!ble_hw_gatt_subscribe(ble.port, conn, handle, enable)) + return InternalResult.failed; + return Result.success; + } +} + +// --- Queries --- + +Result ble_get_mac(ref BLE ble, ref ubyte[6] mac) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (!ble_hw_get_mac(ble.port, mac)) + return InternalResult.failed; + return Result.success; + } +} + +byte ble_get_rssi(ref BLE ble, BLEConn conn) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + return ble_hw_get_rssi(ble.port, conn); +} + +// --- Callbacks --- + +void ble_set_scan_callback(ref BLE ble, BLEScanCallback cb) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_set_scan_callback(ble.port, cb); +} + +void ble_set_conn_callback(ref BLE ble, BLEConnCallback cb) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_set_conn_callback(ble.port, cb); +} + +void ble_set_discover_callback(ref BLE ble, BLEDiscoverCallback cb) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_set_discover_callback(ble.port, cb); +} + +void ble_set_read_callback(ref BLE ble, BLEReadCallback cb) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_set_read_callback(ble.port, cb); +} + +void ble_set_write_callback(ref BLE ble, BLEWriteCallback cb) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_set_write_callback(ble.port, cb); +} + +void ble_set_notify_callback(ref BLE ble, BLENotifyCallback cb) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_set_notify_callback(ble.port, cb); +} + +// --- Poll --- + +// Drive completions for platforms that don't deliver events natively. +// Must be called periodically from the main loop. +void ble_poll(ref BLE ble) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_poll(ble.port); +} + + +// ════════════════════════════════════════════════════════════════════ +// Tests +// ════════════════════════════════════════════════════════════════════ + +unittest +{ + static if (num_ble > 0) + { + BLE b; + BLEConfig cfg; + + ble_init(); + + // Out-of-range port + auto r = ble_open(b, cast(ubyte)num_ble, cfg); + assert(!r); + assert(!b.is_open); + + // Open/close each valid port + foreach (p; 0 .. num_ble) + { + BLE port; + auto r2 = ble_open(port, cast(ubyte)p, cfg); + assert(r2, "ble_open failed"); + assert(port.is_open); + assert(port.port == p); + + ble_poll(port); + + ble_close(port); + assert(!port.is_open); + } + + ble_deinit(); + } +} + + +private: + +__gshared ubyte _init_refcount; diff --git a/src/sys/baremetal/can.d b/src/sys/baremetal/can.d index 41857a9..87ce650 100644 --- a/src/sys/baremetal/can.d +++ b/src/sys/baremetal/can.d @@ -153,7 +153,7 @@ Result can_open(ref Can can, ubyte port, ref const CanConfig cfg, CanRxCallback if (port >= num_can) return InternalResult.invalid_parameter; - if (!can_open(port, cfg)) + if (!can_hw_open(port, cfg)) return InternalResult.failed; can.port = port; @@ -171,26 +171,28 @@ void can_close(ref Can can) static if (num_can == 0) assert(false, "no CAN on this platform"); else - can_close(can.port); + can_hw_close(can.port); can.port = ubyte.max; } // Transmit -int can_transmit(ref Can can, ref const CanFrame frame, CanTxOp* op = null) +Result can_transmit(ref Can can, ref const CanFrame frame, CanTxOp* op = null) { static if (num_can == 0) assert(false, "no CAN on this platform"); else { - int ret = can_transmit(can.port, frame); + bool ok = can_hw_transmit(can.port, frame); if (op !is null) { - op.status = ret >= 0 ? CanTxOp.Status.complete : CanTxOp.Status.error; + op.status = ok ? CanTxOp.Status.complete : CanTxOp.Status.error; if (op.cb !is null) op.cb(&can, *op); } - return ret; + if (!ok) + return InternalResult.failed; + return Result.success; } } @@ -200,7 +202,7 @@ void can_tx_abort(ref Can can, CanTxOp* op) assert(false, "no CAN on this platform"); else { - can_tx_abort(can.port); + can_hw_tx_abort(can.port); if (op !is null) { op.status = CanTxOp.Status.cancelled; @@ -217,7 +219,7 @@ bool can_receive(ref Can can, out CanFrame frame) static if (num_can == 0) assert(false, "no CAN on this platform"); else - return can_receive(can.port, frame); + return can_hw_receive(can.port, frame); } size_t can_rx_available(ref const Can can) @@ -225,7 +227,7 @@ size_t can_rx_available(ref const Can can) static if (num_can == 0) assert(false, "no CAN on this platform"); else - return can_rx_available(can.port); + return can_hw_rx_available(can.port); } void can_rx_flush(ref Can can) @@ -233,7 +235,7 @@ void can_rx_flush(ref Can can) static if (num_can == 0) assert(false, "no CAN on this platform"); else - can_rx_flush(can.port); + can_hw_rx_flush(can.port); } // Filters @@ -260,7 +262,7 @@ CanBusState can_bus_state(ref const Can can) static if (num_can == 0) assert(false, "no CAN on this platform"); else - return can_bus_state(can.port); + return can_hw_bus_state(can.port); } ubyte can_tx_error_count(ref const Can can) @@ -268,7 +270,7 @@ ubyte can_tx_error_count(ref const Can can) static if (num_can == 0) assert(false, "no CAN on this platform"); else - return can_tx_error_count(can.port); + return can_hw_tx_error_count(can.port); } ubyte can_rx_error_count(ref const Can can) @@ -276,7 +278,7 @@ ubyte can_rx_error_count(ref const Can can) static if (num_can == 0) assert(false, "no CAN on this platform"); else - return can_rx_error_count(can.port); + return can_hw_rx_error_count(can.port); } CanError can_check_errors(ref Can can) @@ -284,7 +286,7 @@ CanError can_check_errors(ref Can can) static if (num_can == 0) assert(false, "no CAN on this platform"); else - return can_check_errors(can.port); + return can_hw_check_errors(can.port); } // Bus recovery @@ -295,9 +297,9 @@ Result can_bus_recover(ref Can can) assert(false, "no CAN on this platform"); else { - if (can_bus_recover(can.port)) - return Result.success; - return InternalResult.failed; + if (!can_hw_bus_recover(can.port)) + return InternalResult.failed; + return Result.success; } } @@ -325,7 +327,7 @@ void can_poll(ref Can can) static if (num_can == 0) assert(false, "no CAN on this platform"); else - can_poll(can.port); + can_hw_poll(can.port); } diff --git a/src/sys/baremetal/wifi.d b/src/sys/baremetal/wifi.d index 4ec37cc..8b03ba9 100644 --- a/src/sys/baremetal/wifi.d +++ b/src/sys/baremetal/wifi.d @@ -16,13 +16,13 @@ nothrow @nogc: enum WifiError : ubyte { - none = 0, - auth_failed = 1 << 0, - no_ap = 1 << 1, // target AP not found - assoc_failed = 1 << 2, // association rejected - timeout = 1 << 3, - tx_failed = 1 << 4, - internal = 1 << 5, + none, + auth_failed, + no_ap, // target AP not found + assoc_failed, // association rejected + timeout, + tx_failed, + internal, } // Virtual interface type within a radio. @@ -34,10 +34,11 @@ enum WifiVif : ubyte enum WifiMode : ubyte { - none, // radio idle, no virtual interfaces active - sta, // station only - ap, // access point only - apsta, // concurrent AP + STA + none, // radio off, no virtual interfaces active + monitor, // radio on, raw 802.11 only — no stack + sta, // station only + ap, // access point only + apsta, // concurrent AP + STA } enum WifiAuth : ubyte @@ -129,7 +130,7 @@ struct WifiScanResult ubyte ssid_len; char[32] ssid_buf; - const(char)[] ssid() const pure + const(char)[] ssid() const pure nothrow @nogc => ssid_buf[0 .. ssid_len]; } @@ -143,6 +144,12 @@ struct WifiStaInfo // virtual interface. Data includes the 14-byte Ethernet header. alias WifiRxCallback = void function(Wifi wifi, WifiVif vif, const(ubyte)[] data) nothrow @nogc; +// Called from ISR/driver when a raw 802.11 frame is received +// (promiscuous/monitor tap). Data is the full 802.11 frame +// starting at the MAC header. Delivered independently of the +// Ethernet RX callback — both can be active simultaneously. +alias WifiRawRxCallback = void function(Wifi wifi, const(ubyte)[] frame, byte rssi, ubyte channel) nothrow @nogc; + // Called when a wifi event occurs. Replaces per-event callbacks // to match the single-callback pattern used by the router layer. alias WifiEventCallback = void function(Wifi wifi, WifiEvent event, const(void)* data) nothrow @nogc; @@ -202,7 +209,7 @@ Result wifi_open(ref Wifi wifi, ubyte port, ref const WifiConfig cfg) if (port >= num_wifi) return InternalResult.invalid_parameter; - if (!wifi_open(port, cfg)) + if (!wifi_hw_open(port, cfg)) return InternalResult.failed; wifi.port = port; @@ -215,7 +222,7 @@ void wifi_close(ref Wifi wifi) static if (num_wifi == 0) assert(false, "no WiFi on this platform"); else - wifi_close(wifi.port); + wifi_hw_close(wifi.port); wifi.port = ubyte.max; } @@ -225,7 +232,7 @@ Result wifi_set_mode(ref Wifi wifi, WifiMode mode) assert(false, "no WiFi on this platform"); else { - if (!wifi_set_mode(wifi.port, mode)) + if (!wifi_hw_set_mode(wifi.port, mode)) return InternalResult.failed; return Result.success; } @@ -239,7 +246,7 @@ Result wifi_sta_configure(ref Wifi wifi, ref const WifiStaConfig cfg) assert(false, "no WiFi on this platform"); else { - if (!wifi_sta_configure(wifi.port, cfg)) + if (!wifi_hw_sta_configure(wifi.port, cfg)) return InternalResult.failed; return Result.success; } @@ -253,7 +260,7 @@ Result wifi_sta_connect(ref Wifi wifi) assert(false, "no WiFi on this platform"); else { - if (!wifi_sta_connect(wifi.port)) + if (!wifi_hw_sta_connect(wifi.port)) return InternalResult.failed; return Result.success; } @@ -265,7 +272,7 @@ Result wifi_sta_disconnect(ref Wifi wifi) assert(false, "no WiFi on this platform"); else { - if (!wifi_sta_disconnect(wifi.port)) + if (!wifi_hw_sta_disconnect(wifi.port)) return InternalResult.failed; return Result.success; } @@ -279,7 +286,7 @@ Result wifi_ap_configure(ref Wifi wifi, ref const WifiApConfig cfg) assert(false, "no WiFi on this platform"); else { - if (!wifi_ap_configure(wifi.port, cfg)) + if (!wifi_hw_ap_configure(wifi.port, cfg)) return InternalResult.failed; return Result.success; } @@ -292,7 +299,7 @@ size_t wifi_ap_get_clients(ref Wifi wifi, WifiStaInfo[] buf) static if (num_wifi == 0) assert(false, "no WiFi on this platform"); else - return wifi_ap_get_clients(wifi.port, buf); + return wifi_hw_ap_get_clients(wifi.port, buf); } // Scanning @@ -303,7 +310,7 @@ Result wifi_scan_start(ref Wifi wifi, ref const WifiScanConfig cfg) assert(false, "no WiFi on this platform"); else { - if (!wifi_scan_start(wifi.port, cfg)) + if (!wifi_hw_scan_start(wifi.port, cfg)) return InternalResult.failed; return Result.success; } @@ -314,7 +321,7 @@ void wifi_scan_stop(ref Wifi wifi) static if (num_wifi == 0) assert(false, "no WiFi on this platform"); else - wifi_scan_stop(wifi.port); + wifi_hw_scan_stop(wifi.port); } // Retrieve scan results after WifiEvent.scan_done. @@ -324,19 +331,23 @@ size_t wifi_scan_get_results(ref Wifi wifi, WifiScanResult[] buf) static if (num_wifi == 0) assert(false, "no WiFi on this platform"); else - return wifi_scan_get_results(wifi.port, buf); + return wifi_hw_scan_get_results(wifi.port, buf); } // Frame TX/RX // Transmit an Ethernet frame (including 14-byte header) on a -// virtual interface. Returns 0 on success, negative on error. -int wifi_tx(ref Wifi wifi, WifiVif vif, const(ubyte)[] data) +// virtual interface. +Result wifi_tx(ref Wifi wifi, WifiVif vif, const(ubyte)[] data) { static if (num_wifi == 0) assert(false, "no WiFi on this platform"); else - return wifi_tx(wifi.port, vif, data); + { + if (!wifi_hw_tx(wifi.port, vif, data)) + return InternalResult.failed; + return Result.success; + } } void wifi_set_rx_callback(ref Wifi wifi, WifiRxCallback cb) @@ -344,7 +355,31 @@ void wifi_set_rx_callback(ref Wifi wifi, WifiRxCallback cb) static if (num_wifi == 0) assert(false, "no WiFi on this platform"); else - wifi_set_rx_callback(wifi.port, cb); + wifi_hw_set_rx_callback(wifi.port, cb); +} + +// Raw 802.11 frame TX/RX (monitor/promiscuous) + +// Transmit a raw 802.11 frame. Data must include the full MAC header. +// Requires monitor mode or promiscuous capability on the platform. +Result wifi_raw_tx(ref Wifi wifi, const(ubyte)[] frame) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_hw_raw_tx(wifi.port, frame)) + return InternalResult.failed; + return Result.success; + } +} + +void wifi_set_raw_rx_callback(ref Wifi wifi, WifiRawRxCallback cb) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + wifi_hw_set_raw_rx_callback(wifi.port, cb); } // Queries @@ -355,18 +390,18 @@ Result wifi_get_mac(ref Wifi wifi, WifiVif vif, ref ubyte[6] mac) assert(false, "no WiFi on this platform"); else { - if (!wifi_get_mac(wifi.port, vif, mac)) + if (!wifi_hw_get_mac(wifi.port, vif, mac)) return InternalResult.failed; return Result.success; } } -ubyte wifi_get_channel(ref Wifi wifi) +ubyte wifi_get_channel(ref const Wifi wifi) { static if (num_wifi == 0) assert(false, "no WiFi on this platform"); else - return wifi_get_channel(wifi.port); + return wifi_hw_get_channel(wifi.port); } // STA-only: signal strength of current connection. @@ -375,7 +410,7 @@ byte wifi_get_rssi(ref Wifi wifi) static if (num_wifi == 0) assert(false, "no WiFi on this platform"); else - return wifi_get_rssi(wifi.port); + return wifi_hw_get_rssi(wifi.port); } Result wifi_set_tx_power(ref Wifi wifi, byte power_dbm) @@ -384,7 +419,7 @@ Result wifi_set_tx_power(ref Wifi wifi, byte power_dbm) assert(false, "no WiFi on this platform"); else { - if (!wifi_set_tx_power(wifi.port, power_dbm)) + if (!wifi_hw_set_tx_power(wifi.port, power_dbm)) return InternalResult.failed; return Result.success; } @@ -397,7 +432,7 @@ void wifi_set_event_callback(ref Wifi wifi, WifiEventCallback cb) static if (num_wifi == 0) assert(false, "no WiFi on this platform"); else - wifi_set_event_callback(wifi.port, cb); + wifi_hw_set_event_callback(wifi.port, cb); } // Poll (for platforms without native event delivery) @@ -407,7 +442,7 @@ void wifi_poll(ref Wifi wifi) static if (num_wifi == 0) assert(false, "no WiFi on this platform"); else - wifi_poll(wifi.port); + wifi_hw_poll(wifi.port); } diff --git a/src/sys/esp32/ble.d b/src/sys/esp32/ble.d new file mode 100644 index 0000000..d9edfca --- /dev/null +++ b/src/sys/esp32/ble.d @@ -0,0 +1,980 @@ +// ESP32 BLE driver -- D wrapper over NimBLE via C shim + direct calls +// +// The C shim (ow_shim.c) handles: +// - ow_ble_init/deinit: NimBLE host config struct, port init, host task +// - ow_ble_set_gap_callback: GAP event dispatch trampoline to D +// +// Everything else (scan, connect, GATT) calls NimBLE C API directly. +// +// BLE controller count per chip (ESP-IDF v6.0): +// ESP32, S3, C2, C3, C5, C6, H2: 1 S2, P4: 0 +module sys.esp32.ble; + +import sys.baremetal.ble; + +import urt.uuid : GUID; + +nothrow @nogc: + + +version (ESP32) enum uint num_ble = 1; +else version (ESP32_S3) enum uint num_ble = 1; +else version (ESP32_C2) enum uint num_ble = 1; +else version (ESP32_C3) enum uint num_ble = 1; +else version (ESP32_C5) enum uint num_ble = 1; +else version (ESP32_C6) enum uint num_ble = 1; +else version (ESP32_H2) enum uint num_ble = 1; +else enum uint num_ble = 0; // S2, P4 + + +static if (num_ble > 0): + + +bool ble_hw_open(uint port, ref const BLEConfig cfg) +{ + if (port >= num_ble) + return false; + if (_opened) + return true; + + if (ow_ble_init() != 0) + return false; + + ow_ble_set_gap_callback(&gap_event_trampoline); + + _opened = true; + return true; +} + +void ble_hw_close(uint port) +{ + if (!_opened) + return; + + ble_hw_scan_stop(port); + ble_hw_adv_stop(port); + + // disconnect all active connections + foreach (ref s; _sessions) + { + if (s.active) + ble_gap_terminate(s.nimble_handle, 0x13); // Remote User Terminated + } + + ow_ble_set_gap_callback(null); + ow_ble_deinit(); + + _opened = false; + _scan_cb = null; + _conn_cb = null; + _discover_cb = null; + _read_cb = null; + _write_cb = null; + _notify_cb = null; + _num_sessions = 0; +} + +// --- Scanning --- + +bool ble_hw_scan_start(uint port, ref const BLEScanConfig cfg) +{ + ble_gap_disc_params params; + params.itvl = cast(ushort)(cfg.interval_ms * 1000 / 625); // BLE units of 0.625ms + params.window = cast(ushort)(cfg.window_ms * 1000 / 625); + params.filter_duplicates = cfg.filter_duplicates ? 1 : 0; + params.passive = cfg.active ? 0 : 1; + + if (ble_gap_disc(0, 0, ¶ms, &gap_event_trampoline, null) != 0) + return false; + return true; +} + +void ble_hw_scan_stop(uint port) +{ + ble_gap_disc_cancel(); +} + +// --- Advertising --- + +bool ble_hw_adv_start(uint port, ref const BLEAdvConfig cfg) +{ + if (cfg.adv_data.length > 0 && cfg.adv_data.length <= 31) + { + if (ble_gap_adv_set_data(cfg.adv_data.ptr, cast(int)cfg.adv_data.length) != 0) + return false; + } + + if (cfg.scan_rsp.length > 0 && cfg.scan_rsp.length <= 31) + { + if (ble_gap_adv_rsp_set_data(cfg.scan_rsp.ptr, cast(int)cfg.scan_rsp.length) != 0) + return false; + } + + ble_gap_adv_params params; + params.conn_mode = cfg.adv_type == BLEAdvType.connectable ? 2 : 0; // BLE_GAP_CONN_MODE_UND : NON + params.disc_mode = 2; // BLE_GAP_DISC_MODE_GEN + params.itvl_min = cast(ushort)(cfg.interval_ms * 1000 / 625); + params.itvl_max = params.itvl_min; + + if (ble_gap_adv_start(0, null, 0, ¶ms, &gap_event_trampoline, null) != 0) + return false; + return true; +} + +void ble_hw_adv_stop(uint port) +{ + ble_gap_adv_stop(); +} + +// --- Connection --- + +bool ble_hw_connect(uint port, ref const ubyte[6] peer_addr, BLEAddrType addr_type, ref const BLEConnConfig cfg) +{ + if (_pending_connect) + return false; + + ble_addr_t addr; + addr.type = cast(ubyte)addr_type; + // NimBLE uses LSB-first byte order + foreach (i; 0 .. 6) + addr.val[i] = peer_addr[5 - i]; + + ble_gap_conn_params params; + params.itvl_min = cast(ushort)(cfg.interval_min_ms * 1000 / 1250); // units of 1.25ms + params.itvl_max = cast(ushort)(cfg.interval_max_ms * 1000 / 1250); + params.latency = cfg.latency; + params.supervision_timeout = cast(ushort)(cfg.timeout_ms / 10); // units of 10ms + params.min_ce_len = 0; + params.max_ce_len = 0; + + _pending_connect = true; + if (ble_gap_connect(0, &addr, 30_000, ¶ms, &gap_event_trampoline, null) != 0) + { + _pending_connect = false; + return false; + } + return true; +} + +void ble_hw_connect_cancel(uint port) +{ + ble_gap_conn_cancel(); + _pending_connect = false; +} + +bool ble_hw_disconnect(uint port, BLEConn conn) +{ + auto s = find_session(conn.id); + if (s is null) + return false; + + if (ble_gap_terminate(s.nimble_handle, 0x13) != 0) + return false; + return true; +} + +// --- GATT discovery --- + +bool ble_hw_gatt_discover(uint port, BLEConn conn) +{ + auto s = find_session(conn.id); + if (s is null) + return false; + + _discovering_conn = conn.id; + _discover_phase = DiscoverPhase.services; + s.num_chars = 0; + + if (ble_gattc_disc_all_svcs(s.nimble_handle, &svc_discover_cb, null) != 0) + return false; + return true; +} + +// --- GATT read/write --- + +bool ble_hw_gatt_read(uint port, BLEConn conn, ushort handle) +{ + auto s = find_session(conn.id); + if (s is null) + return false; + + if (ble_gattc_read(s.nimble_handle, handle, &gatt_read_cb, cast(void*)cast(size_t)conn.id) != 0) + return false; + return true; +} + +bool ble_hw_gatt_write(uint port, BLEConn conn, ushort handle, const(ubyte)[] data, bool with_response) +{ + auto s = find_session(conn.id); + if (s is null) + return false; + + if (with_response) + { + if (ble_gattc_write_flat(s.nimble_handle, handle, data.ptr, + cast(ushort)data.length, &gatt_write_cb, cast(void*)cast(size_t)conn.id) != 0) + return false; + } + else + { + if (ble_gattc_write_no_rsp_flat(s.nimble_handle, handle, + data.ptr, cast(ushort)data.length) != 0) + return false; + } + return true; +} + +// --- Notifications --- + +bool ble_hw_gatt_subscribe(uint port, BLEConn conn, ushort handle, bool enable) +{ + auto s = find_session(conn.id); + if (s is null) + return false; + + // find CCCD handle (handle + 1 by convention for standard GATT) + ushort cccd_handle = cast(ushort)(handle + 1); + + ubyte[2] cccd_value; + if (enable) + cccd_value[0] = 0x01; // enable notifications + + if (ble_gattc_write_flat(s.nimble_handle, cccd_handle, + cccd_value.ptr, 2, null, null) != 0) + return false; + return true; +} + +// --- Queries --- + +bool ble_hw_get_mac(uint port, ref ubyte[6] mac) +{ + ubyte addr_type; + if (ble_hs_id_infer_auto(0, &addr_type) != 0) + return false; + if (ble_hs_id_copy_addr(addr_type, mac.ptr, null) != 0) + return false; + return true; +} + +byte ble_hw_get_rssi(uint port, BLEConn conn) +{ + auto s = find_session(conn.id); + if (s is null) + return -128; + + byte rssi; + if (ble_gap_conn_rssi(s.nimble_handle, &rssi) != 0) + return -128; + return rssi; +} + +// --- Callbacks --- + +void ble_hw_set_scan_callback(uint port, BLEScanCallback cb) { _scan_cb = cb; } +void ble_hw_set_conn_callback(uint port, BLEConnCallback cb) { _conn_cb = cb; } +void ble_hw_set_discover_callback(uint port, BLEDiscoverCallback cb) { _discover_cb = cb; } +void ble_hw_set_read_callback(uint port, BLEReadCallback cb) { _read_cb = cb; } +void ble_hw_set_write_callback(uint port, BLEWriteCallback cb) { _write_cb = cb; } +void ble_hw_set_notify_callback(uint port, BLENotifyCallback cb) { _notify_cb = cb; } + +// --- Poll --- + +void ble_hw_poll(uint port) +{ + // Drain buffered events from NimBLE host task. + // Events are queued by GAP/GATT callbacks which run on the NimBLE task. + + auto ble = BLE(0); + + // scan results + while (_scan_queue.count > 0) + { + auto report = &_scan_queue.buf[_scan_queue.tail]; + if (_scan_cb !is null) + _scan_cb(ble, *report); + _scan_queue.tail = (_scan_queue.tail + 1) % _scan_queue.buf.length; + _scan_queue.count--; + } + + // connection events + if (_evt_connected) + { + _evt_connected = false; + if (_conn_cb !is null) + _conn_cb(ble, BLEConn(_evt_conn_id), true, BLEError.none); + } + if (_evt_connect_failed) + { + _evt_connect_failed = false; + if (_conn_cb !is null) + _conn_cb(ble, BLEConn(_evt_conn_id), false, BLEError.timeout); + } + if (_evt_disconnected) + { + _evt_disconnected = false; + if (_conn_cb !is null) + _conn_cb(ble, BLEConn(_evt_disconn_id), false, BLEError.none); + remove_session(_evt_disconn_id); + } + + // discovery complete + if (_evt_discover_done) + { + _evt_discover_done = false; + if (_discover_cb !is null) + { + auto s = find_session(_evt_discover_conn); + if (s !is null) + { + BLEGattChar[max_chars_per_session] chars = void; + foreach (i; 0 .. s.num_chars) + { + chars[i].handle = s.chars[i].handle; + chars[i].cccd_handle = s.chars[i].cccd_handle; + chars[i].service_uuid = s.chars[i].service_uuid; + chars[i].char_uuid = s.chars[i].char_uuid; + chars[i].properties = cast(GattCharProps)s.chars[i].properties; + } + _discover_cb(ble, BLEConn(_evt_discover_conn), chars[0 .. s.num_chars], BLEError.none); + } + } + } + if (_evt_discover_failed) + { + _evt_discover_failed = false; + if (_discover_cb !is null) + _discover_cb(ble, BLEConn(_evt_discover_conn), null, BLEError.protocol); + } + + // GATT read/write completions + while (_gatt_queue.count > 0) + { + auto evt = &_gatt_queue.buf[_gatt_queue.tail]; + if (evt.is_read && _read_cb !is null) + _read_cb(ble, BLEConn(evt.conn_id), evt.handle, evt.data[0 .. evt.data_len], evt.error); + else if (!evt.is_read && _write_cb !is null) + _write_cb(ble, BLEConn(evt.conn_id), evt.handle, evt.error); + _gatt_queue.tail = (_gatt_queue.tail + 1) % _gatt_queue.buf.length; + _gatt_queue.count--; + } + + // notifications + while (_notify_queue.count > 0) + { + auto evt = &_notify_queue.buf[_notify_queue.tail]; + if (_notify_cb !is null) + _notify_cb(ble, BLEConn(evt.conn_id), evt.handle, evt.data[0 .. evt.data_len]); + _notify_queue.tail = (_notify_queue.tail + 1) % _notify_queue.buf.length; + _notify_queue.count--; + } +} + + +private: + +enum int ESP_OK = 0; +enum max_sessions = 4; +enum max_chars_per_session = 32; + +// --- Session table --- + +struct SessionCharInfo +{ + ushort handle; + ushort cccd_handle; + GUID service_uuid; + GUID char_uuid; + ushort properties; +} + +struct Session +{ + bool active; + ubyte id; // our BLEConn.id + ushort nimble_handle; // NimBLE connection handle + SessionCharInfo[max_chars_per_session] chars; + ubyte num_chars; +} + +Session* find_session(ubyte id) +{ + foreach (ref s; _sessions[0 .. _num_sessions]) + { + if (s.active && s.id == id) + return &s; + } + return null; +} + +Session* find_session_by_nimble(ushort nimble_handle) +{ + foreach (ref s; _sessions[0 .. _num_sessions]) + { + if (s.active && s.nimble_handle == nimble_handle) + return &s; + } + return null; +} + +Session* alloc_session(ushort nimble_handle) +{ + if (_num_sessions >= max_sessions) + return null; + auto s = &_sessions[_num_sessions++]; + s.active = true; + s.id = _next_conn_id++; + s.nimble_handle = nimble_handle; + s.num_chars = 0; + return s; +} + +void remove_session(ubyte id) +{ + foreach (i, ref s; _sessions[0 .. _num_sessions]) + { + if (s.id == id) + { + _sessions[i] = _sessions[_num_sessions - 1]; + _num_sessions--; + return; + } + } +} + +// --- Event ring buffers --- + +struct RingBuffer(T, uint N) +{ + T[N] buf; + uint head; + uint tail; + uint count; + + T* push() nothrow @nogc + { + if (count >= N) + return null; // drop oldest would be: tail = (tail + 1) % N; count--; + auto p = &buf[head]; + head = (head + 1) % N; + count++; + return p; + } +} + +struct GattCompletionEvent +{ + ubyte conn_id; + ushort handle; + bool is_read; + BLEError error; + ubyte data_len; + ubyte[247] data; +} + +struct NotifyEvent +{ + ubyte conn_id; + ushort handle; + ubyte data_len; + ubyte[247] data; +} + +// --- Module state --- + +__gshared bool _opened; +__gshared bool _pending_connect; +__gshared ubyte _next_conn_id; + +__gshared Session[max_sessions] _sessions; +__gshared ubyte _num_sessions; + +__gshared BLEScanCallback _scan_cb; +__gshared BLEConnCallback _conn_cb; +__gshared BLEDiscoverCallback _discover_cb; +__gshared BLEReadCallback _read_cb; +__gshared BLEWriteCallback _write_cb; +__gshared BLENotifyCallback _notify_cb; + +// scan result ring buffer (set from NimBLE task, drained from main loop) +__gshared RingBuffer!(BLEAdvReport, 16) _scan_queue; + +// GATT completion ring buffer +__gshared RingBuffer!(GattCompletionEvent, 16) _gatt_queue; + +// notification ring buffer +__gshared RingBuffer!(NotifyEvent, 16) _notify_queue; + +// connection event flags (set from NimBLE task) +__gshared bool _evt_connected; +__gshared bool _evt_connect_failed; +__gshared bool _evt_disconnected; +__gshared ubyte _evt_conn_id; +__gshared ubyte _evt_disconn_id; + +// discovery state +enum DiscoverPhase : ubyte { idle, services, chars } +__gshared DiscoverPhase _discover_phase; +__gshared ubyte _discovering_conn; +__gshared bool _evt_discover_done; +__gshared bool _evt_discover_failed; +__gshared ubyte _evt_discover_conn; + +// service discovery iteration state (used from NimBLE task callbacks) +__gshared ble_gatt_svc[16] _discovered_svcs; +__gshared ubyte _num_discovered_svcs; +__gshared ubyte _current_svc_idx; +__gshared GUID _current_svc_uuid; + + +// --- GAP event trampoline (called from NimBLE host task) --- + +extern(C) int gap_event_trampoline(ble_gap_event* event, void*) nothrow @nogc +{ + if (event is null) + return 0; + + switch (event.type) + { + case BLE_GAP_EVENT_DISC: + // scan result + auto report = _scan_queue.push(); + if (report !is null) + { + auto disc = &event.disc; + // NimBLE addr is LSB-first, we want MSB-first + foreach (i; 0 .. 6) + report.addr[i] = disc.addr.val[5 - i]; + report.addr_type = cast(BLEAddrType)disc.addr.type; + report.rssi = disc.rssi; + report.tx_power = -128; // not in base event + + ubyte len = disc.length_data > 62 ? 62 : disc.length_data; + report.data_len = len; + if (len > 0) + report.data_buf[0 .. len] = disc.data[0 .. len]; + + report.adv_type = disc.event_type == 0 ? BLEAdvType.connectable : BLEAdvType.nonconnectable; + } + return 0; + + case BLE_GAP_EVENT_CONNECT: + _pending_connect = false; + if (event.connect.status == 0) + { + auto s = alloc_session(event.connect.conn_handle); + if (s !is null) + { + _evt_conn_id = s.id; + _evt_connected = true; + } + } + else + { + _evt_conn_id = ubyte.max; + _evt_connect_failed = true; + } + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + auto s = find_session_by_nimble(event.disconnect.conn.conn_handle); + if (s !is null) + { + _evt_disconn_id = s.id; + _evt_disconnected = true; + s.active = false; + } + return 0; + + case BLE_GAP_EVENT_NOTIFY_RX: + auto s = find_session_by_nimble(event.notify_rx.conn_handle); + if (s !is null) + { + auto evt = _notify_queue.push(); + if (evt !is null) + { + evt.conn_id = s.id; + evt.handle = event.notify_rx.attr_handle; + auto om = event.notify_rx.om; + // copy mbuf chain to flat buffer + evt.data_len = 0; + while (om !is null && evt.data_len < evt.data.length) + { + ushort copy = om.om_len; + if (evt.data_len + copy > evt.data.length) + copy = cast(ushort)(evt.data.length - evt.data_len); + evt.data[evt.data_len .. evt.data_len + copy] = om.om_data[0 .. copy]; + evt.data_len += copy; + om = om.om_next; + } + } + } + return 0; + + default: + return 0; + } +} + +// --- GATT service discovery callback (NimBLE task) --- + +extern(C) int svc_discover_cb(ushort conn_handle, const(ble_gatt_error)* error, + const(ble_gatt_svc)* service, void*) nothrow @nogc +{ + if (error !is null && error.status == 0 && service !is null) + { + // accumulate services + if (_num_discovered_svcs < _discovered_svcs.length) + _discovered_svcs[_num_discovered_svcs++] = *service; + return 0; + } + + // discovery complete (error.status != 0 means end of list or actual error) + if (_num_discovered_svcs == 0) + { + _evt_discover_conn = _discovering_conn; + _evt_discover_done = true; + _discover_phase = DiscoverPhase.idle; + return 0; + } + + // start characteristic discovery for first service + _current_svc_idx = 0; + return discover_next_svc_chars(conn_handle); +} + +int discover_next_svc_chars(ushort conn_handle) nothrow @nogc +{ + while (_current_svc_idx < _num_discovered_svcs) + { + auto svc = &_discovered_svcs[_current_svc_idx]; + _current_svc_uuid = nimble_uuid_to_guid(&svc.uuid); + _discover_phase = DiscoverPhase.chars; + + if (ble_gattc_disc_all_chrs(conn_handle, svc.start_handle, svc.end_handle, + &chr_discover_cb, null) == 0) + return 0; + + _current_svc_idx++; + } + + // all services done + _evt_discover_conn = _discovering_conn; + _evt_discover_done = true; + _discover_phase = DiscoverPhase.idle; + _num_discovered_svcs = 0; + return 0; +} + +// --- GATT characteristic discovery callback (NimBLE task) --- + +extern(C) int chr_discover_cb(ushort conn_handle, const(ble_gatt_error)* error, + const(ble_gatt_chr)* chr, void*) nothrow @nogc +{ + if (error !is null && error.status == 0 && chr !is null) + { + auto s = find_session_by_nimble(conn_handle); + if (s !is null && s.num_chars < max_chars_per_session) + { + auto ci = &s.chars[s.num_chars++]; + ci.handle = chr.val_handle; + ci.cccd_handle = 0; // TODO: discover descriptors for CCCD + ci.service_uuid = _current_svc_uuid; + ci.char_uuid = nimble_uuid_to_guid(&chr.uuid); + ci.properties = chr.properties; + } + return 0; + } + + // this service's chars done, move to next + _current_svc_idx++; + return discover_next_svc_chars(conn_handle); +} + +// --- GATT read callback (NimBLE task) --- + +extern(C) int gatt_read_cb(ushort conn_handle, const(ble_gatt_error)* error, + ble_gatt_attr* attr, void* cb_arg) nothrow @nogc +{ + ubyte conn_id = cast(ubyte)cast(size_t)cb_arg; + auto evt = _gatt_queue.push(); + if (evt is null) + return 0; + + evt.conn_id = conn_id; + evt.is_read = true; + + if (error !is null && error.status == 0 && attr !is null) + { + evt.handle = attr.handle; + evt.error = BLEError.none; + // copy mbuf to flat buffer + evt.data_len = 0; + auto om = attr.om; + while (om !is null && evt.data_len < evt.data.length) + { + ushort copy = om.om_len; + if (evt.data_len + copy > evt.data.length) + copy = cast(ushort)(evt.data.length - evt.data_len); + evt.data[evt.data_len .. evt.data_len + copy] = om.om_data[0 .. copy]; + evt.data_len += copy; + om = om.om_next; + } + } + else + { + evt.handle = attr !is null ? attr.handle : 0; + evt.error = BLEError.protocol; + evt.data_len = 0; + } + return 0; +} + +// --- GATT write callback (NimBLE task) --- + +extern(C) int gatt_write_cb(ushort conn_handle, const(ble_gatt_error)* error, + ble_gatt_attr* attr, void* cb_arg) nothrow @nogc +{ + ubyte conn_id = cast(ubyte)cast(size_t)cb_arg; + auto evt = _gatt_queue.push(); + if (evt is null) + return 0; + + evt.conn_id = conn_id; + evt.handle = attr !is null ? attr.handle : 0; + evt.is_read = false; + evt.data_len = 0; + evt.error = (error !is null && error.status == 0) ? BLEError.none : BLEError.protocol; + return 0; +} + +// --- UUID conversion --- + +GUID nimble_uuid_to_guid(const(ble_uuid_any)* uuid) nothrow @nogc +{ + GUID g; + if (uuid.u.type == 16) // BLE_UUID_TYPE_16 + { + // BT SIG base: 0000xxxx-0000-1000-8000-00805F9B34FB + g.data1 = uuid.u16.value; + g.data3 = 0x1000; + g.data4 = [0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB]; + } + else if (uuid.u.type == 32) // BLE_UUID_TYPE_32 + { + g.data1 = uuid.u32.value; + g.data3 = 0x1000; + g.data4 = [0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB]; + } + else if (uuid.u.type == 128) // BLE_UUID_TYPE_128 + { + // NimBLE stores 128-bit UUIDs in little-endian byte order + auto v = uuid.u128.value; + g.data1 = v[12] | (cast(uint)v[13] << 8) | (cast(uint)v[14] << 16) | (cast(uint)v[15] << 24); + g.data2 = cast(ushort)(v[10] | (cast(ushort)v[11] << 8)); + g.data3 = cast(ushort)(v[8] | (cast(ushort)v[9] << 8)); + g.data4[0 .. 2] = v[6 .. 8]; // big-endian in GUID + g.data4[2 .. 8] = v[0 .. 6]; + } + return g; +} + + +// ════════════════════════════════════════════════════════════════════ +// NimBLE C API declarations +// ════════════════════════════════════════════════════════════════════ + +struct ble_addr_t +{ + ubyte type; + ubyte[6] val; +} + +struct ble_gap_disc_params +{ + ushort itvl; + ushort window; + ubyte filter_policy; + ubyte limited; + ubyte passive; + ubyte filter_duplicates; +} + +struct ble_gap_conn_params +{ + ushort scan_itvl; + ushort scan_window; + ushort itvl_min; + ushort itvl_max; + ushort latency; + ushort supervision_timeout; + ushort min_ce_len; + ushort max_ce_len; +} + +struct ble_gap_adv_params +{ + ubyte conn_mode; + ubyte disc_mode; + ushort itvl_min; + ushort itvl_max; + ubyte channel_map; + ubyte filter_policy; + ubyte high_duty_cycle; +} + +// NimBLE GAP event structure (simplified) +struct ble_gap_event +{ + ubyte type; + ubyte[3] _pad; + + struct ConnectData { int status; ushort conn_handle; } + struct DisconnectData { int reason; ble_gap_conn_desc conn; } + struct DiscData { ubyte event_type; ubyte length_data; const(ubyte)* data; byte rssi; + ble_addr_t addr; } + struct NotifyRxData { ushort conn_handle; ushort attr_handle; ubyte indication; + os_mbuf* om; } + + union + { + ConnectData connect; + DisconnectData disconnect; + DiscData disc; + NotifyRxData notify_rx; + } +} + +struct ble_gap_conn_desc +{ + ble_addr_t our_id_addr; + ble_addr_t peer_id_addr; + ble_addr_t our_ota_addr; + ble_addr_t peer_ota_addr; + ushort conn_handle; + ushort conn_itvl; + ushort conn_latency; + ushort supervision_timeout; + ubyte role; + ubyte encrypted; + ubyte authenticated; + ubyte bonded; +} + +struct ble_gatt_error +{ + ushort status; + ushort att_handle; +} + +struct ble_uuid +{ + ubyte type; // 16, 32, or 128 +} + +struct ble_uuid16 +{ + ble_uuid u; + ushort value; +} + +struct ble_uuid32 +{ + ble_uuid u; + uint value; +} + +struct ble_uuid128 +{ + ble_uuid u; + ubyte[16] value; +} + +union ble_uuid_any +{ + ble_uuid u; + ble_uuid16 u16; + ble_uuid32 u32; + ble_uuid128 u128; +} + +struct ble_gatt_svc +{ + ushort start_handle; + ushort end_handle; + ble_uuid_any uuid; +} + +struct ble_gatt_chr +{ + ushort def_handle; + ushort val_handle; + ushort properties; + ble_uuid_any uuid; +} + +struct ble_gatt_attr +{ + ushort handle; + ushort offset; + os_mbuf* om; +} + +// NimBLE mbuf +struct os_mbuf +{ + os_mbuf* om_next; + ubyte* om_data; + ushort om_len; + ushort om_flags; + // ... more fields follow but we only need these +} + +// GAP event types +enum : ubyte +{ + BLE_GAP_EVENT_CONNECT = 0, + BLE_GAP_EVENT_DISCONNECT = 1, + BLE_GAP_EVENT_DISC = 7, + BLE_GAP_EVENT_NOTIFY_RX = 12, +} + +// C shim functions +extern(C) nothrow @nogc +{ + int ow_ble_init(); + void ow_ble_deinit(); + void ow_ble_set_gap_callback(int function(ble_gap_event*, void*) nothrow @nogc cb); +} + +// Direct NimBLE calls +extern(C) nothrow @nogc +{ + int ble_gap_disc(ubyte own_addr_type, int duration_ms, const(ble_gap_disc_params)* params, + int function(ble_gap_event*, void*) cb, void* cb_arg); + int ble_gap_disc_cancel(); + int ble_gap_connect(ubyte own_addr_type, const(ble_addr_t)* peer_addr, int duration_ms, + const(ble_gap_conn_params)* params, int function(ble_gap_event*, void*) cb, void* cb_arg); + int ble_gap_conn_cancel(); + int ble_gap_terminate(ushort conn_handle, ubyte hci_reason); + int ble_gap_conn_rssi(ushort conn_handle, byte* rssi); + + int ble_gap_adv_set_data(const(ubyte)* data, int data_len); + int ble_gap_adv_rsp_set_data(const(ubyte)* data, int data_len); + int ble_gap_adv_start(ubyte own_addr_type, const(ble_addr_t)* direct_addr, int duration_ms, + const(ble_gap_adv_params)* params, int function(ble_gap_event*, void*) cb, void* cb_arg); + int ble_gap_adv_stop(); + + int ble_gattc_disc_all_svcs(ushort conn_handle, + int function(ushort, const(ble_gatt_error)*, const(ble_gatt_svc)*, void*) cb, void* cb_arg); + int ble_gattc_disc_all_chrs(ushort conn_handle, ushort start_handle, ushort end_handle, + int function(ushort, const(ble_gatt_error)*, const(ble_gatt_chr)*, void*) cb, void* cb_arg); + int ble_gattc_read(ushort conn_handle, ushort attr_handle, + int function(ushort, const(ble_gatt_error)*, ble_gatt_attr*, void*) cb, void* cb_arg); + int ble_gattc_write_flat(ushort conn_handle, ushort attr_handle, const(void)* data, ushort data_len, + int function(ushort, const(ble_gatt_error)*, ble_gatt_attr*, void*) cb, void* cb_arg); + int ble_gattc_write_no_rsp_flat(ushort conn_handle, ushort attr_handle, const(void)* data, ushort data_len); + + int ble_hs_id_infer_auto(int privacy, ubyte* out_addr_type); + int ble_hs_id_copy_addr(ubyte addr_type, ubyte* out_addr, int* out_is_nrpa); +} diff --git a/src/sys/esp32/can.d b/src/sys/esp32/can.d index c3aa3cb..54519f5 100644 --- a/src/sys/esp32/can.d +++ b/src/sys/esp32/can.d @@ -30,7 +30,7 @@ else enum bool has_can_fd = false; static if (num_can > 0): -bool can_open(uint port, ref const CanConfig cfg) +bool can_hw_open(uint port, ref const CanConfig cfg) { if (port >= num_can) return false; @@ -43,7 +43,7 @@ bool can_open(uint port, ref const CanConfig cfg) return _handles[port] !is null; } -void can_close(uint port) +void can_hw_close(uint port) { if (port >= num_can || _handles[port] is null) return; @@ -52,20 +52,20 @@ void can_close(uint port) _handles[port] = null; } -int can_transmit(uint port, ref const CanFrame frame) +bool can_hw_transmit(uint port, ref const CanFrame frame) { if (port >= num_can || _handles[port] is null) - return -1; + return false; twai_message_t msg; msg.flags = cast(uint)frame.extended | (cast(uint)frame.rtr << 1); msg.identifier = frame.id; msg.data_length_code = frame.dlc; if (frame.dlc > 0) msg.data[0 .. frame.dlc] = frame.data[0 .. frame.dlc]; - return twai_transmit_v2(_handles[port], &msg, 0) == ESP_OK ? 0 : -1; + return twai_transmit_v2(_handles[port], &msg, 0) == ESP_OK; } -bool can_receive(uint port, out CanFrame frame) +bool can_hw_receive(uint port, out CanFrame frame) { if (port >= num_can || _handles[port] is null) return false; @@ -81,7 +81,7 @@ bool can_receive(uint port, out CanFrame frame) return true; } -CanError can_check_errors(uint port) +CanError can_hw_check_errors(uint port) { if (port >= num_can || _handles[port] is null) return CanError.none; @@ -97,7 +97,7 @@ CanError can_check_errors(uint port) return err; } -CanBusState can_bus_state(uint port) +CanBusState can_hw_bus_state(uint port) { if (port >= num_can || _handles[port] is null) return CanBusState.bus_off; @@ -113,7 +113,7 @@ CanBusState can_bus_state(uint port) return CanBusState.error_active; } -ubyte can_tx_error_count(uint port) +ubyte can_hw_tx_error_count(uint port) { if (port >= num_can || _handles[port] is null) return 0; @@ -123,7 +123,7 @@ ubyte can_tx_error_count(uint port) return info.tx_error_counter > 255 ? 255 : cast(ubyte)info.tx_error_counter; } -ubyte can_rx_error_count(uint port) +ubyte can_hw_rx_error_count(uint port) { if (port >= num_can || _handles[port] is null) return 0; @@ -133,7 +133,7 @@ ubyte can_rx_error_count(uint port) return info.rx_error_counter > 255 ? 255 : cast(ubyte)info.rx_error_counter; } -size_t can_rx_available(uint port) +size_t can_hw_rx_available(uint port) { if (port >= num_can || _handles[port] is null) return 0; @@ -143,28 +143,28 @@ size_t can_rx_available(uint port) return cast(size_t)info.msgs_to_rx; } -void can_rx_flush(uint port) +void can_hw_rx_flush(uint port) { if (port >= num_can || _handles[port] is null) return; twai_clear_receive_queue_v2(_handles[port]); } -void can_tx_abort(uint port) +void can_hw_tx_abort(uint port) { if (port >= num_can || _handles[port] is null) return; twai_clear_transmit_queue_v2(_handles[port]); } -bool can_bus_recover(uint port) +bool can_hw_bus_recover(uint port) { if (port >= num_can || _handles[port] is null) return false; return twai_initiate_recovery_v2(_handles[port]) == ESP_OK; } -void can_poll(uint port) +void can_hw_poll(uint port) { // TWAI driver buffers RX internally } diff --git a/src/sys/esp32/uart.d b/src/sys/esp32/uart.d index 225466d..e96891b 100644 --- a/src/sys/esp32/uart.d +++ b/src/sys/esp32/uart.d @@ -28,7 +28,7 @@ enum uint uart_clock_hz = 80_000_000; enum bool has_irq_driven_uart = false; enum bool has_dma_driven_uart = false; -bool uart_hw_open(uint id, UartConfig cfg) +bool uart_hw_open(uint id, ref const UartConfig cfg) { if (id >= num_uarts) return false; diff --git a/src/sys/esp32/wifi.d b/src/sys/esp32/wifi.d index 2644dd4..4257c08 100644 --- a/src/sys/esp32/wifi.d +++ b/src/sys/esp32/wifi.d @@ -21,7 +21,7 @@ nothrow @nogc: enum uint num_wifi = 1; -bool wifi_open(uint port, ref const WifiConfig cfg) +bool wifi_hw_open(uint port, ref const WifiConfig cfg) { if (_opened) return false; @@ -45,7 +45,7 @@ bool wifi_open(uint port, ref const WifiConfig cfg) return true; } -void wifi_close(uint port) +void wifi_hw_close(uint port) { if (!_opened) return; @@ -57,19 +57,24 @@ void wifi_close(uint port) _opened = false; _event_cb = null; _rx_cb = null; + _raw_rx_cb = null; _evt_sta_connected = false; _evt_sta_disconnected = false; _evt_ap_started = false; _evt_ap_stopped = false; } -bool wifi_set_mode(uint port, WifiMode mode) +bool wifi_hw_set_mode(uint port, WifiMode mode) { - // WifiMode enum values match ESP-IDF wifi_mode_t: 0=none,1=sta,2=ap,3=apsta - return esp_wifi_set_mode(cast(int)mode) == ESP_OK; + if (mode == WifiMode.monitor) + return false; + + // Map to ESP-IDF wifi_mode_t: 0=none,1=sta,2=ap,3=apsta + static immutable ubyte[5] mode_map = [0, 0, 1, 2, 3]; + return esp_wifi_set_mode(mode_map[mode]) == ESP_OK; } -bool wifi_sta_configure(uint port, ref const WifiStaConfig cfg) +bool wifi_hw_sta_configure(uint port, ref const WifiStaConfig cfg) { // Stack buffers for null-termination (SSID max 32, password max 64) char[33] ssid_z = 0; @@ -88,19 +93,19 @@ bool wifi_sta_configure(uint port, ref const WifiStaConfig cfg) has_bssid ? cfg.bssid.ptr : null) != 0; } -bool wifi_sta_connect(uint port) +bool wifi_hw_sta_connect(uint port) { _evt_sta_connected = false; _evt_sta_disconnected = false; return esp_wifi_connect() == ESP_OK; } -bool wifi_sta_disconnect(uint port) +bool wifi_hw_sta_disconnect(uint port) { return esp_wifi_disconnect() == ESP_OK; } -bool wifi_ap_configure(uint port, ref const WifiApConfig cfg) +bool wifi_hw_ap_configure(uint port, ref const WifiApConfig cfg) { char[33] ssid_z = 0; char[65] pw_z = 0; @@ -116,7 +121,7 @@ bool wifi_ap_configure(uint port, ref const WifiApConfig cfg) cfg.channel, cfg.max_clients, cfg.hidden ? 1 : 0) != 0; } -size_t wifi_ap_get_clients(uint port, WifiStaInfo[] buf) +size_t wifi_hw_ap_get_clients(uint port, WifiStaInfo[] buf) { // TODO: esp_wifi_ap_get_sta_list return 0; @@ -124,18 +129,18 @@ size_t wifi_ap_get_clients(uint port, WifiStaInfo[] buf) // Scanning -bool wifi_scan_start(uint port, ref const WifiScanConfig cfg) +bool wifi_hw_scan_start(uint port, ref const WifiScanConfig cfg) { // TODO: esp_wifi_scan_start return false; } -void wifi_scan_stop(uint port) +void wifi_hw_scan_stop(uint port) { // TODO: esp_wifi_scan_stop } -size_t wifi_scan_get_results(uint port, WifiScanResult[] buf) +size_t wifi_hw_scan_get_results(uint port, WifiScanResult[] buf) { // TODO: esp_wifi_scan_get_ap_records return 0; @@ -143,28 +148,42 @@ size_t wifi_scan_get_results(uint port, WifiScanResult[] buf) // Frame TX/RX -int wifi_tx(uint port, WifiVif vif, const(ubyte)[] data) +bool wifi_hw_tx(uint port, WifiVif vif, const(ubyte)[] data) { if (data.length == 0) - return -1; - return esp_wifi_internal_tx(cast(int)vif, cast(void*)data.ptr, cast(ushort)data.length) == ESP_OK ? 0 : -1; + return false; + return esp_wifi_internal_tx(cast(int)vif, cast(void*)data.ptr, cast(ushort)data.length) == ESP_OK; } -void wifi_set_rx_callback(uint port, WifiRxCallback cb) +void wifi_hw_set_rx_callback(uint port, WifiRxCallback cb) { _rx_cb = cb; ow_wifi_set_rx_callback(cb !is null ? &rx_trampoline : null); } +// Raw 802.11 TX/RX + +bool wifi_hw_raw_tx(uint port, const(ubyte)[] frame) +{ + // TODO: esp_wifi_80211_tx + return false; +} + +void wifi_hw_set_raw_rx_callback(uint port, WifiRawRxCallback cb) +{ + // TODO: esp_wifi_set_promiscuous + esp_wifi_set_promiscuous_rx_cb + _raw_rx_cb = cb; +} + // Queries -bool wifi_get_mac(uint port, WifiVif vif, ref ubyte[6] mac) +bool wifi_hw_get_mac(uint port, WifiVif vif, ref ubyte[6] mac) { // ESP_MAC_WIFI_STA=0, ESP_MAC_WIFI_SOFTAP=1 return esp_read_mac(mac.ptr, cast(int)vif) == ESP_OK; } -ubyte wifi_get_channel(uint port) +ubyte wifi_hw_get_channel(uint port) { ubyte primary = void; int second = void; @@ -173,27 +192,27 @@ ubyte wifi_get_channel(uint port) return primary; } -byte wifi_get_rssi(uint port) +byte wifi_hw_get_rssi(uint port) { // TODO: esp_wifi_sta_get_ap_info -> rssi return -127; } -bool wifi_set_tx_power(uint port, byte power_dbm) +bool wifi_hw_set_tx_power(uint port, byte power_dbm) { return esp_wifi_set_max_tx_power(power_dbm) == ESP_OK; } // Events -void wifi_set_event_callback(uint port, WifiEventCallback cb) +void wifi_hw_set_event_callback(uint port, WifiEventCallback cb) { _event_cb = cb; } // Poll -- check ISR-set flags and deliver events to D callback. // Called from main loop since ESP events arrive on the event task. -void wifi_poll(uint port) +void wifi_hw_poll(uint port) { if (_event_cb is null) return; @@ -243,6 +262,7 @@ enum : int __gshared bool _opened; __gshared WifiEventCallback _event_cb; __gshared WifiRxCallback _rx_cb; +__gshared WifiRawRxCallback _raw_rx_cb; // Flags set from ESP event task, polled from main loop __gshared bool _evt_sta_connected; diff --git a/src/sys/windows/ble.d b/src/sys/windows/ble.d new file mode 100644 index 0000000..4ec154d --- /dev/null +++ b/src/sys/windows/ble.d @@ -0,0 +1,1987 @@ +// Windows BLE driver -- WinRT Bluetooth LE APIs +// +// Uses Windows.Devices.Bluetooth.* WinRT classes via COM vtable calls. +// All WinRT async operations are polled from ble_hw_poll(). WinRT +// callbacks (advertisements, notifications, connection status) fire on +// thread pool threads and are buffered into thread-safe queues, +// then delivered from ble_hw_poll() on the main loop. +// +// Windows has one BLE radio (port 0). Multiple radios are not +// distinguished by WinRT -- it uses the system default adapter. +module sys.windows.ble; + +version (Windows): + +import urt.atomic : atomicFetchAdd, atomicFetchSub, atomicLoad, atomicStore; +import urt.log; +import urt.mem.allocator : defaultAllocator; +import urt.thread : ThreadSafeQueue; +import urt.uuid : GUID; + +import sys.baremetal.ble; + +nothrow @nogc: + +alias log = Log!"ble"; + +enum uint num_ble = 1; + + +// ════════════════════════════════════════════════════════════════════ +// Driver API implementation +// ════════════════════════════════════════════════════════════════════ + +bool ble_hw_open(uint port, ref const BLEConfig cfg) +{ + if (_opened) + return true; + + if (!g_winrt.initialized && !g_winrt.init()) + { + log.error("WinRT initialization failed"); + return false; + } + + _opened = true; + return true; +} + +void ble_hw_close(uint port) +{ + if (!_opened) + return; + + ble_hw_scan_stop(port); + stop_all_publishers(); + + // disconnect all sessions + foreach (ref s; _sessions[0 .. _num_sessions]) + { + if (s.active) + release_session(&s); + } + _num_sessions = 0; + + cleanup_connect(); + + _opened = false; + _scan_cb = null; + _conn_cb = null; + _discover_cb = null; + _read_cb = null; + _write_cb = null; + _notify_cb = null; +} + +// --- Scanning --- + +bool ble_hw_scan_start(uint port, ref const BLEScanConfig cfg) +{ + if (_watcher !is null) + return true; // already scanning + + _watcher = g_winrt.activate!IBluetoothLEAdvertisementWatcher( + "Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementWatcher"w, + &IID_IBluetoothLEAdvertisementWatcher); + + if (_watcher is null) + { + log.error("failed to create advertisement watcher"); + return false; + } + + _watcher.put_ScanningMode(cfg.active ? BluetoothLEScanningMode.active : BluetoothLEScanningMode.passive); + + _adv_handler = defaultAllocator().allocT!AdvertisementReceivedHandler; + _adv_handler.callback = &on_advertisement_received; + + EventRegistrationToken token; + if (_watcher.add_Received(cast(IUnknown)_adv_handler, &token) < 0) + { + log.error("failed to subscribe to advertisements"); + return false; + } + _received_token = token; + + // register Stopped handler to detect unexpected watcher shutdown + atomicStore(_watcher_stopped, 0u); + _stopped_handler = defaultAllocator().allocT!WatcherStoppedHandler; + _stopped_handler.flag = &_watcher_stopped; + EventRegistrationToken stopped_token; + _watcher.add_Stopped(cast(IUnknown)_stopped_handler, &stopped_token); + _stopped_token = stopped_token; + + if (_watcher.Start() < 0) + { + log.error("failed to start scanner"); + return false; + } + + return true; +} + +void ble_hw_scan_stop(uint port) +{ + if (_watcher !is null) + { + _watcher.Stop(); + _watcher.remove_Received(_received_token); + _watcher.remove_Stopped(_stopped_token); + _watcher.Release(); + _watcher = null; + } + if (_adv_handler !is null) + { + defaultAllocator().freeT(_adv_handler); + _adv_handler = null; + } + if (_stopped_handler !is null) + { + defaultAllocator().freeT(_stopped_handler); + _stopped_handler = null; + } + atomicStore(_watcher_stopped, 0u); +} + +// --- Advertising --- + +BLEAdv ble_hw_adv_start(uint port, ref const BLEAdvConfig cfg) +{ + if (_num_publishers >= max_publishers) + return BLEAdv.init; + + auto publisher = g_winrt.activate!IBluetoothLEAdvertisementPublisher( + "Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementPublisher"w, + &IID_IBluetoothLEAdvertisementPublisher); + + if (publisher is null) + { + log.error("failed to create advertisement publisher"); + return BLEAdv.init; + } + + IBluetoothLEAdvertisement adv; + publisher.get_Advertisement(&adv); + + if (adv !is null) + { + set_adv_data_from_raw(adv, cfg.adv_data); + adv.Release(); + } + + if (publisher.Start() < 0) + { + log.error("failed to start advertising"); + publisher.Release(); + return BLEAdv.init; + } + + ubyte id = _next_adv_id++; + _publishers[_num_publishers++] = AdvSlot(id, publisher); + return BLEAdv(id); +} + +void ble_hw_adv_stop(uint port, BLEAdv adv) +{ + foreach (i, ref slot; _publishers[0 .. _num_publishers]) + { + if (slot.id == adv.id) + { + slot.publisher.Stop(); + slot.publisher.Release(); + _publishers[i] = _publishers[_num_publishers - 1]; + _num_publishers--; + return; + } + } +} + +// --- Connection --- + +bool ble_hw_connect(uint port, ref const ubyte[6] peer_addr, BLEAddrType addr_type, ref const BLEConnConfig cfg) +{ + if (_pending_connect.async_op !is null) + { + log.warning("connection already in progress"); + return false; + } + + if (_device_statics is null) + { + _device_statics = g_winrt.get_factory!IBluetoothLEDeviceStatics( + "Windows.Devices.Bluetooth.BluetoothLEDevice"w, + &IID_IBluetoothLEDeviceStatics); + + if (_device_statics is null) + { + log.error("failed to get BluetoothLEDevice statics"); + return false; + } + } + + ulong ble_addr = mac_to_ble_addr(peer_addr); + + IInspectable async_op; + if (_device_statics.FromBluetoothAddressAsync(ble_addr, &async_op) < 0 || async_op is null) + { + log.error("FromBluetoothAddressAsync failed"); + return false; + } + + _pending_connect.async_op = async_op; + _pending_connect.peer_addr = peer_addr; + return true; +} + +void ble_hw_connect_cancel(uint port) +{ + cleanup_connect(); +} + +bool ble_hw_disconnect(uint port, BLEConn conn) +{ + auto s = find_session(conn.id); + if (s is null) + return false; + + release_session(s); + remove_session(conn.id); + return true; +} + +// --- GATT discovery --- + +bool ble_hw_gatt_discover(uint port, BLEConn conn) +{ + auto s = find_session(conn.id); + if (s is null || s.device3 is null) + return false; + + IInspectable gatt_op; + s.device3.GetGattServicesWithCacheModeAsync(1, &gatt_op); // 1 = Uncached + if (gatt_op is null) + return false; + + _pending_discover.async_op = gatt_op; + _pending_discover.conn_id = conn.id; + _pending_discover.phase = DiscoverPhase.services; + s.num_chars = 0; + + return true; +} + +// --- GATT read/write --- + +bool ble_hw_gatt_read(uint port, BLEConn conn, ushort handle) +{ + if (_num_pending_gatt >= max_gatt_ops) + return false; + + auto s = find_session(conn.id); + if (s is null) + return false; + + auto gc = find_session_char(s, handle); + if (gc is null || gc.characteristic is null) + return false; + + IInspectable async_op; + gc.characteristic.ReadValueWithCacheModeAsync(1, &async_op); // Uncached + if (async_op is null) + return false; + + _pending_gatt[_num_pending_gatt++] = PendingGattOp( + async_op, conn.id, handle, GattOpType.read); + return true; +} + +bool ble_hw_gatt_write(uint port, BLEConn conn, ushort handle, const(ubyte)[] data, bool with_response) +{ + if (_num_pending_gatt >= max_gatt_ops) + return false; + + auto s = find_session(conn.id); + if (s is null) + return false; + + auto gc = find_session_char(s, handle); + if (gc is null || gc.characteristic is null) + return false; + + auto buf = defaultAllocator().allocT!MemoryBuffer; + if (buf is null) + return false; + + // clone data -- packet payload may be freed before async completes + ubyte* data_copy = cast(ubyte*)defaultAllocator().alloc(data.length).ptr; + if (data_copy is null && data.length > 0) + { + defaultAllocator().freeT(buf); + return false; + } + data_copy[0 .. data.length] = data[]; + buf.set(data_copy[0 .. data.length]); + + IInspectable async_op; + if (with_response) + gc.characteristic.WriteValueAsync(cast(IBuffer)buf, &async_op); + else + gc.characteristic.WriteValueWithOptionAsync(cast(IBuffer)buf, 1, &async_op); // WriteWithoutResponse + + if (async_op is null) + { + buf.Release(); // frees both MemoryBuffer and data_copy + return false; + } + + _pending_gatt[_num_pending_gatt++] = PendingGattOp( + async_op, conn.id, handle, + with_response ? GattOpType.write : GattOpType.write_no_response); + return true; +} + +// --- Notifications --- + +bool ble_hw_gatt_subscribe(uint port, BLEConn conn, ushort handle, bool enable) +{ + auto s = find_session(conn.id); + if (s is null) + return false; + + auto gc = find_session_char(s, handle); + if (gc is null || gc.characteristic is null) + return false; + + if (enable) + { + if (gc.notify_handler !is null) + return true; // already subscribed + + auto handler = defaultAllocator().allocT!GattValueChangedHandler; + handler.conn_id = conn.id; + handler.attr_handle = handle; + handler.callback = &on_gatt_notification; + + EventRegistrationToken token; + if (gc.characteristic.add_ValueChanged(cast(IUnknown)handler, &token) < 0) + { + defaultAllocator().freeT(handler); + return false; + } + gc.notify_handler = handler; + gc.notify_token = token; + + // write CCCD + GattCharacteristicProperties props; + gc.characteristic.get_CharacteristicProperties(&props); + + auto cccd_value = (props & GattCharacteristicProperties.notify) != 0 + ? GattClientCharacteristicConfigurationDescriptorValue.notify + : GattClientCharacteristicConfigurationDescriptorValue.indicate; + + IInspectable async_op; + gc.characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(cccd_value, &async_op); + if (async_op !is null) + (cast(IUnknown)async_op).Release(); // fire and forget + } + else + { + if (gc.notify_handler is null) + return true; // not subscribed + + gc.characteristic.remove_ValueChanged(gc.notify_token); + defaultAllocator().freeT(gc.notify_handler); + gc.notify_handler = null; + + // write CCCD to disable + IInspectable async_op; + gc.characteristic.WriteClientCharacteristicConfigurationDescriptorAsync( + GattClientCharacteristicConfigurationDescriptorValue.none, &async_op); + if (async_op !is null) + (cast(IUnknown)async_op).Release(); // fire and forget + } + + return true; +} + +// --- Queries --- + +bool ble_hw_get_mac(uint port, ref ubyte[6] mac) +{ + // WinRT doesn't easily expose the local adapter MAC. + // Return a placeholder; the interface layer can override. + mac = [0, 0, 0, 0, 0, 0]; + return false; +} + +byte ble_hw_get_rssi(uint port, BLEConn conn) +{ + // WinRT doesn't provide per-connection RSSI queries + return -128; +} + +// --- Callbacks --- + +void ble_hw_set_scan_callback(uint port, BLEScanCallback cb) { _scan_cb = cb; } +void ble_hw_set_conn_callback(uint port, BLEConnCallback cb) { _conn_cb = cb; } +void ble_hw_set_discover_callback(uint port, BLEDiscoverCallback cb) { _discover_cb = cb; } +void ble_hw_set_read_callback(uint port, BLEReadCallback cb) { _read_cb = cb; } +void ble_hw_set_write_callback(uint port, BLEWriteCallback cb) { _write_cb = cb; } +void ble_hw_set_notify_callback(uint port, BLENotifyCallback cb) { _notify_cb = cb; } + +// --- Poll --- + +void ble_hw_poll(uint port) +{ + auto ble = BLE(0); + + // check if watcher stopped unexpectedly (e.g. radio disabled) + if (atomicLoad(_watcher_stopped) != 0) + { + log.warning("BLE scanner stopped unexpectedly"); + atomicStore(_watcher_stopped, 0u); + } + + // drain scan results + BLEAdvReport report = void; + while (_scan_ring.dequeue(&report)) + { + if (_scan_cb !is null) + _scan_cb(ble, report); + } + + // poll pending connection + poll_connect(ble); + + // poll GATT discovery + poll_discover(ble); + + // poll GATT read/write ops + poll_gatt(ble); + + // drain notification events + NotifyEvent evt = void; + while (_notify_ring.dequeue(&evt)) + { + if (_notify_cb !is null) + _notify_cb(ble, BLEConn(evt.conn_id), evt.handle, evt.data[0 .. evt.data_len]); + } + + // drain disconnection events + ubyte conn_id = void; + while (_disconn_ring.dequeue(&conn_id)) + { + auto s = find_session(conn_id); + if (s !is null) + { + if (_conn_cb !is null) + _conn_cb(ble, BLEConn(conn_id), false, BLEError.none); + release_session(s); + remove_session(conn_id); + } + } +} + + +// ════════════════════════════════════════════════════════════════════ +// Internal state +// ════════════════════════════════════════════════════════════════════ + +private: + +enum max_sessions = 8; +enum max_chars_per_session = 32; +enum max_gatt_ops = 8; +enum max_publishers = 8; + +struct AdvSlot +{ + ubyte id; + IBluetoothLEAdvertisementPublisher publisher; +} + +enum GattOpType : ubyte { read, write, write_no_response } +enum DiscoverPhase : ubyte { idle, services, chars } + +struct SessionChar +{ + ushort handle; + GUID service_uuid; + GUID char_uuid; + GattCharacteristicProperties properties; + IGattCharacteristic characteristic; + GattValueChangedHandler notify_handler; + EventRegistrationToken notify_token; +} + +struct WinSession +{ + bool active; + ubyte id; + IBluetoothLEDevice device; + IBluetoothLEDevice3 device3; + ConnectionStatusHandler conn_handler; + EventRegistrationToken conn_status_token; + SessionChar[max_chars_per_session] chars; + ubyte num_chars; +} + +struct PendingConnect +{ + IInspectable async_op; + ubyte[6] peer_addr; +} + +struct PendingDiscover +{ + IInspectable async_op; + IVectorView_IInspectable services; + uint service_count; + uint current_service; + GUID current_service_uuid; + ubyte conn_id; + DiscoverPhase phase; +} + +struct PendingGattOp +{ + IInspectable async_op; + ubyte conn_id; + ushort handle; + GattOpType op_type; +} + +struct NotifyEvent +{ + ubyte conn_id; + ushort handle; + ubyte data_len; + ubyte[247] data; +} + +// --- module-level state --- + +__gshared bool _opened; +__gshared ubyte _next_conn_id; + +// sessions +__gshared WinSession[max_sessions] _sessions; +__gshared ubyte _num_sessions; + +// callbacks +__gshared BLEScanCallback _scan_cb; +__gshared BLEConnCallback _conn_cb; +__gshared BLEDiscoverCallback _discover_cb; +__gshared BLEReadCallback _read_cb; +__gshared BLEWriteCallback _write_cb; +__gshared BLENotifyCallback _notify_cb; + +// scanner +__gshared IBluetoothLEAdvertisementWatcher _watcher; +__gshared AdvertisementReceivedHandler _adv_handler; +__gshared EventRegistrationToken _received_token; +__gshared WatcherStoppedHandler _stopped_handler; +__gshared EventRegistrationToken _stopped_token; +shared uint _watcher_stopped; + +// advertisers +__gshared AdvSlot[max_publishers] _publishers; +__gshared ubyte _num_publishers; +__gshared ubyte _next_adv_id; + +// device factory +__gshared IBluetoothLEDeviceStatics _device_statics; + +// pending connect +__gshared PendingConnect _pending_connect; + +// pending discover +__gshared PendingDiscover _pending_discover; + +// pending GATT ops +__gshared PendingGattOp[max_gatt_ops] _pending_gatt; +__gshared ubyte _num_pending_gatt; + +// thread-safe event queues (written from WinRT threads, read from main) +__gshared ThreadSafeQueue!(32, BLEAdvReport) _scan_ring; +__gshared ThreadSafeQueue!(32, NotifyEvent) _notify_ring; +__gshared ThreadSafeQueue!(8, ubyte) _disconn_ring; + + +// ════════════════════════════════════════════════════════════════════ +// Session management +// ════════════════════════════════════════════════════════════════════ + +WinSession* find_session(ubyte id) +{ + foreach (ref s; _sessions[0 .. _num_sessions]) + { + if (s.active && s.id == id) + return &s; + } + return null; +} + +SessionChar* find_session_char(WinSession* s, ushort handle) +{ + foreach (ref c; s.chars[0 .. s.num_chars]) + { + if (c.handle == handle) + return &c; + } + return null; +} + +WinSession* alloc_session() +{ + if (_num_sessions >= max_sessions) + return null; + + // find an ID not currently in use + ubyte id = _next_conn_id; + outer: foreach (_; 0 .. 256) + { + foreach (ref s; _sessions[0 .. _num_sessions]) + if (s.id == id) + { + id++; + continue outer; + } + break; + } + _next_conn_id = cast(ubyte)(id + 1); + + auto s = &_sessions[_num_sessions++]; + s.active = true; + s.id = id; + s.num_chars = 0; + return s; +} + +void remove_session(ubyte id) +{ + foreach (i, ref s; _sessions[0 .. _num_sessions]) + { + if (s.id == id) + { + _sessions[i] = _sessions[_num_sessions - 1]; + _num_sessions--; + return; + } + } +} + +void release_session(WinSession* s) +{ + if (s.device !is null && s.conn_handler !is null) + { + s.device.remove_ConnectionStatusChanged(s.conn_status_token); + defaultAllocator().freeT(s.conn_handler); + s.conn_handler = null; + } + + foreach (ref gc; s.chars[0 .. s.num_chars]) + { + if (gc.notify_handler !is null) + { + gc.characteristic.remove_ValueChanged(gc.notify_token); + defaultAllocator().freeT(gc.notify_handler); + gc.notify_handler = null; + } + if (gc.characteristic !is null) + { + gc.characteristic.Release(); + gc.characteristic = null; + } + } + + if (s.device3 !is null) + { + s.device3.Release(); + s.device3 = null; + } + if (s.device !is null) + { + s.device.Release(); + s.device = null; + } + s.active = false; +} + +void stop_all_publishers() +{ + foreach (ref slot; _publishers[0 .. _num_publishers]) + { + slot.publisher.Stop(); + slot.publisher.Release(); + } + _num_publishers = 0; +} + +void cleanup_connect() +{ + if (_pending_connect.async_op !is null) + { + _pending_connect.async_op.Release(); + _pending_connect.async_op = null; + } +} + + +// ════════════════════════════════════════════════════════════════════ +// Async polling (main thread) +// ════════════════════════════════════════════════════════════════════ + +void poll_connect(BLE ble) +{ + if (_pending_connect.async_op is null) + return; + + auto async_info = qi!IAsyncInfo(_pending_connect.async_op, &IID_IAsyncInfo); + if (async_info is null) + return; + scope(exit) async_info.Release(); + + AsyncStatus status; + async_info.get_Status(&status); + + if (status == AsyncStatus.started) + return; + + if (status != AsyncStatus.completed) + { + cleanup_connect(); + if (_conn_cb !is null) + _conn_cb(ble, BLEConn(ubyte.max), false, BLEError.not_found); + return; + } + + auto async_op = cast(IAsyncOperation_BluetoothLEDevice)cast(void*)_pending_connect.async_op; + IBluetoothLEDevice device; + async_op.GetResults(&device); + + auto peer_addr = _pending_connect.peer_addr; + cleanup_connect(); + + if (device is null) + { + if (_conn_cb !is null) + _conn_cb(ble, BLEConn(ubyte.max), false, BLEError.not_found); + return; + } + + auto s = alloc_session(); + if (s is null) + { + device.Release(); + if (_conn_cb !is null) + _conn_cb(ble, BLEConn(ubyte.max), false, BLEError.internal); + return; + } + + s.device = device; + s.device3 = qi!IBluetoothLEDevice3(device, &IID_IBluetoothLEDevice3); + + // register connection status handler + auto handler = defaultAllocator().allocT!ConnectionStatusHandler; + handler.conn_id = s.id; + handler.callback = &on_connection_status_changed; + s.device.add_ConnectionStatusChanged(cast(IUnknown)handler, &s.conn_status_token); + s.conn_handler = handler; + + log.info("connected to ", peer_addr); + + if (_conn_cb !is null) + _conn_cb(ble, BLEConn(s.id), true, BLEError.none); +} + +void poll_discover(BLE ble) +{ + if (_pending_discover.phase == DiscoverPhase.idle) + return; + if (_pending_discover.async_op is null && _pending_discover.services is null) + return; + if (_pending_discover.async_op is null) + return; + + auto async_info = qi!IAsyncInfo(_pending_discover.async_op, &IID_IAsyncInfo); + if (async_info is null) + return; + scope(exit) async_info.Release(); + + AsyncStatus status; + async_info.get_Status(&status); + + if (status == AsyncStatus.started) + return; + + IInspectable result_raw; + if (status == AsyncStatus.completed) + { + if (_pending_discover.services is null) + { + auto async_op = cast(IAsyncOperation_GattDeviceServicesResult)cast(void*)_pending_discover.async_op; + IGattDeviceServicesResult svc_result; + async_op.GetResults(&svc_result); + result_raw = cast(IInspectable)cast(void*)svc_result; + } + else + { + auto async_op = cast(IAsyncOperation_GattCharacteristicsResult)cast(void*)_pending_discover.async_op; + IGattCharacteristicsResult chr_result; + async_op.GetResults(&chr_result); + result_raw = cast(IInspectable)cast(void*)chr_result; + } + } + + // release async op + _pending_discover.async_op.Release(); + _pending_discover.async_op = null; + + auto conn_id = _pending_discover.conn_id; + auto s = find_session(conn_id); + + if (_pending_discover.services is null) + { + // phase 1: service list + if (result_raw is null) + { + finish_discover(ble, conn_id, BLEError.protocol); + return; + } + + auto svc_result = cast(IGattDeviceServicesResult)cast(void*)result_raw; + GattCommunicationStatus gatt_status; + svc_result.get_Status(&gatt_status); + + if (gatt_status != GattCommunicationStatus.success) + { + result_raw.Release(); + finish_discover(ble, conn_id, BLEError.protocol); + return; + } + + IInspectable services_raw; + svc_result.get_Services(&services_raw); + result_raw.Release(); + + if (services_raw is null) + { + finish_discover(ble, conn_id, BLEError.none); + return; + } + + auto services = cast(IVectorView_IInspectable)cast(void*)services_raw; + uint count; + services.get_Size(&count); + + _pending_discover.services = services; + _pending_discover.service_count = count; + _pending_discover.current_service = 0; + _pending_discover.phase = DiscoverPhase.chars; + + discover_next_service(); + } + else + { + // phase 2: characteristics for a service + if (result_raw !is null && s !is null) + { + auto chars_result = cast(IGattCharacteristicsResult)cast(void*)result_raw; + GattCommunicationStatus gatt_status; + chars_result.get_Status(&gatt_status); + + if (gatt_status == GattCommunicationStatus.success) + { + IInspectable chars_raw; + chars_result.get_Characteristics(&chars_raw); + if (chars_raw !is null) + { + auto chars = cast(IVectorView_IInspectable)cast(void*)chars_raw; + uint count; + chars.get_Size(&count); + + foreach (j; 0 .. count) + { + IInspectable char_raw; + chars.GetAt(j, &char_raw); + if (char_raw is null) + continue; + + auto chr = cast(IGattCharacteristic)cast(void*)char_raw; + if (s.num_chars < max_chars_per_session) + { + auto ci = &s.chars[s.num_chars++]; + chr.get_AttributeHandle(&ci.handle); + ci.service_uuid = _pending_discover.current_service_uuid; + chr.get_Uuid(&ci.char_uuid); + chr.get_CharacteristicProperties(&ci.properties); + ci.characteristic = chr; + } + else + (cast(IUnknown)chr).Release(); + } + chars_raw.Release(); + } + } + result_raw.Release(); + } + + _pending_discover.current_service++; + if (!discover_next_service()) + finish_discover(ble, conn_id, BLEError.none); + } +} + +bool discover_next_service() +{ + auto services = _pending_discover.services; + while (_pending_discover.current_service < _pending_discover.service_count) + { + IInspectable svc_raw; + services.GetAt(_pending_discover.current_service, &svc_raw); + if (svc_raw is null) + { + _pending_discover.current_service++; + continue; + } + + auto svc = cast(IGattDeviceService)cast(void*)svc_raw; + svc.get_Uuid(&_pending_discover.current_service_uuid); + + auto svc3 = qi!IGattDeviceService3(svc_raw, &IID_IGattDeviceService3); + svc_raw.Release(); + + if (svc3 is null) + { + _pending_discover.current_service++; + continue; + } + + IInspectable chars_op; + svc3.GetCharacteristicsAsync(&chars_op); + svc3.Release(); + + if (chars_op is null) + { + _pending_discover.current_service++; + continue; + } + + _pending_discover.async_op = chars_op; + return true; + } + + // all done + if (_pending_discover.services !is null) + { + (cast(IUnknown)_pending_discover.services).Release(); + _pending_discover.services = null; + } + return false; +} + +void finish_discover(BLE ble, ubyte conn_id, BLEError error) +{ + if (_pending_discover.services !is null) + { + (cast(IUnknown)_pending_discover.services).Release(); + _pending_discover.services = null; + } + _pending_discover.phase = DiscoverPhase.idle; + + if (_discover_cb !is null) + { + auto s = find_session(conn_id); + if (s !is null && error == BLEError.none) + { + BLEGattChar[max_chars_per_session] chars = void; + foreach (i; 0 .. s.num_chars) + { + chars[i].handle = s.chars[i].handle; + chars[i].cccd_handle = 0; // WinRT handles CCCD internally + chars[i].service_uuid = s.chars[i].service_uuid; + chars[i].char_uuid = s.chars[i].char_uuid; + chars[i].properties = cast(GattCharProps)s.chars[i].properties; + } + _discover_cb(ble, BLEConn(conn_id), chars[0 .. s.num_chars], BLEError.none); + } + else + _discover_cb(ble, BLEConn(conn_id), null, error); + } +} + +void poll_gatt(BLE ble) +{ + uint i = 0; + while (i < _num_pending_gatt) + { + auto pg = &_pending_gatt[i]; + + auto async_info = qi!IAsyncInfo(pg.async_op, &IID_IAsyncInfo); + if (async_info is null) + { + i++; + continue; + } + + AsyncStatus status; + async_info.get_Status(&status); + async_info.Release(); + + if (status == AsyncStatus.started) + { + i++; + continue; + } + + auto conn = BLEConn(pg.conn_id); + bool success = false; + const(ubyte)[] data; + IBuffer value_buf; + + if (status == AsyncStatus.completed) + { + if (pg.op_type == GattOpType.read) + { + auto async_op = cast(IAsyncOperation_GattReadResult)cast(void*)pg.async_op; + IGattReadResult read_result; + async_op.GetResults(&read_result); + + if (read_result !is null) + { + GattCommunicationStatus gatt_status; + read_result.get_Status(&gatt_status); + if (gatt_status == GattCommunicationStatus.success) + { + read_result.get_Value(&value_buf); + data = get_buffer_bytes(value_buf); + success = true; + } + read_result.Release(); + } + } + else + { + auto async_op = cast(IAsyncOperation_GattCommunicationStatus)cast(void*)pg.async_op; + GattCommunicationStatus gatt_status; + async_op.GetResults(&gatt_status); + success = gatt_status == GattCommunicationStatus.success; + } + } + + // fire callback + if (pg.op_type == GattOpType.read) + { + if (_read_cb !is null) + _read_cb(ble, conn, pg.handle, data, success ? BLEError.none : BLEError.protocol); + } + else + { + if (_write_cb !is null) + _write_cb(ble, conn, pg.handle, success ? BLEError.none : BLEError.protocol); + } + + if (value_buf !is null) + value_buf.Release(); + pg.async_op.Release(); + + // compact array + --_num_pending_gatt; + if (i < _num_pending_gatt) + _pending_gatt[i] = _pending_gatt[_num_pending_gatt]; + // don't increment i -- swapped element needs checking + } +} + + +// ════════════════════════════════════════════════════════════════════ +// WinRT thread callbacks (fire on thread pool) +// ════════════════════════════════════════════════════════════════════ + +void on_advertisement_received(ubyte[6] addr, byte rssi, bool connectable, bool is_scan_response, const(ubyte)[] ad_payload) +{ + BLEAdvReport report = void; + report.addr = addr; + report.addr_type = BLEAddrType.public_; + report.adv_type = connectable ? BLEAdvType.connectable : BLEAdvType.nonconnectable; + report.rssi = rssi; + report.tx_power = -128; + ubyte len = ad_payload.length > 62 ? 62 : cast(ubyte)ad_payload.length; + report.data_len = len; + if (len > 0) + report.data_buf[0 .. len] = cast(const(ubyte)[])ad_payload[0 .. len]; + _scan_ring.enqueue(report); +} + +void on_gatt_notification(ubyte conn_id, ushort attr_handle, const(ubyte)[] data) +{ + NotifyEvent evt = void; + evt.conn_id = conn_id; + evt.handle = attr_handle; + ubyte len = data.length > 247 ? 247 : cast(ubyte)data.length; + evt.data_len = len; + if (len > 0) + evt.data[0 .. len] = cast(const(ubyte)[])data[0 .. len]; + _notify_ring.enqueue(evt); +} + +void on_connection_status_changed(ubyte conn_id, int status) +{ + if (status == 0) // Disconnected + _disconn_ring.enqueue(conn_id); +} + + +// ════════════════════════════════════════════════════════════════════ +// Helpers +// ════════════════════════════════════════════════════════════════════ + +T qi(T)(IUnknown obj, const(GUID)* iid) +{ + if (obj is null) + return null; + void* result; + HRESULT hr = obj.QueryInterface(iid, &result); + if (hr < 0) + return null; + return cast(T)cast(void*)result; +} + +ulong mac_to_ble_addr(ref const ubyte[6] mac) +{ + return (cast(ulong)mac[0] << 40) | (cast(ulong)mac[1] << 32) | + (cast(ulong)mac[2] << 24) | (cast(ulong)mac[3] << 16) | + (cast(ulong)mac[4] << 8) | mac[5]; +} + +void ble_addr_to_mac(ulong addr, ref ubyte[6] mac) +{ + mac[0] = cast(ubyte)(addr >> 40); + mac[1] = cast(ubyte)(addr >> 32); + mac[2] = cast(ubyte)(addr >> 24); + mac[3] = cast(ubyte)(addr >> 16); + mac[4] = cast(ubyte)(addr >> 8); + mac[5] = cast(ubyte)(addr); +} + +const(ubyte)[] get_buffer_bytes(IBuffer buf) +{ + if (buf is null) + return null; + + uint len; + if (buf.get_Length(&len) < 0) + return null; + if (len == 0) + return null; + + auto access = qi!IBufferByteAccess(buf, &IID_IBufferByteAccess); + if (access is null) + return null; + scope(exit) access.Release(); + + ubyte* ptr; + if (access.Buffer(&ptr) < 0 || ptr is null) + return null; + + return ptr[0 .. len]; +} + +void set_adv_data_from_raw(IBluetoothLEAdvertisement adv, const(ubyte)[] raw) +{ + uint offset = 0; + while (offset < raw.length) + { + if (offset + 1 >= raw.length) + break; + ubyte len = raw[offset++]; + if (len == 0 || offset + len > raw.length) + break; + ubyte ad_type = raw[offset]; + const(ubyte)[] ad_data = raw[offset + 1 .. offset + len]; + offset += len; + + // set local name if present + if (ad_type == 0x09 || ad_type == 0x08) // complete/shortened local name + { + wchar[64] wname = void; + uint wlen = cast(uint)ad_data.length; + if (wlen > 64) wlen = 64; + foreach (i; 0 .. wlen) + wname[i] = ad_data[i]; + HSTRING hname = g_winrt.make_string(wname[0 .. wlen]); + if (hname !is null) + { + adv.put_LocalName(hname); + g_winrt.WindowsDeleteString(hname); + } + } + } +} + + +// ════════════════════════════════════════════════════════════════════ +// WinRT bootstrap +// ════════════════════════════════════════════════════════════════════ + +struct WinRT +{ +nothrow @nogc: + bool initialized; + + import urt.internal.sys.windows.windef : HMODULE; + private HMODULE _lib; + + extern (Windows) HRESULT function(uint initType) RoInitialize; + extern (Windows) HRESULT function(HSTRING classId, IInspectable* instance) RoActivateInstance; + extern (Windows) HRESULT function(HSTRING classId, const(GUID)* iid, void** factory) RoGetActivationFactory; + extern (Windows) HRESULT function(const(wchar)* str, uint len, HSTRING* out_) WindowsCreateString; + extern (Windows) HRESULT function(HSTRING str) WindowsDeleteString; + extern (Windows) const(wchar)* function(HSTRING str, uint* len) WindowsGetStringRawBuffer; + + bool init() + { + import urt.internal.sys.windows.winbase : LoadLibrary, FreeLibrary, GetProcAddress; + + auto lib = LoadLibrary("combase.dll"); + if (!lib) + { + log.error("failed to load combase.dll"); + return false; + } + + RoInitialize = cast(typeof(RoInitialize)) GetProcAddress(lib, "RoInitialize"); + RoActivateInstance = cast(typeof(RoActivateInstance)) GetProcAddress(lib, "RoActivateInstance"); + RoGetActivationFactory = cast(typeof(RoGetActivationFactory)) GetProcAddress(lib, "RoGetActivationFactory"); + WindowsCreateString = cast(typeof(WindowsCreateString)) GetProcAddress(lib, "WindowsCreateString"); + WindowsDeleteString = cast(typeof(WindowsDeleteString)) GetProcAddress(lib, "WindowsDeleteString"); + WindowsGetStringRawBuffer = cast(typeof(WindowsGetStringRawBuffer)) GetProcAddress(lib, "WindowsGetStringRawBuffer"); + + if (!RoInitialize || !RoActivateInstance || !RoGetActivationFactory || + !WindowsCreateString || !WindowsDeleteString || !WindowsGetStringRawBuffer) + { + log.error("failed to resolve WinRT functions from combase.dll"); + FreeLibrary(lib); + return false; + } + + HRESULT hr = RoInitialize(1); // RO_INIT_MULTITHREADED + if (hr < 0 && hr != cast(HRESULT)0x80010106) // RPC_E_CHANGED_MODE is ok + { + log.error("RoInitialize failed: ", hr); + FreeLibrary(lib); + return false; + } + + _lib = lib; + initialized = true; + log.info("WinRT initialized"); + return true; + } + + HSTRING make_string(const(wchar)[] s) + { + HSTRING h; + if (WindowsCreateString(s.ptr, cast(uint)s.length, &h) < 0) + return null; + return h; + } + + T activate(T : IInspectable)(const(wchar)[] className, const(GUID)* iid) + { + HSTRING cls = make_string(className); + if (!cls) + return null; + scope(exit) WindowsDeleteString(cls); + + IInspectable inspectable; + if (RoActivateInstance(cls, &inspectable) < 0 || inspectable is null) + return null; + scope(exit) inspectable.Release(); + + void* result; + if (inspectable.QueryInterface(iid, &result) < 0) + return null; + return cast(T)cast(void*)result; + } + + T get_factory(T)(const(wchar)[] className, const(GUID)* iid) + { + HSTRING cls = make_string(className); + if (!cls) + return null; + scope(exit) WindowsDeleteString(cls); + + void* result; + if (RoGetActivationFactory(cls, iid, &result) < 0) + return null; + return cast(T)cast(void*)result; + } +} + +__gshared WinRT g_winrt; + + +// ════════════════════════════════════════════════════════════════════ +// COM/WinRT types and interfaces +// ════════════════════════════════════════════════════════════════════ + +alias HRESULT = int; +alias ULONG = uint; +alias BOOL = int; +alias HSTRING = void*; + +struct EventRegistrationToken { long value; } + +enum AsyncStatus : int { started = 0, completed = 1, canceled = 2, error = 3 } +enum BluetoothLEScanningMode : int { passive = 0, active = 1 } +enum GattCommunicationStatus : int { success = 0, unreachable = 1, protocol_error = 2, access_denied = 3 } + +enum GattCharacteristicProperties : uint +{ + none = 0, broadcast = 0x0001, read = 0x0002, write_without_response = 0x0004, + write = 0x0008, notify = 0x0010, indicate = 0x0020, + authenticated_signed_writes = 0x0040, extended_properties = 0x0080, + reliable_write = 0x0100, writable_auxiliaries = 0x0200, +} + +enum GattClientCharacteristicConfigurationDescriptorValue : int { none = 0, notify = 1, indicate = 2 } + +// GUIDs +static immutable IID_IUnknown = GUID(0x00000000, 0x0000, 0x0000, [0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46]); +static immutable IID_IInspectable = GUID(0xAF86E2E0, 0xB12D, 0x4C6A, [0x9C,0x5A,0xD7,0xAA,0x65,0x10,0x1E,0x90]); +static immutable IID_IAgileObject = GUID(0x94EA2B94, 0xE9CC, 0x49E0, [0xC0,0xFF,0xEE,0x64,0xCA,0x8F,0x5B,0x90]); +static immutable IID_IAsyncInfo = GUID(0x00000036, 0x0000, 0x0000, [0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46]); + +static immutable IID_IBluetoothLEAdvertisementWatcher = GUID(0xA6AC336F, 0xF3D3, 0x4297, [0x8D,0x6C,0xC8,0x1E,0xA6,0x62,0x3F,0x40]); +static immutable IID_IBluetoothLEAdvertisementReceivedEventArgs = GUID(0x27987DDF, 0xE596, 0x41BE, [0x8D,0x43,0x9E,0x67,0x31,0xD4,0xA9,0x13]); +static immutable IID_IBluetoothLEAdvertisementReceivedEventArgs2 = GUID(0x12D9C87B, 0x0399, 0x5F0E, [0xA3,0x48,0x53,0xB0,0x2B,0x6B,0x16,0x2E]); +static immutable IID_IBluetoothLEAdvertisement = GUID(0x066FB2B7, 0x33D1, 0x4E7D, [0x83,0x67,0xCF,0x81,0xD0,0xF7,0x96,0x53]); +static immutable IID_IBluetoothLEDevice = GUID(0xB5EE2F7B, 0x4AD8, 0x4642, [0xAC,0x48,0x80,0xA0,0xB5,0x00,0xE8,0x87]); +static immutable IID_IBluetoothLEDeviceStatics = GUID(0xC8CF1A19, 0xF0B6, 0x4BF0, [0x86,0x89,0x41,0x30,0x3D,0xE2,0xD9,0xF4]); +static immutable IID_IBluetoothLEDevice3 = GUID(0xAEE9E493, 0x44AC, 0x40DC, [0xAF,0x33,0xB2,0xC1,0x3C,0x01,0xCA,0x46]); +static immutable IID_IGattDeviceServicesResult = GUID(0x171DD3EE, 0x016D, 0x419D, [0x83,0x8A,0x57,0x6C,0xF4,0x75,0xA3,0xD8]); +static immutable IID_IGattDeviceService3 = GUID(0xB293A950, 0x0C53, 0x437C, [0xA9,0xB3,0x5C,0x32,0x10,0xC6,0xE5,0x69]); +static immutable IID_IGattCharacteristic = GUID(0x59CB50C1, 0x5934, 0x4F68, [0xA1,0x98,0xEB,0x86,0x4F,0xA4,0x4E,0x6B]); +static immutable IID_IGattReadResult = GUID(0x63A66F08, 0x1AEA, 0x4C4C, [0xA5,0x0F,0x97,0xBA,0xE4,0x74,0xB3,0x48]); +static immutable IID_IBluetoothLEAdvertisementPublisher = GUID(0xCDE820F9, 0xD9FA, 0x43D6, [0xA2,0x64,0xDD,0xD8,0xB7,0xDA,0x8B,0x78]); +static immutable IID_IBufferByteAccess = GUID(0x905A0FEF, 0xBC53, 0x11DF, [0x8C,0x49,0x00,0x1E,0x4F,0xC6,0x86,0xDA]); +static immutable IID_TypedEventHandler_Watcher_Received = GUID(0x90EB4ECA, 0xD465, 0x5EA0, [0xA6,0x1C,0x03,0x3C,0x8C,0x5E,0xCE,0xF2]); +static immutable IID_TypedEventHandler_Gatt_ValueChanged = GUID(0xC1F420F6, 0x6292, 0x5760, [0xA2,0xC9,0x9D,0xDF,0x98,0x68,0x3C,0xFC]); +static immutable IID_TypedEventHandler_Device_ConnectionStatus = GUID(0x24A901AD, 0x910F, 0x5C29, [0xB2,0x36,0x80,0x3C,0xC0,0x30,0x60,0xFE]); +static immutable IID_TypedEventHandler_Watcher_Stopped = GUID(0x9936A4DB, 0xDC99, 0x55C3, [0x9E,0x9B,0xBF,0x48,0x54,0xBD,0x9F,0x0B]); + + +// --- COM interfaces --- + +extern (Windows): + +interface IUnknown +{ +nothrow @nogc: + HRESULT QueryInterface(const(GUID)* riid, void** ppv); + ULONG AddRef(); + ULONG Release(); +} + +interface IInspectable : IUnknown +{ +nothrow @nogc: + HRESULT GetIids(uint* count, GUID** iids); + HRESULT GetRuntimeClassName(HSTRING* name); + HRESULT GetTrustLevel(int* level); +} + +interface IAsyncInfo : IInspectable +{ +nothrow @nogc: + HRESULT get_Id(uint* id); + HRESULT get_Status(AsyncStatus* status); + HRESULT get_ErrorCode(HRESULT* code); + HRESULT Cancel(); + HRESULT Close(); +} + +interface IAsyncOperation_BluetoothLEDevice : IInspectable +{ +nothrow @nogc: + HRESULT put_Completed(IUnknown handler); + HRESULT get_Completed(IUnknown* handler); + HRESULT GetResults(IBluetoothLEDevice* result); +} + +interface IAsyncOperation_GattDeviceServicesResult : IInspectable +{ +nothrow @nogc: + HRESULT put_Completed(IUnknown handler); + HRESULT get_Completed(IUnknown* handler); + HRESULT GetResults(IGattDeviceServicesResult* result); +} + +interface IAsyncOperation_GattCharacteristicsResult : IInspectable +{ +nothrow @nogc: + HRESULT put_Completed(IUnknown handler); + HRESULT get_Completed(IUnknown* handler); + HRESULT GetResults(IGattCharacteristicsResult* result); +} + +interface IAsyncOperation_GattReadResult : IInspectable +{ +nothrow @nogc: + HRESULT put_Completed(IUnknown handler); + HRESULT get_Completed(IUnknown* handler); + HRESULT GetResults(IGattReadResult* result); +} + +interface IAsyncOperation_GattCommunicationStatus : IInspectable +{ +nothrow @nogc: + HRESULT put_Completed(IUnknown handler); + HRESULT get_Completed(IUnknown* handler); + HRESULT GetResults(GattCommunicationStatus* result); +} + +interface IBluetoothLEAdvertisementWatcher : IInspectable +{ +nothrow @nogc: + HRESULT get_MinSamplingInterval(long* value); + HRESULT get_MaxSamplingInterval(long* value); + HRESULT get_MinOutOfRangeTimeout(long* value); + HRESULT get_MaxOutOfRangeTimeout(long* value); + HRESULT get_Status(int* value); + HRESULT get_ScanningMode(BluetoothLEScanningMode* value); + HRESULT put_ScanningMode(BluetoothLEScanningMode value); + HRESULT get_SignalStrengthFilter(IInspectable* value); + HRESULT put_SignalStrengthFilter(IInspectable value); + HRESULT get_AdvertisementFilter(IInspectable* value); + HRESULT put_AdvertisementFilter(IInspectable value); + HRESULT Start(); + HRESULT Stop(); + HRESULT add_Received(IUnknown handler, EventRegistrationToken* token); + HRESULT remove_Received(EventRegistrationToken token); + HRESULT add_Stopped(IUnknown handler, EventRegistrationToken* token); + HRESULT remove_Stopped(EventRegistrationToken token); +} + +interface IBluetoothLEAdvertisementReceivedEventArgs : IInspectable +{ +nothrow @nogc: + HRESULT get_RawSignalStrengthInDBm(short* value); + HRESULT get_BluetoothAddress(ulong* value); + HRESULT get_AdvertisementType(int* value); + HRESULT get_Timestamp(long* value); + HRESULT get_Advertisement(IBluetoothLEAdvertisement* value); +} + +interface IBluetoothLEAdvertisementReceivedEventArgs2 : IInspectable +{ +nothrow @nogc: + HRESULT get_BluetoothAddressType(int* value); + HRESULT get_TransmitPowerLevelInDBm(IInspectable* value); + HRESULT get_IsAnonymous(BOOL* value); + HRESULT get_IsConnectable(BOOL* value); + HRESULT get_IsScannable(BOOL* value); + HRESULT get_IsDirected(BOOL* value); + HRESULT get_IsScanResponse(BOOL* value); +} + +interface IBluetoothLEAdvertisement : IInspectable +{ +nothrow @nogc: + HRESULT get_Flags(IInspectable* value); + HRESULT put_Flags(IInspectable value); + HRESULT get_LocalName(HSTRING* value); + HRESULT put_LocalName(HSTRING value); + HRESULT get_ServiceUuids(IInspectable* value); + HRESULT get_ManufacturerData(IInspectable* value); + HRESULT get_DataSections(IInspectable* value); + HRESULT GetManufacturerDataByCompanyId(ushort companyId, IInspectable* dataList); + HRESULT GetSectionsByType(ubyte type_, IInspectable* sectionList); +} + +interface IVectorView_IInspectable : IInspectable +{ +nothrow @nogc: + HRESULT GetAt(uint index, IInspectable* item); + HRESULT get_Size(uint* size); + HRESULT IndexOf(IInspectable value, uint* index, BOOL* found); + HRESULT GetMany(uint startIndex, uint capacity, IInspectable* items, uint* actual); +} + +interface IBuffer : IInspectable +{ +nothrow @nogc: + HRESULT get_Capacity(uint* value); + HRESULT get_Length(uint* value); + HRESULT put_Length(uint value); +} + +interface IBufferByteAccess : IUnknown +{ +nothrow @nogc: + HRESULT Buffer(ubyte** value); +} + +interface IGattDeviceServicesResult : IInspectable +{ +nothrow @nogc: + HRESULT get_Status(GattCommunicationStatus* value); + HRESULT get_ProtocolError(IInspectable* value); + HRESULT get_Services(IInspectable* value); +} + +interface IGattDeviceService : IInspectable +{ +nothrow @nogc: + HRESULT GetCharacteristics(GUID characteristicUuid, IInspectable* value); + HRESULT GetIncludedServices(GUID serviceUuid, IInspectable* value); + HRESULT get_DeviceId(HSTRING* value); + HRESULT get_Uuid(GUID* value); + HRESULT get_AttributeHandle(ushort* value); +} + +interface IGattDeviceService3 : IInspectable +{ +nothrow @nogc: + HRESULT get_DeviceAccessInformation(IInspectable* value); + HRESULT get_Session(IInspectable* value); + HRESULT get_SharingMode(int* value); + HRESULT RequestAccessAsync(IInspectable* operation); + HRESULT OpenAsync(int sharingMode, IInspectable* operation); + HRESULT GetCharacteristicsAsync(IInspectable* operation); + HRESULT GetCharacteristicsWithCacheModeAsync(int cacheMode, IInspectable* operation); + HRESULT GetCharacteristicsForUuidAsync(GUID uuid, IInspectable* operation); + HRESULT GetCharacteristicsForUuidWithCacheModeAsync(GUID uuid, int cacheMode, IInspectable* operation); + HRESULT GetIncludedServicesAsync(IInspectable* operation); + HRESULT GetIncludedServicesWithCacheModeAsync(int cacheMode, IInspectable* operation); +} + +interface IGattCharacteristicsResult : IInspectable +{ +nothrow @nogc: + HRESULT get_Status(GattCommunicationStatus* value); + HRESULT get_ProtocolError(IInspectable* value); + HRESULT get_Characteristics(IInspectable* value); +} + +interface IGattCharacteristic : IInspectable +{ +nothrow @nogc: + HRESULT GetDescriptors(GUID descriptorUuid, IInspectable* value); + HRESULT get_CharacteristicProperties(GattCharacteristicProperties* value); + HRESULT get_ProtectionLevel(int* value); + HRESULT put_ProtectionLevel(int value); + HRESULT get_UserDescription(HSTRING* value); + HRESULT get_Uuid(GUID* value); + HRESULT get_AttributeHandle(ushort* value); + HRESULT get_PresentationFormats(IInspectable* value); + HRESULT ReadValueAsync(IInspectable* operation); + HRESULT ReadValueWithCacheModeAsync(int cacheMode, IInspectable* operation); + HRESULT WriteValueAsync(IBuffer value, IInspectable* operation); + HRESULT WriteValueWithOptionAsync(IBuffer value, int writeOption, IInspectable* operation); + HRESULT ReadClientCharacteristicConfigurationDescriptorAsync(IInspectable* operation); + HRESULT WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue value, IInspectable* operation); + HRESULT add_ValueChanged(IUnknown handler, EventRegistrationToken* token); + HRESULT remove_ValueChanged(EventRegistrationToken token); +} + +interface IGattReadResult : IInspectable +{ +nothrow @nogc: + HRESULT get_Status(GattCommunicationStatus* value); + HRESULT get_Value(IBuffer* value); +} + +interface IGattValueChangedEventArgs : IInspectable +{ +nothrow @nogc: + HRESULT get_CharacteristicValue(IBuffer* value); + HRESULT get_Timestamp(long* value); +} + +interface IBluetoothLEAdvertisementPublisher : IInspectable +{ +nothrow @nogc: + HRESULT get_Status(int* value); + HRESULT get_Advertisement(IBluetoothLEAdvertisement* value); + HRESULT Start(); + HRESULT Stop(); + HRESULT add_StatusChanged(IUnknown handler, EventRegistrationToken* token); + HRESULT remove_StatusChanged(EventRegistrationToken token); +} + +interface IBluetoothLEDeviceStatics : IInspectable +{ +nothrow @nogc: + HRESULT FromIdAsync(HSTRING deviceId, IInspectable* operation); + HRESULT FromBluetoothAddressAsync(ulong bluetoothAddress, IInspectable* operation); + HRESULT GetDeviceSelector(HSTRING* selector); +} + +interface IBluetoothLEDevice : IInspectable +{ +nothrow @nogc: + HRESULT get_DeviceId(HSTRING* value); + HRESULT get_Name(HSTRING* value); + HRESULT get_GattServices(IInspectable* value); + HRESULT get_ConnectionStatus(int* value); + HRESULT get_BluetoothAddress(ulong* value); + HRESULT GetGattService(GUID serviceUuid, IInspectable* service); + HRESULT add_NameChanged(IUnknown handler, EventRegistrationToken* token); + HRESULT remove_NameChanged(EventRegistrationToken token); + HRESULT add_GattServicesChanged(IUnknown handler, EventRegistrationToken* token); + HRESULT remove_GattServicesChanged(EventRegistrationToken token); + HRESULT add_ConnectionStatusChanged(IUnknown handler, EventRegistrationToken* token); + HRESULT remove_ConnectionStatusChanged(EventRegistrationToken token); +} + +interface IBluetoothLEDevice3 : IInspectable +{ +nothrow @nogc: + HRESULT get_DeviceAccessInformation(IInspectable* value); + HRESULT RequestAccessAsync(IInspectable* operation); + HRESULT GetGattServicesAsync(IInspectable* operation); + HRESULT GetGattServicesWithCacheModeAsync(int cacheMode, IInspectable* operation); + HRESULT GetGattServicesForUuidAsync(GUID serviceUuid, IInspectable* operation); + HRESULT GetGattServicesForUuidWithCacheModeAsync(GUID serviceUuid, int cacheMode, IInspectable* operation); +} + +// Handler interfaces +interface IAdvertisementReceivedHandler : IUnknown { nothrow @nogc: HRESULT Invoke(IInspectable sender, IInspectable args); } +interface IGattValueChangedHandler : IUnknown { nothrow @nogc: HRESULT Invoke(IInspectable sender, IInspectable args); } +interface IConnectionStatusHandler : IUnknown { nothrow @nogc: HRESULT Invoke(IInspectable sender, IInspectable args); } +interface IWatcherStoppedHandler : IUnknown { nothrow @nogc: HRESULT Invoke(IInspectable sender, IInspectable args); } + + +// ════════════════════════════════════════════════════════════════════ +// COM implementation classes +// ════════════════════════════════════════════════════════════════════ + +class ComObject : IInspectable +{ +nothrow @nogc: + protected shared uint _ref_count = 1; + + HRESULT QueryInterface(const(GUID)* riid, void** ppv) + { + if (*riid == IID_IUnknown || *riid == IID_IInspectable || *riid == IID_IAgileObject) + { + *ppv = cast(void*)cast(IUnknown)this; + AddRef(); + return 0; + } + *ppv = null; + return 0x80004002; // E_NOINTERFACE + } + + ULONG AddRef() { return atomicFetchAdd(_ref_count, 1) + 1; } + ULONG Release() + { + auto prev = atomicFetchSub(_ref_count, 1); + if (prev == 1) + { + defaultAllocator().freeT(this); + return 0; + } + return prev - 1; + } + + HRESULT GetIids(uint* count, GUID** iids) { *count = 0; *iids = null; return 0; } + HRESULT GetRuntimeClassName(HSTRING* name) { *name = null; return 0; } + HRESULT GetTrustLevel(int* level) { *level = 0; return 0; } +} + + +class MemoryBuffer : ComObject, IBuffer, IBufferByteAccess +{ +nothrow @nogc: + private ubyte* _data; + private uint _length; + private uint _capacity; + + void set(const(ubyte)[] data) + { + _data = cast(ubyte*)data.ptr; + _length = cast(uint)data.length; + _capacity = cast(uint)data.length; + } + + override ULONG Release() + { + auto prev = atomicFetchSub(_ref_count, 1); + if (prev == 1) + { + if (_data !is null) + defaultAllocator().free(_data[0 .. _capacity]); + defaultAllocator().freeT(this); + return 0; + } + return prev - 1; + } + + override HRESULT QueryInterface(const(GUID)* riid, void** ppv) + { + if (*riid == IID_IBufferByteAccess) + { + *ppv = cast(void*)cast(IBufferByteAccess)this; + AddRef(); + return 0; + } + return super.QueryInterface(riid, ppv); + } + + HRESULT get_Capacity(uint* value) { *value = _capacity; return 0; } + HRESULT get_Length(uint* value) { *value = _length; return 0; } + HRESULT put_Length(uint value) { _length = value; return 0; } + HRESULT Buffer(ubyte** value) { *value = _data; return 0; } +} + + +class AdvertisementReceivedHandler : ComObject, IAdvertisementReceivedHandler +{ +nothrow @nogc: + extern(D) void function(ubyte[6] addr, byte rssi, bool connectable, bool is_scan_response, const(ubyte)[] ad_payload) nothrow @nogc callback; + + override HRESULT QueryInterface(const(GUID)* riid, void** ppv) + { + if (*riid == IID_TypedEventHandler_Watcher_Received) + { + *ppv = cast(void*)cast(IUnknown)this; + AddRef(); + return 0; + } + return super.QueryInterface(riid, ppv); + } + + HRESULT Invoke(IInspectable sender, IInspectable args_raw) + { + if (!callback) + return 0; + + auto args = qi!IBluetoothLEAdvertisementReceivedEventArgs(args_raw, &IID_IBluetoothLEAdvertisementReceivedEventArgs); + if (args is null) + return 0; + scope(exit) args.Release(); + + ulong addr; + short rssi; + IBluetoothLEAdvertisement adv; + args.get_BluetoothAddress(&addr); + args.get_RawSignalStrengthInDBm(&rssi); + args.get_Advertisement(&adv); + + bool connectable = false; + bool is_scan_response = false; + auto args2 = qi!IBluetoothLEAdvertisementReceivedEventArgs2(args_raw, &IID_IBluetoothLEAdvertisementReceivedEventArgs2); + if (args2 !is null) + { + BOOL conn, scan_rsp; + args2.get_IsConnectable(&conn); + args2.get_IsScanResponse(&scan_rsp); + connectable = conn != 0; + is_scan_response = scan_rsp != 0; + args2.Release(); + } + + ubyte[6] mac; + ble_addr_to_mac(addr, mac); + + // serialize AD sections to raw bytes + ubyte[62] payload = void; + uint payload_len = serialize_adv(adv, payload[]); + if (adv !is null) + adv.Release(); + + callback(mac, cast(byte)rssi, connectable, is_scan_response, payload[0 .. payload_len]); + return 0; + } +} + + +class ConnectionStatusHandler : ComObject, IConnectionStatusHandler +{ +nothrow @nogc: + extern(D) void function(ubyte conn_id, int status) nothrow @nogc callback; + ubyte conn_id; + + override HRESULT QueryInterface(const(GUID)* riid, void** ppv) + { + if (*riid == IID_TypedEventHandler_Device_ConnectionStatus) + { + *ppv = cast(void*)cast(IUnknown)this; + AddRef(); + return 0; + } + return super.QueryInterface(riid, ppv); + } + + HRESULT Invoke(IInspectable sender, IInspectable args) + { + if (!callback) + return 0; + + // query connection status from sender (IBluetoothLEDevice) + auto dev = qi!IBluetoothLEDevice(sender, &IID_IBluetoothLEDevice); + if (dev !is null) + { + int conn_status; + dev.get_ConnectionStatus(&conn_status); + dev.Release(); + callback(conn_id, conn_status); + } + return 0; + } +} + + +class GattValueChangedHandler : ComObject, IGattValueChangedHandler +{ +nothrow @nogc: + extern(D) void function(ubyte conn_id, ushort attr_handle, const(ubyte)[] data) nothrow @nogc callback; + ubyte conn_id; + ushort attr_handle; + + override HRESULT QueryInterface(const(GUID)* riid, void** ppv) + { + if (*riid == IID_TypedEventHandler_Gatt_ValueChanged) + { + *ppv = cast(void*)cast(IUnknown)this; + AddRef(); + return 0; + } + return super.QueryInterface(riid, ppv); + } + + HRESULT Invoke(IInspectable sender, IInspectable args_raw) + { + if (!callback) + return 0; + + auto args = cast(IGattValueChangedEventArgs)cast(void*)args_raw; + if (args is null) + return 0; + + IBuffer value_buf; + args.get_CharacteristicValue(&value_buf); + const(ubyte)[] data = get_buffer_bytes(value_buf); + + callback(conn_id, attr_handle, data); + + if (value_buf !is null) + value_buf.Release(); + return 0; + } +} + + +class WatcherStoppedHandler : ComObject, IWatcherStoppedHandler +{ +nothrow @nogc: + shared(uint)* flag; + + override HRESULT QueryInterface(const(GUID)* riid, void** ppv) + { + if (*riid == IID_TypedEventHandler_Watcher_Stopped) + { + *ppv = cast(void*)cast(IUnknown)this; + AddRef(); + return 0; + } + return super.QueryInterface(riid, ppv); + } + + HRESULT Invoke(IInspectable sender, IInspectable args) + { + if (flag !is null) + atomicStore(*flag, 1u); + return 0; + } +} + + +// serialize WinRT advertisement to raw AD bytes [len][type][data]... +uint serialize_adv(IBluetoothLEAdvertisement adv, ubyte[] buf) +{ + if (adv is null) + return 0; + + uint offset = 0; + + // local name + HSTRING hname; + adv.get_LocalName(&hname); + if (hname !is null) + { + uint len; + const(wchar)* raw = g_winrt.WindowsGetStringRawBuffer(hname, &len); + if (raw && len > 0) + { + uint copy_len = len < 64 ? len : 64; + ubyte name_len = cast(ubyte)(copy_len + 1); + if (offset + 1 + name_len <= buf.length) + { + buf[offset++] = name_len; + buf[offset++] = 0x09; // complete local name + foreach (i; 0 .. copy_len) + buf[offset++] = cast(ubyte)raw[i]; + } + } + g_winrt.WindowsDeleteString(hname); + } + + // AD data sections + IInspectable sections_raw; + adv.get_DataSections(§ions_raw); + if (sections_raw !is null) + { + auto sections = cast(IVectorView_IInspectable)cast(void*)sections_raw; + uint count; + sections.get_Size(&count); + + foreach (i; 0 .. count) + { + IInspectable item; + sections.GetAt(i, &item); + if (item is null) + continue; + + auto section = cast(IBluetoothLEAdvertisementDataSection)cast(void*)item; + ubyte dt; + section.get_DataType(&dt); + + IBuffer dbuf; + section.get_Data(&dbuf); + const(ubyte)[] data = get_buffer_bytes(dbuf); + + if (data.length > 0) + { + ubyte sec_len = cast(ubyte)(data.length + 1); + if (offset + 1 + sec_len <= buf.length) + { + buf[offset++] = sec_len; + buf[offset++] = dt; + buf[offset .. offset + data.length] = cast(const(ubyte)[])data[]; + offset += cast(uint)data.length; + } + } + + if (dbuf !is null) + dbuf.Release(); + item.Release(); + } + sections_raw.Release(); + } + + return offset; +} + +interface IBluetoothLEAdvertisementDataSection : IInspectable +{ +nothrow @nogc: + HRESULT get_DataType(ubyte* value); + HRESULT put_DataType(ubyte value); + HRESULT get_Data(IBuffer* value); + HRESULT put_Data(IBuffer value); +} diff --git a/src/urt/thread.d b/src/urt/thread.d new file mode 100644 index 0000000..2be17bc --- /dev/null +++ b/src/urt/thread.d @@ -0,0 +1,71 @@ +module urt.thread; + +import urt.atomic; + +nothrow @nogc: + + +// thread-safe FIFO for passing data between threads. +// uses a spinlock to protect enqueue/dequeue. +struct ThreadSafeQueue(uint capacity = 64, T = void*) +{ +nothrow @nogc: + + // returns false if queue is full (item not enqueued). + bool enqueue(T item) + { + while (!cas(&_lock, false, true)) {} + uint count = _tail >= _head ? _tail - _head : capacity - _head + _tail; + if (count >= capacity) + { + _lock = false; + return false; + } + _queue[_tail] = item; + _tail = (_tail + 1) % capacity; + _lock = false; + return true; + } + + static if (is(T == U*, U) || is(T == void*)) + { + // dequeue a pointer, or null if empty. + T dequeue() + { + while (!cas(&_lock, false, true)) {} + if (_head == _tail) + { + _lock = false; + return null; + } + auto result = _queue[_head]; + _head = (_head + 1) % capacity; + _lock = false; + return result; + } + } + else + { + // dequeue a value type via output parameter. + // returns false if empty. + bool dequeue(T* out_) + { + while (!cas(&_lock, false, true)) {} + if (_head == _tail) + { + _lock = false; + return false; + } + *out_ = _queue[_head]; + _head = (_head + 1) % capacity; + _lock = false; + return true; + } + } + +private: + T[capacity] _queue; + shared uint _head; + shared uint _tail; + shared bool _lock; +} From d72252823a455193fc1eb5bce6b12db777f5f085 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sat, 11 Apr 2026 00:41:46 +1000 Subject: [PATCH 123/138] Renovate alloc module with flags and platform drivers. --- Makefile | 3 + src/sys/bk7231/alloc.d | 32 +++++ src/sys/bl618/alloc.d | 32 +++++ src/sys/bl808/alloc.d | 32 +++++ src/sys/esp32/alloc.d | 77 +++++++++++ src/sys/posix/alloc.d | 55 ++++++++ src/sys/rp2350/alloc.d | 32 +++++ src/sys/rp2350/package.d | 30 ++--- src/sys/stm32/alloc.d | 32 +++++ src/sys/windows/alloc.d | 68 ++++++++++ src/urt/mem/alloc.d | 270 ++++++++++++++++++++++----------------- src/urt/mem/allocator.d | 6 +- src/urt/platform.d | 2 +- 13 files changed, 532 insertions(+), 139 deletions(-) create mode 100644 src/sys/bk7231/alloc.d create mode 100644 src/sys/bl618/alloc.d create mode 100644 src/sys/bl808/alloc.d create mode 100644 src/sys/esp32/alloc.d create mode 100644 src/sys/posix/alloc.d create mode 100644 src/sys/rp2350/alloc.d create mode 100644 src/sys/stm32/alloc.d create mode 100644 src/sys/windows/alloc.d diff --git a/Makefile b/Makefile index 704f3da..ad0abbf 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,9 @@ SOURCES := $(SOURCES) $(shell find "$(SRCDIR)/sys/baremetal" -type f -name '*.d' ifeq ($(OS),windows) SOURCES := $(SOURCES) $(shell find "$(SRCDIR)/sys/windows" -type f -name '*.d') endif +ifneq ($(filter linux ubuntu freebsd,$(OS)),) + SOURCES := $(SOURCES) $(shell find "$(SRCDIR)/sys/posix" -type f -name '*.d') +endif SOURCES := $(SOURCES) $(SRCDIR)/urt/internal/mbedtls.c ifeq ($(PLATFORM),riscv64) diff --git a/src/sys/bk7231/alloc.d b/src/sys/bk7231/alloc.d new file mode 100644 index 0000000..8d5603e --- /dev/null +++ b/src/sys/bk7231/alloc.d @@ -0,0 +1,32 @@ +module sys.bk7231.alloc; + +import urt.mem : malloc, free; +import urt.mem.alloc : MemFlags; + +nothrow @nogc: + +enum has_realloc = false; +enum has_expand = false; +enum has_memsize = false; +enum has_exec = false; +enum has_retain = false; +enum has_memflags = false; + +void[] _alloc(size_t size, size_t alignment, MemFlags) pure +{ + import urt.util : align_down; + + size_t header_size = (void*).sizeof + alignment; + void* p = malloc(header_size + size); + if (p is null) + return null; + + size_t allocptr = align_down(cast(size_t)p + header_size, alignment); + (cast(void**)allocptr)[-1] = p; + return (cast(void*)allocptr)[0 .. size]; +} + +void _free(void* ptr) pure +{ + free((cast(void**)ptr)[-1]); +} diff --git a/src/sys/bl618/alloc.d b/src/sys/bl618/alloc.d new file mode 100644 index 0000000..bbe14d5 --- /dev/null +++ b/src/sys/bl618/alloc.d @@ -0,0 +1,32 @@ +module sys.bl618.alloc; + +import urt.mem : malloc, free; +import urt.mem.alloc : MemFlags; + +nothrow @nogc: + +enum has_realloc = false; +enum has_expand = false; +enum has_memsize = false; +enum has_exec = false; +enum has_retain = false; +enum has_memflags = false; + +void[] _alloc(size_t size, size_t alignment, MemFlags) pure +{ + import urt.util : align_down; + + size_t header_size = (void*).sizeof + alignment; + void* p = malloc(header_size + size); + if (p is null) + return null; + + size_t allocptr = align_down(cast(size_t)p + header_size, alignment); + (cast(void**)allocptr)[-1] = p; + return (cast(void*)allocptr)[0 .. size]; +} + +void _free(void* ptr) pure +{ + free((cast(void**)ptr)[-1]); +} diff --git a/src/sys/bl808/alloc.d b/src/sys/bl808/alloc.d new file mode 100644 index 0000000..94d8b72 --- /dev/null +++ b/src/sys/bl808/alloc.d @@ -0,0 +1,32 @@ +module sys.bl808.alloc; + +import urt.mem : malloc, free; +import urt.mem.alloc : MemFlags; + +nothrow @nogc: + +enum has_realloc = false; +enum has_expand = false; +enum has_memsize = false; +enum has_exec = false; +enum has_retain = false; // TODO: HBN RAM +enum has_memflags = false; // TODO: SRAM vs PSRAM + +void[] _alloc(size_t size, size_t alignment, MemFlags) pure +{ + import urt.util : align_down; + + size_t header_size = (void*).sizeof + alignment; + void* p = malloc(header_size + size); + if (p is null) + return null; + + size_t allocptr = align_down(cast(size_t)p + header_size, alignment); + (cast(void**)allocptr)[-1] = p; + return (cast(void*)allocptr)[0 .. size]; +} + +void _free(void* ptr) pure +{ + free((cast(void**)ptr)[-1]); +} diff --git a/src/sys/esp32/alloc.d b/src/sys/esp32/alloc.d new file mode 100644 index 0000000..9ebcaef --- /dev/null +++ b/src/sys/esp32/alloc.d @@ -0,0 +1,77 @@ +module sys.esp32.alloc; + +import urt.mem.alloc : MemFlags; + +nothrow @nogc: + +enum has_realloc = false; +enum has_expand = false; +enum has_memsize = true; +enum has_exec = true; +enum has_retain = true; +enum has_memflags = true; + +void[] _alloc(size_t size, size_t alignment, MemFlags flags) pure +{ + void* p = heap_caps_aligned_alloc(alignment, size, _esp_caps[flags]); + return p ? p[0 .. size] : null; +} + +void _free(void* ptr) pure +{ + heap_caps_aligned_free(ptr); +} + +size_t _memsize(void* ptr) pure +{ + return heap_caps_get_allocated_size(ptr); +} + +void[] _alloc_exec(size_t size) pure +{ + void* p = heap_caps_aligned_alloc(8, size, CAP_INTERNAL); + return p ? p[0 .. size] : null; +} + +void _free_exec(void[] mem) pure +{ + heap_caps_aligned_free(mem.ptr); +} + +void[] _alloc_retain(size_t size) pure +{ + void* p = heap_caps_aligned_alloc(8, size, CAP_RTCRAM); + return p ? p[0 .. size] : null; +} + +void _free_retain(void[] mem) pure +{ + heap_caps_aligned_free(mem.ptr); +} + + +private: + +enum CAP_DMA = 1 << 2; +enum CAP_INTERNAL = 1 << 11; +enum CAP_DEFAULT = 1 << 12; +enum CAP_SPIRAM = 1 << 17; +enum CAP_RTCRAM = 1 << 10; + +// MemFlags [2:0] → ESP-IDF heap_caps +// [1:0] speed: 0=default, 1=fast, 2=slow, 3=fastest +// [2] dma +immutable uint[8] _esp_caps = [ + CAP_DEFAULT, // 0: default + CAP_DEFAULT | CAP_INTERNAL, // 1: fast + CAP_DEFAULT | CAP_SPIRAM, // 2: slow + CAP_DEFAULT | CAP_INTERNAL, // 3: fastest (no TCM on ESP32) + CAP_DEFAULT | CAP_DMA, // 4: dma + CAP_DEFAULT | CAP_DMA | CAP_INTERNAL, // 5: dma+fast + CAP_DEFAULT | CAP_DMA | CAP_SPIRAM, // 6: dma+slow + CAP_DEFAULT | CAP_DMA | CAP_INTERNAL, // 7: dma+fastest +]; + +extern(C) void* heap_caps_aligned_alloc(size_t alignment, size_t size, uint caps) pure; +extern(C) void heap_caps_aligned_free(void* ptr) pure; +extern(C) size_t heap_caps_get_allocated_size(void* ptr) pure; diff --git a/src/sys/posix/alloc.d b/src/sys/posix/alloc.d new file mode 100644 index 0000000..d0d9cec --- /dev/null +++ b/src/sys/posix/alloc.d @@ -0,0 +1,55 @@ +module sys.posix.alloc; + +import urt.mem : free; +import urt.mem.alloc : MemFlags; + +nothrow @nogc: + +enum has_realloc = false; +enum has_expand = false; +enum has_memsize = true; +enum has_exec = true; +enum has_retain = false; +enum has_memflags = false; + +void[] _alloc(size_t size, size_t alignment, MemFlags) pure +{ + void* p; + return posix_memalign(&p, alignment <= 8 ? 8 : alignment, size) ? null : p[0 .. size]; +} + +void _free(void* ptr) pure +{ + free(ptr); +} + +size_t _memsize(void* ptr) pure +{ + return malloc_usable_size(ptr); +} + +void[] _alloc_exec(size_t size) pure +{ + void* p = mmap(null, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + return (p is MAP_FAILED) ? null : p[0 .. size]; +} + +void _free_exec(void[] mem) pure +{ + cast(void)munmap(mem.ptr, mem.length); +} + + +private: + +extern(C) int posix_memalign(void** memptr, size_t alignment, size_t size) pure; +extern(C) size_t malloc_usable_size(void* ptr) pure; +extern(C) void* mmap(void* addr, size_t length, int prot, int flags, int fd, long offset) pure; +extern(C) int munmap(void* addr, size_t length) pure; + +enum PROT_READ = 0x1; +enum PROT_WRITE = 0x2; +enum PROT_EXEC = 0x4; +enum MAP_PRIVATE = 0x02; +enum MAP_ANONYMOUS = 0x20; +enum MAP_FAILED = cast(void*)-1; diff --git a/src/sys/rp2350/alloc.d b/src/sys/rp2350/alloc.d new file mode 100644 index 0000000..eef19ff --- /dev/null +++ b/src/sys/rp2350/alloc.d @@ -0,0 +1,32 @@ +module sys.rp2350.alloc; + +import urt.mem : malloc, free; +import urt.mem.alloc : MemFlags; + +nothrow @nogc: + +enum has_realloc = false; +enum has_expand = false; +enum has_memsize = false; +enum has_exec = false; +enum has_retain = false; +enum has_memflags = false; + +void[] _alloc(size_t size, size_t alignment, MemFlags) pure +{ + import urt.util : align_down; + + size_t header_size = (void*).sizeof + alignment; + void* p = malloc(header_size + size); + if (p is null) + return null; + + size_t allocptr = align_down(cast(size_t)p + header_size, alignment); + (cast(void**)allocptr)[-1] = p; + return (cast(void*)allocptr)[0 .. size]; +} + +void _free(void* ptr) pure +{ + free((cast(void**)ptr)[-1]); +} diff --git a/src/sys/rp2350/package.d b/src/sys/rp2350/package.d index 3b4186a..f02ddf1 100644 --- a/src/sys/rp2350/package.d +++ b/src/sys/rp2350/package.d @@ -18,27 +18,27 @@ private extern(C) extern const ubyte __eh_frame_start; private ubyte[48] __eh_frame_object; // storage for libgcc (no-op on ARM EHABI) // RP2350 peripheral base addresses -enum ulong RESETS_BASE = 0x40020000; -enum ulong CLOCKS_BASE = 0x40010000; -enum ulong XOSC_BASE = 0x40048000; -enum ulong PLL_SYS_BASE = 0x40060000; -enum ulong SIO_BASE = 0xD0000000; -enum ulong IO_BANK0_BASE = 0x40028000; -enum ulong PADS_BANK0_BASE = 0x40038000; +enum ulong RESETS_BASE = 0x40020000; +enum ulong CLOCKS_BASE = 0x40010000; +enum ulong XOSC_BASE = 0x40048000; +enum ulong PLL_SYS_BASE = 0x40060000; +enum ulong SIO_BASE = 0xD0000000; +enum ulong IO_BANK0_BASE = 0x40028000; +enum ulong PADS_BANK0_BASE = 0x40038000; // Atomic set/clear/xor aliases (RP2350 address alias trick) -enum ulong REG_ALIAS_SET = 0x00002000; -enum ulong REG_ALIAS_CLR = 0x00003000; +enum ulong REG_ALIAS_SET = 0x00002000; +enum ulong REG_ALIAS_CLR = 0x00003000; // RESETS register offsets -enum ulong RESETS_RESET = 0x00; -enum ulong RESETS_DONE = 0x08; +enum ulong RESETS_RESET = 0x00; +enum ulong RESETS_DONE = 0x08; // Reset bits for peripherals we need early -enum uint RESET_UART0 = 1 << 26; -enum uint RESET_UART1 = 1 << 27; -enum uint RESET_IO_BANK0 = 1 << 8; -enum uint RESET_PADS_BANK0 = 1 << 9; +enum uint RESET_UART0 = 1 << 26; +enum uint RESET_UART1 = 1 << 27; +enum uint RESET_IO_BANK0 = 1 << 8; +enum uint RESET_PADS_BANK0 = 1 << 9; private void mmio_write(ulong addr, uint val) @nogc nothrow { diff --git a/src/sys/stm32/alloc.d b/src/sys/stm32/alloc.d new file mode 100644 index 0000000..f7dd09d --- /dev/null +++ b/src/sys/stm32/alloc.d @@ -0,0 +1,32 @@ +module sys.stm32.alloc; + +import urt.mem : malloc, free; +import urt.mem.alloc : MemFlags; + +nothrow @nogc: + +enum has_realloc = false; +enum has_expand = false; +enum has_memsize = false; +enum has_exec = false; +enum has_retain = false; // TODO: backup SRAM +enum has_memflags = false; // TODO: TCM vs SRAM + +void[] _alloc(size_t size, size_t alignment, MemFlags) pure +{ + import urt.util : align_down; + + size_t header_size = (void*).sizeof + alignment; + void* p = malloc(header_size + size); + if (p is null) + return null; + + size_t allocptr = align_down(cast(size_t)p + header_size, alignment); + (cast(void**)allocptr)[-1] = p; + return (cast(void*)allocptr)[0 .. size]; +} + +void _free(void* ptr) pure +{ + free((cast(void**)ptr)[-1]); +} diff --git a/src/sys/windows/alloc.d b/src/sys/windows/alloc.d new file mode 100644 index 0000000..e179024 --- /dev/null +++ b/src/sys/windows/alloc.d @@ -0,0 +1,68 @@ +module sys.windows.alloc; + +import urt.mem.alloc : MemFlags; + +nothrow @nogc: + +enum has_realloc = true; +enum has_expand = true; +enum has_memsize = true; +enum has_exec = true; +enum has_retain = false; +enum has_memflags = false; + +void[] _alloc(size_t size, size_t alignment, MemFlags) pure +{ + void* p = _aligned_malloc(size, alignment); + return p ? p[0 .. size] : null; +} + +void _free(void* ptr) pure +{ + _aligned_free(ptr); +} + +void[] _realloc(void[] mem, size_t new_size, size_t alignment, MemFlags) pure +{ + void* p = _aligned_realloc(mem.ptr, new_size, alignment); + return p ? p[0 .. new_size] : null; +} + +void[] _expand(void[] mem, size_t new_size) pure +{ + if (new_size <= _aligned_msize(mem.ptr)) + return mem.ptr[0 .. new_size]; + return null; +} + +size_t _memsize(void* ptr) pure +{ + return _aligned_msize(ptr); +} + +void[] _alloc_exec(size_t size) pure +{ + void* p = VirtualAlloc(null, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + return p ? p[0 .. size] : null; +} + +void _free_exec(void[] mem) pure +{ + cast(void)VirtualFree(mem.ptr, 0, MEM_RELEASE); +} + + +private: + +extern(C) void* _aligned_malloc(size_t size, size_t alignment) pure; +extern(C) void* _aligned_realloc(void* memblock, size_t size, size_t alignment) pure; +extern(C) void _aligned_free(void* memblock) pure; +extern(C) size_t _aligned_msize(void* memblock) pure; + +extern(Windows) void* VirtualAlloc(void* addr, size_t size, uint type, uint protect) pure; +extern(Windows) bool VirtualFree(void* addr, size_t size, uint type) pure; + +enum MEM_COMMIT = 0x1000; +enum MEM_RESERVE = 0x2000; +enum MEM_RELEASE = 0x8000; +enum PAGE_EXECUTE_READWRITE = 0x40; diff --git a/src/urt/mem/alloc.d b/src/urt/mem/alloc.d index caa6be1..1a7d4d6 100644 --- a/src/urt/mem/alloc.d +++ b/src/urt/mem/alloc.d @@ -5,171 +5,201 @@ import urt.mem; nothrow @nogc: -void[] alloc(size_t size) pure +enum MemFlags : ubyte { - // TODO: pure malloc is meant to copy and restore errno around the call, because that's user-accessible state... + none = 0, - // TODO: we might pin the length to a debug table somewhere... - return malloc(size)[0 .. size]; -} + fast = 1, // internal SRAM, lowest latency + slow = 2, // external PSRAM, bulk storage + fastest = 3, // TCM / tightly-coupled, single cycle -void[] realloc(void[] mem, size_t newSize) pure -{ - // TODO: we might pin the length to a debug table somewhere... - return urt.mem.realloc(mem.ptr, newSize)[0 .. newSize]; + dma = 0x4, // DMA-accessible } -void free(void[] mem) pure -{ - // maybe check the length passed to free matches the alloc? - // ... or you know, just don't do that. - urt.mem.free(mem.ptr); -} +MemFlags mem_speed(MemFlags flags) pure => cast(MemFlags)(flags & 3); +bool mem_is_dma(MemFlags flags) pure => (flags & MemFlags.dma) != 0; -void[] alloc_aligned(size_t size, size_t alignment) pure -{ - import urt.util : align_down, is_power_of_2, max; - - alignment = max(alignment, (void*).sizeof); - assert(is_power_of_2(alignment), "Alignment must be a power of two!"); - version (Windows) - { - void* mem = _aligned_malloc(size, alignment); - return mem ? mem[0 .. size] : null; - } - else version (Posix) - { - import urt.internal.sys.posix; - void* mem; - return posix_memalign(&mem, alignment, size) ? null : mem[0 .. size]; - } - else version (Espressif) - { - enum MALLOC_CAP_DEFAULT = 1 << 12; - void* mem = heap_caps_aligned_alloc(alignment, size, MALLOC_CAP_DEFAULT); - return mem ? mem[0 .. size] : null; - } - else version (FreeStanding) - { - size_t header_size = (void*).sizeof + alignment; - size_t total = header_size + size; +void[] alloc(size_t size, MemFlags flags = MemFlags.none) pure + => alloc(size, size_t.sizeof, flags); - void* mem = malloc(total); - if (mem is null) - return null; +void[] alloc(size_t size, size_t alignment, MemFlags flags = MemFlags.none) pure +{ + import urt.util : is_power_of_2; - size_t ptr = cast(size_t)mem; - size_t allocptr = align_down(ptr + header_size, alignment); - (cast(void**)allocptr)[-1] = mem; + assert(is_power_of_2(alignment), "Alignment must be a power of two!"); - return (cast(void*)allocptr)[0 .. size]; - } - else - assert(false, "Unsupported platform"); + return _alloc(size, alignment, flags); } -void[] realloc_aligned(void[] mem, size_t newSize, size_t alignment) pure +void[] realloc(void[] mem, size_t new_size, size_t alignment = 8, MemFlags flags = MemFlags.none) pure { - import urt.util : is_power_of_2, min, max; + import urt.util : min; - alignment = max(alignment, (void*).sizeof); - assert(is_power_of_2(alignment), "Alignment must be a power of two!"); + if (new_size == 0) + { + free(mem); + return null; + } + if (mem.ptr is null) + return alloc(new_size, alignment, flags); - void[] newAlloc = newSize > 0 ? alloc_aligned(newSize, alignment) : null; - if (newAlloc !is null && mem !is null) + static if (has_realloc) + return _realloc(mem, new_size, alignment, flags); + else { - size_t toCopy = min(mem.length, newSize); - newAlloc[0 .. toCopy] = mem[0 .. toCopy]; + void[] new_mem = alloc(new_size, alignment, flags); + if (new_mem.ptr !is null) + { + size_t copy = min(mem.length, new_size); + new_mem[0 .. copy] = mem[0 .. copy]; + } + free(mem); + return new_mem; } - free_aligned(mem); - return newAlloc; } -void free_aligned(void[] mem) pure +void free(void[] mem) pure { if (mem.ptr is null) return; - version (Windows) - _aligned_free(mem.ptr); - else version (Posix) - urt.mem.free(mem.ptr); - else version (Espressif) - heap_caps_aligned_free(mem.ptr); - else version (FreeStanding) - { - void* p = (cast(void**)mem.ptr)[-1]; - urt.mem.free(p); - } - else - assert(false, "Unsupported platform"); + _free(mem.ptr); } -// NOTE: This function is only compatible with alloc_aligned! -void[] expand(void[] mem, size_t newSize) pure +void[] expand(void[] mem, size_t new_size) pure { if (mem.ptr is null) return null; - if (newSize <= memsize(mem.ptr)) - return mem.ptr[0 .. newSize]; - return null; + static if (has_expand) + return _expand(mem, new_size); + else static if (has_memsize) + { + if (new_size <= _memsize(mem.ptr)) + return mem.ptr[0 .. new_size]; + return null; + } + else + assert(false, "unsupported"); } -// NOTE: This function is only compatible with alloc_aligned! size_t memsize(void* ptr) pure { if (ptr is null) return 0; - version (Windows) - return _aligned_msize(ptr); - else version (Posix) - return malloc_usable_size(ptr); - else version (Espressif) - return heap_caps_get_allocated_size(ptr); - else version (FreeStanding) - { - void* mem = (cast(void**)ptr)[-1]; - size_t offset = cast(size_t)ptr - cast(size_t)mem; - return malloc_usable_size(mem) - offset; - } + static if (has_memsize) + return _memsize(ptr); else - assert(false, "Unsupported platform"); + assert(false, "unsupported"); } - -unittest +void[] alloc_exec(size_t size) pure { - void[] mem = alloc_aligned(16, 8); - assert(mem !is null); - size_t s = memsize(mem.ptr); - assert(s >= 16); - mem = expand(mem, 8); - assert(mem !is null); - mem = expand(mem, 16); - assert(mem !is null); - free_aligned(mem); + static if (has_exec) + return _alloc_exec(size); + else + return null; } - -version (Windows) +void free_exec(void[] mem) pure { - extern(C) void* _aligned_malloc(size_t size, size_t alignment) pure; - extern(C) void _aligned_free(void* memblock) pure; - extern(C) size_t _aligned_msize(void* memblock) pure; + static if (has_exec) + { + if (mem.ptr !is null) + _free_exec(mem); + } } -version (Espressif) +void[] alloc_retain(size_t size) pure { - // ESP-IDF heap_caps API — provides aligned alloc and size query - extern(C) void* heap_caps_aligned_alloc(size_t alignment, size_t size, uint caps) pure; - extern(C) void heap_caps_aligned_free(void* ptr) pure; - extern(C) size_t heap_caps_get_allocated_size(void* ptr) pure; + static if (has_retain) + return _alloc_retain(size); + else + return null; } -else version (Posix) + +void free_retain(void[] mem) pure { - extern(C) size_t malloc_usable_size(void *__ptr) pure; + static if (has_retain) + { + if (mem.ptr !is null) + _free_retain(mem); + } } -else version (FreeStanding) + + +// pointer tagging utilities -- for containers to store flags in low 3 bits +// of 8-byte aligned pointers. the allocator itself returns clean pointers. +T* tag(T)(T* ptr, MemFlags flags) pure + => cast(T*)(cast(size_t)ptr | flags); + +T* untag(T)(T* ptr) pure + => cast(T*)(cast(size_t)ptr & ~cast(size_t)0x7); + +MemFlags get_flags(void* ptr) pure + => cast(MemFlags)(cast(size_t)ptr & 0x7); + + +version (Espressif) + public import sys.esp32.alloc; +else version (BL808_M0) + public import sys.bl618.alloc; +else version (BL808) + public import sys.bl808.alloc; +else version (BL618) + public import sys.bl618.alloc; +else version (RP2350) + public import sys.rp2350.alloc; +else version (BK7231N) + public import sys.bk7231.alloc; +else version (BK7231T) + public import sys.bk7231.alloc; +else version (STM32F4) + public import sys.stm32.alloc; +else version (STM32F7) + public import sys.stm32.alloc; +else version (Windows) + public import sys.windows.alloc; +else version (Posix) + public import sys.posix.alloc; +else + static assert(false, "No alloc driver for this platform"); + + +unittest { - extern(C) size_t malloc_usable_size(void *__ptr) pure; + // basic alloc/free + void[] mem = alloc(32, 8); + assert(mem !is null); + assert((cast(size_t)mem.ptr & 0x7) == 0); // 8-byte aligned + assert(mem.length == 32); + free(mem); + + // alloc with flags (on desktop, flags are ignored but API works) + mem = alloc(64, 8, MemFlags.fast); + assert(mem !is null); + size_t s = memsize(mem.ptr); + assert(s >= 64); + free(mem); + + // realloc preserves data + mem = alloc(16, 8); + (cast(ubyte*)mem.ptr)[0 .. 16] = 0xAB; + mem = realloc(mem, 64); + assert(mem !is null); + assert((cast(ubyte*)mem.ptr)[0] == 0xAB); + free(mem); + + // expand + mem = alloc(16, 8); + void[] expanded = expand(mem, 8); + if (expanded !is null) + assert(expanded.ptr is mem.ptr); + free(mem); + + // pointer tagging utilities + void* p = mem.ptr; + enum test_flags = cast(MemFlags)(MemFlags.fast | MemFlags.dma); + void* tagged = tag(p, test_flags); + assert(get_flags(tagged) == test_flags); + assert(untag(tagged) is p); } diff --git a/src/urt/mem/allocator.d b/src/urt/mem/allocator.d index 7da1c58..7c1ad50 100644 --- a/src/urt/mem/allocator.d +++ b/src/urt/mem/allocator.d @@ -371,12 +371,12 @@ nothrow @nogc: override void[] alloc(size_t size, size_t alignment = DefaultAlign) pure { - return urt.mem.alloc.alloc_aligned(size, alignment); + return urt.mem.alloc.alloc(size, alignment); } override void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) pure { - return urt.mem.alloc.realloc_aligned(mem, newSize, alignment); + return urt.mem.alloc.realloc(mem, newSize, alignment); } override void[] expand(void[] mem, size_t newSize) pure @@ -386,7 +386,7 @@ nothrow @nogc: override void free(void[] mem) pure { - urt.mem.alloc.free_aligned(mem); + urt.mem.alloc.free(mem); } private: diff --git a/src/urt/platform.d b/src/urt/platform.d index 23454bf..ca97907 100644 --- a/src/urt/platform.d +++ b/src/urt/platform.d @@ -29,8 +29,8 @@ else version (ESP32_C5) enum string Platform = "ESP32-C5"; else version (ESP32_C6) enum string Platform = "ESP32-C6"; else version (ESP32_H2) enum string Platform = "ESP32-H2"; else version (ESP32_P4) enum string Platform = "ESP32-P4"; -else version (BL808) enum string Platform = "BL808"; else version (BL808_M0) enum string Platform = "BL808-M0"; +else version (BL808) enum string Platform = "BL808"; else version (BL618) enum string Platform = "BL618"; else version (BK7231N) enum string Platform = "BK7231N"; else version (BK7231T) enum string Platform = "BK7231T"; From 645d9516e165e0f008ee654761c54e104bf4b7ca Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 12 Apr 2026 22:00:30 +1000 Subject: [PATCH 124/138] Centralise all allocations around the central allocator. Optimise lots of template bloat. --- src/object.d | 38 +- src/sys/bk7231/alloc.d | 8 +- src/sys/bl618/alloc.d | 7 +- src/sys/bl808/alloc.d | 7 +- src/sys/posix/alloc.d | 3 +- src/sys/rp2350/alloc.d | 7 +- src/sys/stm32/alloc.d | 7 +- src/urt/array.d | 506 +++++++++++++-------------- src/urt/internal/exception.d | 26 +- src/urt/internal/mbedtls.d | 22 +- src/urt/log.d | 5 +- src/urt/map.d | 650 ++++++++++++++++++----------------- src/urt/mem/package.d | 6 +- src/urt/mem/temp.d | 27 +- src/urt/string/format.d | 445 ++++++++++++++---------- src/urt/string/string.d | 70 ++-- 16 files changed, 968 insertions(+), 866 deletions(-) diff --git a/src/object.d b/src/object.d index cc22652..73f56be 100644 --- a/src/object.d +++ b/src/object.d @@ -709,11 +709,10 @@ T _d_newclassT(T)() @trusted if (__ctfe) assert(false, "new class not supported at CTFE without druntime"); - import urt.mem : malloc; - import urt.mem : memcpy; + import urt.mem : alloc, memcpy; enum sz = __traits(classInstanceSize, T); - auto p = malloc(sz); + auto p = alloc(sz).ptr; if (p is null) assert(false, "out of memory in _d_newclassT"); @@ -740,9 +739,9 @@ ref Tarr _d_arrayappendT(Tarr : T[], T)(return ref scope Tarr x, scope Tarr y) @ T[] _d_newarrayT(T)(size_t length, bool isShared = false) @trusted { - import urt.mem : malloc; + import urt.mem.alloc : alloc; // assert(false, "new array requires druntime"); - return (cast(T*)malloc(T.sizeof * length))[0 .. length]; + return cast(T[])alloc(T.sizeof * length); } Tarr _d_newarraymTX(Tarr : U[], T, U)(size_t[] dims, bool isShared = false) @trusted @@ -752,9 +751,9 @@ Tarr _d_newarraymTX(Tarr : U[], T, U)(size_t[] dims, bool isShared = false) @tru T* _d_newitemT(T)() @trusted { - import urt.mem : malloc; + import urt.mem.alloc : alloc; // assert(false, "new item requires druntime"); - return cast(T*)malloc(T.sizeof); + return cast(T*)alloc(T.sizeof).ptr; } T _d_newThrowable(T)() @trusted @@ -944,8 +943,8 @@ extern(C) void _d_unittest(string file, uint line) nothrow @nogc // blocks that accidentally use `new` can at least link. extern(C) void* _d_allocmemory(size_t sz) nothrow @nogc @trusted { - import urt.mem : malloc; - return malloc(sz); + import urt.mem.alloc : alloc; + return alloc(sz).ptr; } // ────────────────────────────────────────────────────────────────────── @@ -993,9 +992,9 @@ extern(C) void _d_array_slice_copy(void* dst, size_t dstlen, void* src, size_t s extern(C) void* _d_allocclass(TypeInfo_Class ci) nothrow @nogc @trusted { - import urt.mem : malloc, memcpy; + import urt.mem : alloc, memcpy; auto init = ci.initializer; - auto p = malloc(init.length); + auto p = alloc(init.length).ptr; if (p !is null) memcpy(p, init.ptr, init.length); return p; @@ -1038,28 +1037,23 @@ extern(C) int _adEq2(void[] a1, void[] a2, TypeInfo ti) nothrow @nogc @trusted extern(C) void[] _d_newarrayT(const TypeInfo ti, size_t length) nothrow @nogc @trusted { - import urt.mem : calloc; + import urt.mem.alloc : alloc; auto elemsize = ti.next ? ti.next.tsize : 1; - auto p = calloc(length, elemsize); - return p[0 .. length * elemsize]; + return alloc(length * elemsize); } extern(C) void[] _d_newarrayiT(const TypeInfo ti, size_t length) nothrow @nogc @trusted { - import urt.mem : calloc; + import urt.mem.alloc : alloc; auto elemsize = ti.next ? ti.next.tsize : 1; - auto p = calloc(length, elemsize); - return p[0 .. length * elemsize]; + return alloc(length * elemsize); } extern(C) void[] _d_newarrayU(const TypeInfo ti, size_t length) nothrow @nogc @trusted { - import urt.mem : malloc; + import urt.mem.alloc : alloc; auto elemsize = ti.next ? ti.next.tsize : 1; - auto sz = length * elemsize; - auto p = malloc(sz); - if (p is null) return null; - return p[0 .. sz]; + return alloc(length * elemsize); } // Unconditional halt — avoids circular dependency with assert. diff --git a/src/sys/bk7231/alloc.d b/src/sys/bk7231/alloc.d index 8d5603e..7b6a5b8 100644 --- a/src/sys/bk7231/alloc.d +++ b/src/sys/bk7231/alloc.d @@ -1,6 +1,5 @@ module sys.bk7231.alloc; -import urt.mem : malloc, free; import urt.mem.alloc : MemFlags; nothrow @nogc: @@ -30,3 +29,10 @@ void _free(void* ptr) pure { free((cast(void**)ptr)[-1]); } + + +private: + +extern(C) void* malloc(size_t size) pure; +extern(C) void free(void* ptr) pure; + diff --git a/src/sys/bl618/alloc.d b/src/sys/bl618/alloc.d index bbe14d5..aad4caa 100644 --- a/src/sys/bl618/alloc.d +++ b/src/sys/bl618/alloc.d @@ -1,6 +1,5 @@ module sys.bl618.alloc; -import urt.mem : malloc, free; import urt.mem.alloc : MemFlags; nothrow @nogc: @@ -30,3 +29,9 @@ void _free(void* ptr) pure { free((cast(void**)ptr)[-1]); } + + +private: + +extern(C) void* malloc(size_t size) pure; +extern(C) void free(void* ptr) pure; diff --git a/src/sys/bl808/alloc.d b/src/sys/bl808/alloc.d index 94d8b72..260bb65 100644 --- a/src/sys/bl808/alloc.d +++ b/src/sys/bl808/alloc.d @@ -1,6 +1,5 @@ module sys.bl808.alloc; -import urt.mem : malloc, free; import urt.mem.alloc : MemFlags; nothrow @nogc: @@ -30,3 +29,9 @@ void _free(void* ptr) pure { free((cast(void**)ptr)[-1]); } + + +private: + +extern(C) void* malloc(size_t size) pure; +extern(C) void free(void* ptr) pure; diff --git a/src/sys/posix/alloc.d b/src/sys/posix/alloc.d index d0d9cec..497414e 100644 --- a/src/sys/posix/alloc.d +++ b/src/sys/posix/alloc.d @@ -1,6 +1,5 @@ module sys.posix.alloc; -import urt.mem : free; import urt.mem.alloc : MemFlags; nothrow @nogc: @@ -23,6 +22,7 @@ void _free(void* ptr) pure free(ptr); } + size_t _memsize(void* ptr) pure { return malloc_usable_size(ptr); @@ -46,6 +46,7 @@ extern(C) int posix_memalign(void** memptr, size_t alignment, size_t size) pure; extern(C) size_t malloc_usable_size(void* ptr) pure; extern(C) void* mmap(void* addr, size_t length, int prot, int flags, int fd, long offset) pure; extern(C) int munmap(void* addr, size_t length) pure; +extern(C) void free(void* ptr) pure; enum PROT_READ = 0x1; enum PROT_WRITE = 0x2; diff --git a/src/sys/rp2350/alloc.d b/src/sys/rp2350/alloc.d index eef19ff..695118e 100644 --- a/src/sys/rp2350/alloc.d +++ b/src/sys/rp2350/alloc.d @@ -1,6 +1,5 @@ module sys.rp2350.alloc; -import urt.mem : malloc, free; import urt.mem.alloc : MemFlags; nothrow @nogc: @@ -30,3 +29,9 @@ void _free(void* ptr) pure { free((cast(void**)ptr)[-1]); } + + +private: + +extern(C) void* malloc(size_t size) pure; +extern(C) void free(void* ptr) pure; diff --git a/src/sys/stm32/alloc.d b/src/sys/stm32/alloc.d index f7dd09d..9136bf8 100644 --- a/src/sys/stm32/alloc.d +++ b/src/sys/stm32/alloc.d @@ -1,6 +1,5 @@ module sys.stm32.alloc; -import urt.mem : malloc, free; import urt.mem.alloc : MemFlags; nothrow @nogc: @@ -30,3 +29,9 @@ void _free(void* ptr) pure { free((cast(void**)ptr)[-1]); } + + +private: + +extern(C) void* malloc(size_t size) pure; +extern(C) void free(void* ptr) pure; diff --git a/src/urt/array.d b/src/urt/array.d index d2e9acb..9f2897c 100644 --- a/src/urt/array.d +++ b/src/urt/array.d @@ -1,7 +1,10 @@ module urt.array; -import urt.mem; -import urt.traits : is_some_char, is_primitive, is_trivial; +import urt.lifetime : move, emplace, moveEmplace; +import urt.mem : memcpy, memmove, memset; +import urt.mem.alloc; +import urt.mem.allocator : NoGCAllocator; +import urt.traits : is_some_char, is_primitive, is_trivial, Unqual; nothrow @nogc: @@ -84,7 +87,7 @@ size_t findFirst(T, U)(const(T)[] arr, auto ref const U el) if (!is_some_char!T) { size_t i = 0; - while (i < arr.length && !arr[i].elCmp(el)) + while (i < arr.length && !arr[i].el_cmp(el)) ++i; return i; } @@ -94,7 +97,7 @@ size_t findLast(T, U)(const(T)[] arr, auto ref const U el) if (!is_some_char!T) { ptrdiff_t last = arr.length-1; - while (last >= 0 && !arr[last].elCmp(el)) + while (last >= 0 && !arr[last].el_cmp(el)) --last; return last < 0 ? arr.length : last; } @@ -192,17 +195,153 @@ inout(T)* search(T)(inout(T)[] arr, bool delegate(ref const T) nothrow @nogc pre return null; } -U[] copyTo(T, U)(T[] arr, U[] dest) +// TODO: use is_trivially_copy_constructible! +private enum copy_use_memcpy(T, U) = is_trivial!T && is(Unqual!T == U); +private enum copy_use_memcpy(T) = copy_use_memcpy!(T, T); +// TODO: use is_trivially_move_constructible! +private enum move_use_memcpy(T, U) = is_trivial!T && is(Unqual!T == U); +private enum move_use_memcpy(T) = move_use_memcpy!(T, T); + +U[] copy_to(bool overlapping = false, bool move = false, T, U)(T[] arr, U[] dest) { + alias UT = Unqual!T; + enum same_type = is(UT == U); + enum both_byte = (is(UT == ubyte) || is(UT == byte) || is(UT == char) || is(UT == void)) && + (is(U == ubyte) || is(U == byte) || is(U == char) || is(U == void)); + + static assert (!overlapping || same_type, "overlapping copy must be same type"); + assert(dest.length >= arr.length); - dest[0 .. arr.length] = arr[]; + + if (__ctfe) + { + static if (same_type) + { + // handle overlapping copies correctly where src < dst + if (arr.ptr < dest.ptr) + { + for (ptrdiff_t i = ptrdiff_t(arr.length) - 1; i >= 0; --i) + dest[i] = arr[i]; + return dest[0 .. arr.length]; + } + } + for (size_t i = 0; i < arr.length; ++i) + dest[i] = arr[i]; + } + else + { + static if (!same_type && both_byte) + return copy_to!(overlapping, move)(cast(ubyte[])arr, cast(ubyte[])dest); + static if ((!move && copy_use_memcpy!(T, U)) || (move && move_use_memcpy!(T, U))) + { + static if (move && copy_use_memcpy!(T, U)) + return copy_to!(overlapping, false, T, U)(arr, dest); + else static if (overlapping) + memmove(dest.ptr, arr.ptr, arr.length * T.sizeof); + else + memcpy(dest.ptr, arr.ptr, arr.length * T.sizeof); + } + else static if (move) + { +// pragma(msg, "move: ", T, " --> ", U, is(T == class) ? " *** class" : ""); + static if (overlapping) + { + if (arr.ptr < dest.ptr && dest.ptr < arr.ptr + arr.length) + { + for (ptrdiff_t i = ptrdiff_t(arr.length) - 1; i >= 0; --i) + .move(arr[i], dest[i]); + return dest[0 .. arr.length]; + } + } + for (size_t i = 0; i < arr.length; ++i) + .move(arr[i], dest[i]); + } + else + { +// pragma(msg, "copy: ", T, " --> ", U, is(T == class) ? " *** class" : ""); + static if (overlapping) + { + if (arr.ptr < dest.ptr && dest.ptr < arr.ptr + arr.length) + { + for (ptrdiff_t i = ptrdiff_t(arr.length) - 1; i >= 0; --i) + dest[i] = arr[i]; + return dest[0 .. arr.length]; + } + } + else static if (same_type) + dest[] = arr[]; + else for (size_t i = 0; i < arr.length; ++i) + dest[i] = arr[i]; + } + } return dest[0 .. arr.length]; } +U[] move_to(bool overlapping = false, T, U)(T[] arr, U[] dest) + => copy_to!(overlapping, true, T, U)(arr, dest); + +void emplace_all(bool move = false, T, U)(T[] arr, U[] dest) +{ + assert(!__ctfe, "emplace_all should not be called at compile time"); + + static if (move && move_use_memcpy!(T, U)) + move_to(cast(Unqual!T[])arr, dest); + else static if ((!move && copy_use_memcpy!(T, U)) || (is(T : U) && is_trivial!U)) + copy_to(cast(Unqual!T[])arr, dest); + else static if (move) + { +// pragma(msg, "move-emplace: ", T, " --> ", U); + for (size_t i = 0; i < arr.length; ++i) + moveEmplace(arr[i], dest[i]); + } + else + { +// pragma(msg, "emplace: ", T, " --> ", U); + for (size_t i = 0; i < arr.length; ++i) + emplace!T(&dest[i], arr[i]); + } +} +void move_emplace_all(T, U)(T[] arr, U[] dest) + => emplace_all!(true, T, U)(arr, dest); -T[] duplicate(T)(const T[] src, NoGCAllocator allocator) nothrow @nogc +void destroy_all(bool initialize = true, T)(T[] arr) { - T[] r = cast(T[])allocator.alloc(src.length * T.sizeof); - r[] = src[]; + assert(!__ctfe, "destroy_all should not be called at compile time"); + + static if (is_trivial!T) + { + static if (initialize) + init_all(arr); + } + else + { + for (uint i = 0; i < arr.length; ++i) + destroy!initialize(arr[i]); + } +} + +void init_all(T)(T[] arr) +{ + assert(!__ctfe, "init_all should not be called at compile time"); + + static if (is_trivial!T) + { + static if (__traits(isZeroInit, T)) + memset(arr.ptr, 0, arr.length * T.sizeof); + else + arr[] = T.init; + } + else for (size_t i = 0; i < arr.length; ++i) + emplace!T(&arr[i]); +} + +T[] duplicate(T)(const T[] src, NoGCAllocator allocator = null) nothrow @nogc +{ + T[] r; + if (allocator) + r = cast(T[])allocator.alloc(src.length * T.sizeof); + else + r = cast(T[])alloc(src.length * T.sizeof); + emplace_all(src, r); return r; } @@ -246,8 +385,7 @@ struct Array(T, size_t EmbedCount = 0) { debug assert(arr.length <= uint.max); reserve(arr.length); - for (uint i = 0; i < arr.length; ++i) - emplace!T(&ptr[i], arr.ptr[i]); + emplace_all(arr[], ptr[0..arr.length]); _length = cast(uint)arr.length; } @@ -275,7 +413,7 @@ struct Array(T, size_t EmbedCount = 0) ~this() { clear(); - if (hasAllocation()) + if (has_allocation()) free(ptr); } @@ -306,8 +444,7 @@ nothrow @nogc: debug assert(arr.length <= uint.max); clear(); reserve(arr.length); - for (uint i = 0; i < arr.length; ++i) - emplace!T(&ptr[i], arr.ptr[i]); + emplace_all(arr[], ptr[0 .. arr.length]); _length = cast(uint)arr.length; } @@ -341,33 +478,17 @@ nothrow @nogc: { static if (is(Things[i] == V[], V) && is(V : T)) { - static if (copy_elements) - { - foreach (ref el; things[i]) - ptr[_length++] = el; - } - else - { - foreach (ref el; things[i]) - emplace!T(&ptr[_length++], el); - } + emplace_all(things[i][], ptr[_length .. _length + things[i].length]); + _length += cast(uint)things[i].length; } else static if (is(Things[i] == V[M], V, size_t M) && is(V : T)) { - static if (copy_elements) - { - foreach (ref el; things[i]) - ptr[_length++] = el; - } - else - { - foreach (ref el; things[i]) - emplace!T(&ptr[_length++], el); - } + emplace_all(things[i][], ptr[_length .. _length + M]); + _length += cast(uint)M; } else static if (is(Things[i] : T)) { - static if (copy_elements) + static if (is_trivial!T) ptr[_length++] = things[i]; else emplace!T(&ptr[_length++], forward!(things[i])); @@ -385,18 +506,20 @@ nothrow @nogc: // we need to tighten this up! ref Array!(T, EmbedCount) concat(Things...)(auto ref Things things) { + pragma(inline, true); clear(); - append(forward!things); - return this; + return append(forward!things); } ref Array!(T, EmbedCount) append(Things...)(auto ref Things things) { - import urt.string.format : _concat = concat; + import urt.string.format : _concat = concat_impl, normalise_args; - size_t ext_len = _concat(null, things).length; + auto args = normalise_args(things); + + size_t ext_len = _concat(null, args).length; reserve(_length + ext_len); - _concat(ptr[_length .. _length + ext_len], forward!things); + _concat(ptr[_length .. _length + ext_len], args); _length += ext_len; return this; } @@ -420,16 +543,7 @@ nothrow @nogc: size_t old_len = _length; reserve(_length + length); static if (do_init) - { - foreach (i; _length .. _length + length) - { - // TODO: replace with palcement new... - static if (copy_elements) - ptr[i] = T.init; - else - emplace!T(&ptr[i]); - } - } + init_all(ptr[_length .. _length + length]); _length += cast(uint)length; return ptr[old_len .. _length]; } @@ -450,7 +564,7 @@ nothrow @nogc: return ptr[_length - 1]; } - ref T pushFront() + ref T pushFront()() { static if (is(T == class) || is(T == interface)) return pushFront(null); @@ -460,26 +574,24 @@ nothrow @nogc: ref T pushFront(U)(auto ref U item) if (is(U : T)) { - static if (is(T == class) || is(T == interface)) - { - reserve(_length + 1); - for (uint i = _length++; i > 0; --i) - ptr[i] = ptr[i-1]; - return (ptr[0] = item); - } + reserve(_length + 1); + static if (is_trivial!T) + memmove(ptr + 1, ptr, _length++ * T.sizeof); else { - reserve(_length + 1); for (uint i = _length++; i > 0; --i) { moveEmplace!T(ptr[i-1], ptr[i]); destroy!false(ptr[i-1]); } - return *emplace!T(&ptr[0], forward!item); } + static if (is(T == class) || is(T == interface)) + return (ptr[0] = item); + else + return *emplace!T(&ptr[0], forward!item); } - ref T pushBack() + ref T pushBack()() { static if (is(T == class) || is(T == interface)) return pushBack(null); @@ -500,12 +612,17 @@ nothrow @nogc: if (!is(T == class) && !is(T == interface)) { reserve(_length + 1); - for (uint i = _length++; i > 0; --i) + static if (is_trivial!T) + memmove(ptr + 1, ptr, _length++ * T.sizeof); + else { - moveEmplace(ptr[i-1], ptr[i]); - destroy!false(ptr[i-1]); + for (uint i = _length++; i > 0; --i) + { + moveEmplace!T(ptr[i-1], ptr[i]); + destroy!false(ptr[i-1]); + } } - retirn *emplace!T(&ptr[0], forward!args); + return *emplace!T(&ptr[0], forward!args); } ref T emplaceBack(Args...)(auto ref Args args) @@ -517,132 +634,57 @@ nothrow @nogc: T popFront() { - debug assert(_length > 0); - - // TODO: this should be removed and uses replaced with a queue container - static if (is(T == class) || is(T == interface)) - { - T copy = ptr[0]; - for (uint i = 1; i < _length; ++i) - ptr[i-1] = ptr[i]; - ptr[--_length] = null; - return copy; - } - else - { - T copy = ptr[0].move; - for (uint i = 1; i < _length; ++i) - { - destroy!false(ptr[i-1]); - moveEmplace(ptr[i], ptr[i-1]); - } - destroy!false(ptr[--_length]); - return copy; - } + debug assert(_length > 0, "Range error"); + T r = ptr[0].move; + remove(0); + return r; } T popBack() { - debug assert(_length > 0); - - static if (is(T == class) || is(T == interface)) - { - T copy = ptr[--_length]; - ptr[_length] = null; - return copy; - } - else - { - T copy = ptr[--_length].move; - destroy!false(ptr[_length]); - return copy; - } - } - - Array!T takeFront(size_t count) - { - assert(false, "TODO"); - } - - Array!(T, N) takeFront(size_t N)() - { - assert(false, "TODO"); + debug assert(_length > 0, "Range error"); + T r = ptr[_length - 1].move; + destroy!false(ptr[--_length]); + return r; } - Array!T takeBack(size_t count) + Array!T takeFront()(size_t count) { - assert(false, "TODO"); + auto r = Array!T(Reserve, count); + move_emplace_all(ptr[0 .. count], r.ptr[0 .. count]); + r._length = cast(uint)count; + remove(0, count); + return r; } - Array!(T, N) takeBack(size_t N)() + Array!T takeBack()(size_t count) { - assert(false, "TODO"); - } - - void remove(size_t i) - { - debug assert(i < _length); - - static if (is(T == class) || is(T == interface)) - { - ptr[i .. _length - 1] = ptr[i + 1 .. _length]; - ptr[--_length] = null; - } - else - { - destroy!false(ptr[i]); - for (size_t j = i + 1; j < _length; ++j) - { - moveEmplace(ptr[j], ptr[j-1]); - destroy!false(ptr[j]); - } - --_length; - } + auto r = Array!T(Reserve, count); + move_emplace_all(ptr[_length - count .. _length], r.ptr[0 .. count]); + r._length = cast(uint)count; + remove(_length - count, count); + return r; } - void remove(size_t i, size_t count) + void remove(size_t i, size_t count = 1) { - debug assert(i + count <= _length); - - static if (is(T == class) || is(T == interface)) - { - ptr[i .. _length - count] = ptr[i + count .. _length]; - ptr[_length - count .. _length] = null; - _length -= cast(uint)count; - } - else - { - for (size_t j = i; j < i + count; ++j) - destroy!false(ptr[j]); - for (size_t j = i + count; j < _length; ++j) - { - moveEmplace(ptr[j], ptr[j - count]); - destroy!false(ptr[j]); - } - } + debug assert(i + count <= _length, "Range error"); + if (i < _length - count) + move_to!true(ptr[i + count .. _length], ptr[i .. _length - count]); + destroy_all!false(ptr[_length - count .. length]); _length -= cast(uint)count; } - - void remove(const(T)* pItem) { remove(ptr[0 .. _length].indexOfElement(pItem)); } void removeFirst(U)(ref const U item) { remove(ptr[0 .. _length].findFirst(item)); } - void removeSwapLast(size_t i) + void removeSwapLast(size_t i, size_t count = 1) { - assert(i < _length, "Range error"); - static if (is(T == class) || is(T == interface)) - { - ptr[i] = ptr[--_length]; - ptr[_length] = null; - } - else - { - destroy!false(ptr[i]); - emplace!T(&ptr[i], ptr[--_length].move); - destroy!false(ptr[_length]); - } + debug assert(i + count <= _length, "Range error"); + if (i < _length - count) + move_to!true(ptr[_length - count .. _length], ptr[i .. count]); + destroy_all!false(ptr[_length - count .. length]); + _length -= cast(uint)count; } - void removeSwapLast(const(T)* pItem) { removeSwapLast(ptr[0 .. _length].indexOfElement(pItem)); } void removeFirstSwapLast(U)(ref const U item) { removeSwapLast(ptr[0 .. _length].findFirst(item)); } @@ -655,9 +697,9 @@ nothrow @nogc: inout(void)[] getBuffer() inout { static if (EmbedCount > 0) - return ptr ? ptr[0 .. allocCount()] : embed[]; + return ptr ? ptr[0 .. alloc_count()] : embed[]; else - return ptr ? ptr[0 .. allocCount()] : null; + return ptr ? ptr[0 .. alloc_count()] : null; } bool opCast(T : bool)() const pure @@ -706,91 +748,46 @@ nothrow @nogc: void reserve(size_t count) { - if (count > allocCount()) + if (count > alloc_count()) { debug assert(count <= uint.max, "Exceed maximum size"); - T* newArray = allocate(cast(uint)count); - // TODO: POD should memcpy... (including class) + T* new_array = allocate(cast(uint)count); + move_emplace_all(ptr[0 .. _length], new_array[0 .. _length]); + destroy_all!false(ptr[0 .. _length]); - static if (is(T == class) || is(T == interface)) - { - for (uint i = 0; i < _length; ++i) - newArray[i] = ptr[i]; - } - else - { - for (uint i = 0; i < _length; ++i) - { - moveEmplace(ptr[i], newArray[i]); - destroy!false(ptr[i]); - } - } - - if (hasAllocation()) + if (has_allocation()) free(ptr); - - ptr = newArray; + ptr = new_array; } } - void alloc(size_t count) - { - assert(false); - } - void resize(size_t count) { if (count == _length) return; - if (count < _length) { - static if (is(T == class) || is(T == interface)) - { - for (ptrdiff_t i = _length - 1; i >= count; --i) - ptr[i] = null; - } - else - { - for (ptrdiff_t i = _length - 1; i >= count; --i) - destroy!false(ptr[i]); - } + destroy_all!false(ptr[count .. _length]); _length = cast(uint)count; } else { reserve(count); - static if (is(T == class) || is(T == interface)) - { - foreach (i; _length .. count) - ptr[i] = null; - } - else - { - foreach (i; _length .. count) - emplace!T(&ptr[i]); - } + init_all(ptr[_length .. count]); _length = cast(uint)count; } } void clear() { - static if (!is(T == class) && !is(T == interface)) - for (uint i = 0; i < _length; ++i) - destroy!false(ptr[i]); + destroy_all!false(ptr[0 .. _length]); _length = 0; } import urt.string.format : FormatArg, formatValue; ptrdiff_t toString()(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const - { - static if (is(T == class) || is(T == interface)) - assert(false, "TODO: class toString is not @nogc!"); - else - return formatValue(ptr[0 .. _length], buffer, format, formatArgs); - } + => formatValue(ptr[0 .. _length], buffer, format, formatArgs); ptrdiff_t fromString()(const(char)[] s) { @@ -798,29 +795,27 @@ nothrow @nogc: } private: - enum copy_elements = is(T == class) || is(T == interface) || is_primitive!T || is_trivial!T; - uint _length; static if (EmbedCount > 0) T[EmbedCount] embed = void; - bool hasAllocation() const pure + bool has_allocation() const pure { static if (EmbedCount > 0) return ptr && ptr != embed.ptr; else return ptr !is null; } - uint allocCount() const pure - => hasAllocation() ? (cast(uint*)ptr)[-1] : EmbedCount; + uint alloc_count() const pure + => has_allocation() ? (cast(uint*)ptr)[-1] : EmbedCount; T* allocate(uint count) { enum AllocAlignment = T.alignof < 4 ? 4 : T.alignof; enum AllocPrefixBytes = T.sizeof < 4 ? 4 : T.sizeof; - void[] mem = defaultAllocator().alloc(AllocPrefixBytes + T.sizeof * count, AllocAlignment); + void[] mem = .alloc(AllocPrefixBytes + T.sizeof * count, AllocAlignment); T* array = cast(T*)(mem.ptr + AllocPrefixBytes); (cast(uint*)array)[-1] = count; return array; @@ -829,11 +824,11 @@ private: { enum AllocPrefixBytes = T.sizeof < 4 ? 4 : T.sizeof; - defaultAllocator().free((cast(void*)ptr - AllocPrefixBytes)[0 .. AllocPrefixBytes + T.sizeof * allocCount()]); + .free((cast(void*)ptr - AllocPrefixBytes)[0 .. AllocPrefixBytes + T.sizeof * alloc_count()]); } pragma(inline, true) - static uint numToAlloc(uint i) pure + static uint num_to_alloc(uint i) pure { // TODO: i'm sure we can imagine a better heuristic... return i > 16 ? i * 2 : 16; @@ -851,7 +846,7 @@ struct SharedArray(T) this(this) { if (ptr) - incRef(); + inc_ref(); } this(typeof(null)) {} @@ -861,7 +856,7 @@ struct SharedArray(T) ptr = val.ptr; _length = val._length; if (ptr) - incRef(); + inc_ref(); } this(U)(U[] arr) @@ -890,23 +885,21 @@ nothrow @nogc: ptr = val.ptr; _length = val._length; if (ptr) - incRef(); + inc_ref(); } void opAssign(U)(U[] arr) if (is(U : T)) { debug assert(arr.length <= uint.max); - uint len = cast(uint)arr.length; + _length = cast(uint)arr.length; clear(); - _length = len; if (len > 0) { - ptr = allocate(len); - for (uint i = 0; i < len; ++i) - emplace!T(&ptr[i], arr.ptr[i]); + ptr = allocate(_length); + emplace_all(arr[], ptr[0 .. _length]); } else ptr = null; @@ -937,9 +930,9 @@ nothrow @nogc: // inout(void)[] getBuffer() inout // { // static if (EmbedCount > 0) -// return ptr ? ptr[0 .. allocCount()] : embed[]; +// return ptr ? ptr[0 .. alloc_count()] : embed[]; // else -// return ptr ? ptr[0 .. allocCount()] : null; +// return ptr ? ptr[0 .. alloc_count()] : null; // } bool opCast(T : bool)() const @@ -971,12 +964,9 @@ nothrow @nogc: void clear() { - static if (!is(T == class) && !is(T == interface)) - for (uint i = 0; i < _length; ++i) - ptr[i].destroy!false(); - + destroy_all!false(ptr[0 .. _length]); if (ptr) - decRef(); + dec_ref(); ptr = null; _length = 0; } @@ -985,12 +975,12 @@ private: T* ptr; uint _length; - void incRef() + void inc_ref() { ++(cast(uint*)ptr)[-1]; } - void decRef() + void dec_ref() { uint* rc = cast(uint*)ptr - 1; if (*rc == 0) @@ -1003,7 +993,7 @@ private: { enum AllocAlignment = T.alignof < uint.alignof ? uint.alignof : T.alignof; - void[] mem = defaultAllocator().alloc(AllocAlignment + T.sizeof * count, AllocAlignment); + void[] mem = .alloc(AllocAlignment + T.sizeof * count, AllocAlignment); T* array = cast(T*)(mem.ptr + AllocPrefixBytes); (cast(uint*)array)[-1] = 0; return array; @@ -1012,7 +1002,7 @@ private: { enum AllocAlignment = T.alignof < uint.alignof ? uint.alignof : T.alignof; - defaultAllocator().free((cast(void*)ptr - AllocAlignment)[0 .. AllocAlignment + T.sizeof * allocCount()]); + .free((cast(void*)ptr - AllocAlignment)[0 .. AllocAlignment + T.sizeof * alloc_count()]); } } @@ -1020,21 +1010,21 @@ private: private: pragma(inline, true) -bool elCmp(T)(const T a, const T b) +bool el_cmp(T)(const T a, const T b) if (is(T == class) || is(T == interface)) { return a is b; } pragma(inline, true) -bool elCmp(T)(const T a, const T b) +bool el_cmp(T)(const T a, const T b) if (is(T == U[], U)) { return a[] == b[]; } pragma(inline, true) -bool elCmp(T)(auto ref const T a, auto ref const T b) +bool el_cmp(T)(auto ref const T a, auto ref const T b) if (!is(T == class) && !is(T == interface) && !is(T == U[], U)) { return a == b; diff --git a/src/urt/internal/exception.d b/src/urt/internal/exception.d index cc51feb..0d5684f 100644 --- a/src/urt/internal/exception.d +++ b/src/urt/internal/exception.d @@ -1692,15 +1692,15 @@ version (Win64) void initialize(size_t initial_capacity) nothrow @nogc { - import urt.mem : malloc; - base = malloc(initial_capacity); + import urt.mem : alloc; + base = alloc(initial_capacity).ptr; capacity = initial_capacity; length = size_t.sizeof; // offset 0 reserved (null sentinel) } size_t alloc(size_t size) nothrow @nogc { - import urt.mem : malloc, memcpy; + import urt.mem : alloc, memcpy; auto offset = length; enum align_mask = size_t.sizeof - 1; auto new_length = (length + size + align_mask) & ~align_mask; @@ -1709,7 +1709,7 @@ version (Win64) new_capacity *= 2; if (new_capacity != capacity) { - auto new_base = malloc(new_capacity); + auto new_base = alloc(new_capacity).ptr; memcpy(new_base, base, length); // Old base leaks — may be referenced by in-flight exceptions. base = new_base; @@ -1736,8 +1736,8 @@ else // Win32 { private ImgPtr!T eh_malloc(T)(size_t size = T.sizeof) nothrow @nogc { - import urt.mem : malloc; - return cast(T*) malloc(size); + import urt.mem : alloc; + return cast(T*)alloc(size).ptr; } private T* to_pointer(T)(T* img_ptr) nothrow @nogc @@ -1880,13 +1880,13 @@ private: void grow() { - import urt.mem : malloc, free, memcpy; + import urt.mem : alloc, free, memcpy; immutable ncap = _cap ? 2 * _cap : 16; - auto p = cast(Throwable*) malloc(ncap * size_t.sizeof); + auto p = cast(Throwable*)alloc(ncap * size_t.sizeof).ptr; if (_length > 0) memcpy(p, _p, _length * size_t.sizeof); if (_p !is null) - free(_p); + free((cast(void*)_p)[0 .. _cap * size_t.sizeof]); _p = p; _cap = ncap; } @@ -3069,11 +3069,11 @@ struct ExceptionHeader static ExceptionHeader* create(Throwable o) nothrow @nogc { - import urt.mem : calloc; + import urt.mem.alloc : alloc; auto eh = &ehstorage; if (eh.object) { - eh = cast(ExceptionHeader*) calloc(1, ExceptionHeader.sizeof); + eh = cast(ExceptionHeader*)alloc(ExceptionHeader.sizeof).ptr; if (!eh) dwarf_terminate(__LINE__); } @@ -3084,10 +3084,10 @@ struct ExceptionHeader static void release(ExceptionHeader* eh) nothrow @nogc { - import urt.mem : free; + import urt.mem.alloc : free; *eh = ExceptionHeader.init; if (eh != &ehstorage) - free(eh); + free(eh[0..1]); } void push() nothrow @nogc diff --git a/src/urt/internal/mbedtls.d b/src/urt/internal/mbedtls.d index 5b3b869..db9dcfe 100644 --- a/src/urt/internal/mbedtls.d +++ b/src/urt/internal/mbedtls.d @@ -115,11 +115,11 @@ size_t urt_sizeof_x509_crt(); size_t urt_sizeof_ssl_context(); size_t urt_sizeof_ssl_config(); -import urt.mem; +import urt.mem.alloc; mbedtls_entropy_context* urt_entropy_new() { - auto ctx = cast(mbedtls_entropy_context*)calloc(1, urt_sizeof_entropy()); + auto ctx = cast(mbedtls_entropy_context*)alloc(urt_sizeof_entropy()).ptr; if (ctx) mbedtls_entropy_init(ctx); return ctx; @@ -130,13 +130,13 @@ void urt_entropy_delete(mbedtls_entropy_context* ctx) if (ctx) { mbedtls_entropy_free(ctx); - free(ctx); + free((cast(void*)ctx)[0..urt_sizeof_entropy()]); } } mbedtls_ctr_drbg_context* urt_ctr_drbg_new() { - auto ctx = cast(mbedtls_ctr_drbg_context*)calloc(1, urt_sizeof_ctr_drbg()); + auto ctx = cast(mbedtls_ctr_drbg_context*)alloc(urt_sizeof_ctr_drbg()).ptr; if (ctx) mbedtls_ctr_drbg_init(ctx); return ctx; @@ -147,13 +147,13 @@ void urt_ctr_drbg_delete(mbedtls_ctr_drbg_context* ctx) if (ctx) { mbedtls_ctr_drbg_free(ctx); - free(ctx); + free((cast(void*)ctx)[0..urt_sizeof_ctr_drbg()]); } } mbedtls_x509_crt* urt_x509_crt_new() { - auto ctx = cast(mbedtls_x509_crt*)calloc(1, urt_sizeof_x509_crt()); + auto ctx = cast(mbedtls_x509_crt*)alloc(urt_sizeof_x509_crt()).ptr; if (ctx) mbedtls_x509_crt_init(ctx); return ctx; @@ -164,13 +164,13 @@ void urt_x509_crt_delete(mbedtls_x509_crt* crt) if (crt) { mbedtls_x509_crt_free(crt); - free(crt); + free((cast(void*)crt)[0..urt_sizeof_x509_crt()]); } } mbedtls_ssl_context* urt_ssl_new() { - auto ctx = cast(mbedtls_ssl_context*)calloc(1, urt_sizeof_ssl_context()); + auto ctx = cast(mbedtls_ssl_context*)alloc(urt_sizeof_ssl_context()).ptr; if (ctx) mbedtls_ssl_init(ctx); return ctx; @@ -181,13 +181,13 @@ void urt_ssl_delete(mbedtls_ssl_context* ssl) if (ssl) { mbedtls_ssl_free(ssl); - free(ssl); + free((cast(void*)ssl)[0..urt_sizeof_ssl_context()]); } } mbedtls_ssl_config* urt_ssl_config_new() { - auto ctx = cast(mbedtls_ssl_config*)calloc(1, urt_sizeof_ssl_config()); + auto ctx = cast(mbedtls_ssl_config*)alloc(urt_sizeof_ssl_config()).ptr; if (ctx) mbedtls_ssl_config_init(ctx); return ctx; @@ -198,6 +198,6 @@ void urt_ssl_config_delete(mbedtls_ssl_config* conf) if (conf) { mbedtls_ssl_config_free(conf); - free(conf); + free((cast(void*)conf)[0..urt_sizeof_ssl_config()]); } } diff --git a/src/urt/log.d b/src/urt/log.d index 11de672..e7a3bb7 100644 --- a/src/urt/log.d +++ b/src/urt/log.d @@ -1,6 +1,7 @@ module urt.log; -import urt.mem.temp : tconcat, tformat; +import urt.mem.temp : tconcat, tconcat_impl, tformat; +import urt.string.format : normalise_args; import urt.time; nothrow @nogc: @@ -145,7 +146,7 @@ void write_log(T...)(Severity severity, const(char)[] tag, const(char)[] object_ static if (T.length == 1 && (is(T[0] : const(char)[]) || is(T[0] : const String) || is(T[0] : const MutableString!N, size_t N) || is(T[0] : const Array!char))) auto msg = LogMessage(severity, tag, object_name, args[0][], getTime()); else - auto msg = LogMessage(severity, tag, object_name, tconcat(args), getTime()); + auto msg = LogMessage(severity, tag, object_name, tconcat_impl(normalise_args(args)), getTime()); write_log(msg); } diff --git a/src/urt/map.d b/src/urt/map.d index 3a624ac..325641d 100644 --- a/src/urt/map.d +++ b/src/urt/map.d @@ -42,14 +42,14 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) } size_t length() const nothrow - => numNodes; + => _num_modes; bool empty() const nothrow - => numNodes == 0; + => _num_modes == 0; void clear() nothrow { - destroy(pRoot); - pRoot = null; + destroy(_root); + _root = null; } V* insert(_K, _V)(auto ref _K key, auto ref _V val) @@ -167,11 +167,12 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) { Node* node = cast(Node*)Allocator.instance.alloc(Node.sizeof); emplace(&node.kvp, forward!key, forward!val); - node.left = node.right = null; - node.height = 1; - pRoot = insert(pRoot, node); + node._base.left = node._base.right = null; + node._base.height = 1; + _root = insert(_root, node); return node.kvp.value; } + /+ V& replace(K &&key, V &&val) { @@ -180,7 +181,7 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) epConstruct(&node.kvp) KVP(std::move(key), std::move(val)); node.left = node.right = null; node.height = 1; - pRoot = insert(pRoot, node); + _root = insert(_root, node); return node.kvp.value; } V& replace(const K &key, V &&val) @@ -190,7 +191,7 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) epConstruct(&node.kvp) KVP(key, std::move(val)); node.left = node.right = null; node.height = 1; - pRoot = insert(pRoot, node); + _root = insert(_root, node); return node.kvp.value; } V& replace(K &&key, const V &val) @@ -200,7 +201,7 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) epConstruct(&node.kvp) KVP(std::move(key), val); node.left = node.right = null; node.height = 1; - pRoot = insert(pRoot, node); + _root = insert(_root, node); return node.kvp.value; } V& replace(const K &key, const V &val) @@ -210,7 +211,7 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) epConstruct(&node.kvp) KVP(key, val); node.left = node.right = null; node.height = 1; - pRoot = insert(pRoot, node); + _root = insert(_root, node); return node.kvp.value; } @@ -221,7 +222,7 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) epConstruct(&node.kvp) KVP(std::move(kvp)); node.left = node.right = null; node.height = 1; - pRoot = insert(pRoot, node); + _root = insert(_root, node); return node.kvp.value; } V& replace(const KVP &kvp) @@ -231,19 +232,19 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) epConstruct(&node.kvp) KVP(kvp); node.left = node.right = null; node.height = 1; - pRoot = insert(pRoot, node); + _root = insert(_root, node); return node.kvp.value; } +/ void remove(_K)(ref const _K key) { - pRoot = deleteNode(pRoot, key); + _root = delete_node(_root, key); } inout(V)* get(_K)(ref const _K key) inout { - inout(Node)* n = find(pRoot, key); + inout(Node)* n = find(_root, key); return n ? &n.kvp.value : null; } @@ -289,16 +290,16 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) // TODO: why don't the const overloads work properly? auto keys() const nothrow - => Range!(IterateBy.Keys, true)(pRoot); + => Range!(IterateBy.Keys, true)(_root); auto values() nothrow - => Range!(IterateBy.Values)(pRoot); + => Range!(IterateBy.Values)(_root); auto values() const nothrow - => Range!(IterateBy.Values, true)(pRoot); + => Range!(IterateBy.Values, true)(_root); auto opIndex() nothrow - => Range!(IterateBy.KVP)(pRoot); + => Range!(IterateBy.KVP)(_root); auto opIndex() const nothrow - => Range!(IterateBy.KVP, true)(pRoot); + => Range!(IterateBy.KVP, true)(_root); import urt.string.format : FormatArg, formatValue; ptrdiff_t toString()(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const @@ -367,273 +368,39 @@ private: nothrow: alias Node = AVLTreeNode!(K, V); - size_t numNodes = 0; - Node* pRoot = null; - - static int height(const(Node)* n) pure - { - return n ? n.height : 0; - } + size_t _num_modes = 0; + Node* _root = null; - static int maxHeight(const(Node)* n) pure - { - if (!n) - return 0; - if (n.left) - { - if (n.right) - return max(n.left.height, n.right.height); - else - return n.left.height; - } - if (n.right) - return n.right.height; - return 0; - } + static ptrdiff_t compare_node(const void* a, const void* b) pure + => Pred(*cast(K*)a, *cast(K*)b); - static int getBalance(Node* n) pure + static void free_node(void* p) { - return n ? height(n.left) - height(n.right) : 0; + Allocator.instance.freeT(cast(Node*)p); } - static Node* rightRotate(Node* y) pure - { - Node* x = y.left; - Node* T2 = x.right; - - // Perform rotation - x.right = y; - y.left = T2; - - // Update heights - y.height = maxHeight(y) + 1; - x.height = maxHeight(x) + 1; - - // Return new root - return x; - } - - static Node* leftRotate(Node* x) pure - { - Node* y = x.right; - Node* T2 = y.left; - - // Perform rotation - y.left = x; - x.right = T2; - - // Update heights - x.height = maxHeight(x) + 1; - y.height = maxHeight(y) + 1; - - // Return new root - return y; - } - - static inout(Node)* find(_K)(inout(Node)* n, ref const _K key) - { - if (n is null) - return null; - ptrdiff_t c = Pred(n.kvp.key, key); - if (c > 0) - return find(n.left, key); - if (c < 0) - return find(n.right, key); - return n; - } + static inout(Node)* find(_K)(inout(Node)* n, ref const _K key) pure + => cast(inout(Node)*)find_node(n.base, (a, b) => Pred(*cast(K*)a, *cast(_K*)b), &key); void destroy(Node* n) { - if (n is null) - return; - - destroy(n.left); - destroy(n.right); - - Allocator.instance.freeT(n); - - --numNodes; + _num_modes -= destroy_node(n.base, &free_node); } Node* insert(Node* n, Node* newnode) { - // 1. Perform the normal BST rotation - if (n is null) - { - ++numNodes; - return newnode; - } - - ptrdiff_t c = Pred(newnode.kvp.key, n.kvp.key); - if (c < 0) - n.left = insert(n.left, newnode); - else if (c > 0) - n.right = insert(n.right, newnode); - else - { - newnode.left = n.left; - newnode.right = n.right; - newnode.height = n.height; - - Allocator.instance.freeT(n); - - return newnode; - } - - // 2. Update height of this ancestor Node - n.height = maxHeight(n) + 1; - - // 3. get the balance factor of this ancestor Node to check whether - // this Node became unbalanced - int balance = getBalance(n); - - // If this Node becomes unbalanced, then there are 4 cases - - if (balance > 1) - { - ptrdiff_t lc = Pred(newnode.kvp.key, n.left.kvp.key); - // Left Left Case - if (lc < 0) - return rightRotate(n); - - // Left Right Case - if (lc > 0) - { - n.left = leftRotate(n.left); - return rightRotate(n); - } - } - - if (balance < -1) - { - ptrdiff_t rc = Pred(newnode.kvp.key, n.right.kvp.key); - - // Right Right Case - if (rc > 0) - return leftRotate(n); - - // Right Left Case - if (rc < 0) - { - n.right = rightRotate(n.right); - return leftRotate(n); - } - } - - // return the (unchanged) Node pointer - return n; + return cast(Node*)insert_node(n.base, newnode.base, _num_modes, + &compare_node, + &free_node); } - Node* deleteNode(_K)(Node* _pRoot, ref const _K key) + Node* delete_node(_K)(Node* _pRoot, ref const _K key) { - // STEP 1: PERFORM STANDARD BST DELETE - - if (_pRoot is null) - return _pRoot; - - ptrdiff_t c = Pred(_pRoot.kvp.key, key); - - // If the key to be deleted is smaller than the _pRoot's key, - // then it lies in left subtree - if (c > 0) - _pRoot.left = deleteNode(_pRoot.left, key); - - // If the key to be deleted is greater than the _pRoot's key, - // then it lies in right subtree - else if (c < 0) - _pRoot.right = deleteNode(_pRoot.right, key); - - // if key is same as _pRoot's key, then this is the Node - // to be deleted - else - _pRoot = doDelete(_pRoot); - - return rebalance(_pRoot); - } - - Node* doDelete(Node* _pRoot) - { - // Node with only one child or no child - if ((_pRoot.left is null) || (_pRoot.right is null)) - { - Node* temp = _pRoot.left ? _pRoot.left : _pRoot.right; - - // No child case - if (temp is null) - { - temp = _pRoot; - _pRoot = null; - } - else // One child case - { - // TODO: FIX THIS!! - // this is copying the child node into the parent node because there is no parent pointer - // DO: add parent pointer, then fix up the parent's child pointer to the child, and do away with this pointless copy! - *_pRoot = (*temp).move; // Copy the contents of the non-empty child - } - - Allocator.instance.freeT(temp); - - --numNodes; - } - else - { - // Node with two children: we replace 'this' node with the next one in sequence... - - // get the in-order successor: the 'next' item is the far left node on the right hand side) - Node* next = _pRoot.right; - while (next.left !is null) // find the leftmost leaf - next = next.left; - + return cast(Node*).delete_node(_pRoot.base, &key, _num_modes, &compare_node, (void* from, void* to) { // Copy the in-order successor's data to this Node - _pRoot.kvp.key = next.kvp.key; // we can't move the key, because deleteNode still needs to be able to find it - _pRoot.kvp.value = next.kvp.value.move; - - // Delete the node we just shifted - _pRoot.right = deleteNode(_pRoot.right, next.kvp.key); - } - - return _pRoot; - } - - Node* rebalance(Node* _pRoot) - { - // If the tree had only one Node then return - if (_pRoot is null) - return null; - - // STEP 2: UPDATE HEIGHT OF THE CURRENT NODE - _pRoot.height = max(height(_pRoot.left), height(_pRoot.right)) + 1; - - // STEP 3: GET THE BALANCE FACTOR OF THIS NODE (to check whether - // this Node became unbalanced) - int balance = getBalance(_pRoot); - - // If this Node becomes unbalanced, then there are 4 cases - - // Left Left Case - if (balance > 1 && getBalance(_pRoot.left) >= 0) - return rightRotate(_pRoot); - - // Left Right Case - if (balance > 1 && getBalance(_pRoot.left) < 0) - { - _pRoot.left = leftRotate(_pRoot.left); - return rightRotate(_pRoot); - } - - // Right Right Case - if (balance < -1 && getBalance(_pRoot.right) <= 0) - return leftRotate(_pRoot); - - // Right Left Case - if (balance < -1 && getBalance(_pRoot.right) > 0) - { - _pRoot.right = rightRotate(_pRoot.right); - return leftRotate(_pRoot); - } - - return _pRoot; + (cast(Node*)to).kvp.key = (cast(Node*)from).kvp.key; // we can't move the key, because delete_node still needs to be able to find it + (cast(Node*)to).kvp.value = (cast(Node*)from).kvp.value.move; + }, &free_node); } // static Node* clone(Node* pOld) @@ -749,14 +516,36 @@ public: } } } +struct BaseNode +{ + BaseNode* left, right; + int height; +} struct AVLTreeNode(K, V) { nothrow @nogc: - AVLTreeNode* left, right; + alias _base this; + + BaseNode _base; KVP!(K, V) kvp; - int height; + + inout(BaseNode)* base() inout pure @property + => &_base; + + inout(AVLTreeNode)* left() inout pure @property + => cast(inout(AVLTreeNode)*)_base.left; + void left(AVLTreeNode* node) pure @property + { + _base.left = node.base; + } + inout(AVLTreeNode)* right() inout pure @property + => cast(inout(AVLTreeNode)*)_base.right; + void right(AVLTreeNode* node) pure @property + { + _base.right = node.base; + } this() @disable; @@ -772,7 +561,7 @@ nothrow @nogc: left = rh.left; right = rh.right; kvp = rh.kvp; - height = rh.height; + _base.height = rh._base.height; } ref AVLTreeNode opAssign(ref AVLTreeNode rh) @@ -790,49 +579,6 @@ nothrow @nogc: } } -/+ -template -ptrdiff_t epStringify(Slice buffer, String epUnusedParam(format), const AVLTree &tree, const VarArg* epUnusedParam(pArgs)) -{ - size_t offset = 0; - if (buffer) - offset += String("{ ").copyTo(buffer); - else - offset += String("{ ").length; - - bool bFirst = true; - for (auto &&kvp : tree) - { - if (!bFirst) - { - if (buffer) - offset += String(", ").copyTo(buffer.drop(offset)); - else - offset += String(", ").length; - } - else - bFirst = false; - - if (buffer) - offset += epStringify(buffer.drop(offset), null, kvp, null); - else - offset += epStringify(null, null, kvp, null); - } - - if (buffer) - offset += String(" }").copyTo(buffer.drop(offset)); - else - offset += String(" }").length; - - return offset; -} -+/ - -//// Range retrieval -//template -//TreeRange> range(const AVLTree &input) { return TreeRange>(input); } - - unittest { @@ -1065,3 +811,273 @@ unittest assert(count == 2); } } + + +private: + +alias CompFn = ptrdiff_t function(const void* a, const void* b) pure nothrow @nogc; +alias MoveFn = void function(void* from, void* to) nothrow @nogc; +alias DestroyFn = void function(void* a) nothrow @nogc; + +int height(const(BaseNode)* n) pure +{ + return n ? n.height : 0; +} + +int max_height(const(BaseNode)* n) pure +{ + if (!n) + return 0; + if (n.left) + { + if (n.right) + return max(n.left.height, n.right.height); + else + return n.left.height; + } + if (n.right) + return n.right.height; + return 0; +} + +int get_balance(BaseNode* n) pure +{ + return n ? height(n.left) - height(n.right) : 0; +} + +BaseNode* right_rotate(BaseNode* y) pure +{ + BaseNode* x = y.left; + BaseNode* T2 = x.right; + + // Perform rotation + x.right = y; + y.left = T2; + + // Update heights + y.height = max_height(y) + 1; + x.height = max_height(x) + 1; + + // Return new root + return x; +} + +BaseNode* left_rotate(BaseNode* x) pure +{ + BaseNode* y = x.right; + BaseNode* T2 = y.left; + + // Perform rotation + y.left = x; + x.right = T2; + + // Update heights + x.height = max_height(x) + 1; + y.height = max_height(y) + 1; + + // Return new root + return y; +} + +BaseNode* rebalance(BaseNode* root) pure +{ + // If the tree had only one Node then return + if (root is null) + return null; + + // STEP 2: UPDATE HEIGHT OF THE CURRENT NODE + root.height = max(height(root.left), height(root.right)) + 1; + + // STEP 3: GET THE BALANCE FACTOR OF THIS NODE (to check whether + // this Node became unbalanced) + int balance = get_balance(root); + + // If this Node becomes unbalanced, then there are 4 cases + + // Left Left Case + if (balance > 1 && get_balance(root.left) >= 0) + return right_rotate(root); + + // Left Right Case + if (balance > 1 && get_balance(root.left) < 0) + { + root.left = left_rotate(root.left); + return right_rotate(root); + } + + // Right Right Case + if (balance < -1 && get_balance(root.right) <= 0) + return left_rotate(root); + + // Right Left Case + if (balance < -1 && get_balance(root.right) > 0) + { + root.right = right_rotate(root.right); + return left_rotate(root); + } + + return root; +} + +inout(BaseNode)* find_node(inout(BaseNode)* n, CompFn pred, const void* key) pure +{ + if (n is null) + return null; + ptrdiff_t c = pred(&n[1], key); + if (c > 0) + return find_node(n.left, pred, key); + if (c < 0) + return find_node(n.right, pred, key); + return n; +} + +size_t destroy_node(BaseNode* n, DestroyFn free_fun) +{ + if (n is null) + return 0; + + size_t count = destroy_node(n.left, free_fun); + count += destroy_node(n.right, free_fun); + free_fun(n); + return count + 1; +} + +BaseNode* insert_node(BaseNode* n, BaseNode* newnode, ref size_t num_nodes, CompFn pred, DestroyFn free_fun) +{ + // 1. Perform the normal BST rotation + if (n is null) + { + ++num_nodes; + return newnode; + } + + ptrdiff_t c = pred(&newnode[1], &n[1]); + if (c < 0) + n.left = insert_node(n.left, newnode, num_nodes, pred, free_fun); + else if (c > 0) + n.right = insert_node(n.right, newnode, num_nodes, pred, free_fun); + else + { + newnode.left = n.left; + newnode.right = n.right; + newnode.height = n.height; + + free_fun(n); + + return newnode; + } + + // 2. Update height of this ancestor Node + n.height = max_height(n) + 1; + + // 3. get the balance factor of this ancestor Node to check whether + // this Node became unbalanced + int balance = get_balance(n); + + // If this Node becomes unbalanced, then there are 4 cases + + if (balance > 1) + { + ptrdiff_t lc = pred(&newnode[1], &n.left[1]); + // Left Left Case + if (lc < 0) + return right_rotate(n); + + // Left Right Case + if (lc > 0) + { + n.left = left_rotate(n.left); + return right_rotate(n); + } + } + + if (balance < -1) + { + ptrdiff_t rc = pred(&newnode[1], &n.right[1]); + + // Right Right Case + if (rc > 0) + return left_rotate(n); + + // Right Left Case + if (rc < 0) + { + n.right = right_rotate(n.right); + return left_rotate(n); + } + } + + // return the (unchanged) Node pointer + return n; +} + +BaseNode* delete_node(BaseNode* root, const void* key, ref size_t num_nodes, CompFn pred, MoveFn move_fun, DestroyFn free_fun) +{ + // STEP 1: PERFORM STANDARD BST DELETE + + if (root is null) + return root; + + ptrdiff_t c = pred(&root[1], key); + + // If the key to be deleted is smaller than the root's key, + // then it lies in left subtree + if (c > 0) + root.left = delete_node(root.left, key, num_nodes, pred, move_fun, free_fun); + + // If the key to be deleted is greater than the root's key, + // then it lies in right subtree + else if (c < 0) + root.right = delete_node(root.right, key, num_nodes, pred, move_fun, free_fun); + + // if key is same as root's key, then this is the Node + // to be deleted + else + root = do_delete(root, num_nodes, pred, move_fun, free_fun); + + return rebalance(root); +} + +BaseNode* do_delete(BaseNode* root, ref size_t num_nodes, CompFn pred, MoveFn move_fun, DestroyFn free_fun) +{ + // Node with only one child or no child + if ((root.left is null) || (root.right is null)) + { + BaseNode* temp = root.left ? root.left : root.right; + + // No child case + if (temp is null) + { + temp = root; + root = null; + } + else // One child case + { + // TODO: FIX THIS!! + // this is copying the child node into the parent node because there is no parent pointer + // DO: add parent pointer, then fix up the parent's child pointer to the child, and do away with this pointless copy! + *root = (*temp).move; // Copy the tree structure (BaseNode fields) + move_fun(temp, root); // Copy the key/value data + } + + free_fun(temp); + + --num_nodes; + } + else + { + // Node with two children: we replace 'this' node with the next one in sequence... + + // get the in-order successor: the 'next' item is the far left node on the right hand side) + BaseNode* next = root.right; + while (next.left !is null) // find the leftmost leaf + next = next.left; + + move_fun(next, root); + + // Delete the node we just shifted + root.right = delete_node(root.right, &next[1], num_nodes, pred, move_fun, free_fun); + } + + return root; +} diff --git a/src/urt/mem/package.d b/src/urt/mem/package.d index 98acb69..f187ef8 100644 --- a/src/urt/mem/package.d +++ b/src/urt/mem/package.d @@ -2,6 +2,7 @@ module urt.mem; // TODO: remove these public imports, because this is pulled by object.d! public import urt.lifetime : emplace, moveEmplace, forward, move; +public import urt.mem.alloc; public import urt.mem.allocator; nothrow @nogc: @@ -16,11 +17,6 @@ extern(C) { nothrow @nogc: - void* malloc(size_t size) pure @trusted; - void* calloc(size_t num, size_t size) pure @trusted; - void* realloc(void* ptr, size_t new_size) pure @trusted; - void free(void* ptr) pure @trusted; - void* memcpy(void* dest, const void* src, size_t n) pure; void* memmove(void* dest, const void* src, size_t n) pure; void* memset(void* s, int c, size_t n) pure; diff --git a/src/urt/mem/temp.d b/src/urt/mem/temp.d index ada95d2..6a17a5e 100644 --- a/src/urt/mem/temp.d +++ b/src/urt/mem/temp.d @@ -150,18 +150,27 @@ const(char)[] tconcat(Args...)(ref Args args) } else { - import urt.string.format : concat; - const(char)[] r = concat(cast(char[])tempMem[alloc_offset..$], args); - if (!r) - { - alloc_offset = 0; - r = concat(cast(char[])tempMem[0..TempMemSize / 2], args); - } - alloc_offset += r.length; - return r; + pragma(inline, true); + import urt.string.format : normalise_args; + return tconcat_impl(normalise_args(args)); } } +import urt.meta.tuple : Tuple; +const(char)[] tconcat_impl(Args...)(Tuple!Args args) +{ + import urt.string.format : concat_impl; + const(char)[] r = concat_impl(cast(char[])tempMem[alloc_offset..$], args); + if (!r) + { + alloc_offset = 0; + r = concat_impl(cast(char[])tempMem[0..TempMemSize / 2], args); + } + alloc_offset += r.length; + return r; +} + + char[] tformat(Args...)(const(char)[] fmt, ref Args args) { import urt.string.format : format; diff --git a/src/urt/string/format.d b/src/urt/string/format.d index f188cb3..993a41b 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -17,99 +17,122 @@ debug alias StringifyFunc = ptrdiff_t delegate(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) nothrow @nogc; +struct Arg +{ + StringifyFunc fn; +} + +struct Format +{ + this(T)(ref const T value, const(char)[] format) pure nothrow @nogc + { + fn = get_to_string_func(value); + fmt = format; + } + + StringifyFunc fn; + const(char)[] fmt; +} ptrdiff_t toString(T)(ref const T value, char[] buffer, const(char)[] format = null, const(FormatArg)[] formatArgs = null) { debug InFormatFunction = true; - ptrdiff_t r = get_to_string_func(value)(buffer, null, null); + ptrdiff_t r = get_to_string_func(value)(buffer, format, formatArgs); debug InFormatFunction = false; return r; } alias formatValue = toString; // TODO: remove me? -char[] concat(Args...)(char[] buffer, ref const Args args) +auto normalise_args(Args...)(ref const Args args) { + import urt.meta.tuple : Tuple; alias NormalisedArgs = NormaliseArgs!Args; - enum is_normalised = is(Args == NormalisedArgs); + Tuple!(NormalisedArgs) result = void; - static if (Args.length == 0) - { - return buffer.ptr[0 .. 0]; - } - else static if (!is_normalised) +// pragma(msg, "[ ", Args, " ] --> [ ", NormalisedArgs, " ]"); + + static foreach (i; 0 .. Args.length) { - pragma(inline, true); - return concat!(NormalisedArgs)(buffer, args); + static if (is(NormalisedArgs[i] == char) || is(NormalisedArgs[i] == Format)) + result[i] = args[i]; + else static if (is(NormalisedArgs[i] == Arg)) + result[i] = Arg(get_to_string_func(args[i])); + else static if (is(NormalisedArgs[i] == Array!char) || is(NormalisedArgs[i] == String) || is(NormalisedArgs[i] == MutableString!N, size_t N)) + static assert(false, "use container[] at the callsite!"); + else + result[i] = args[i][]; } + return result; +} + +char[] concat(Args...)(char[] buffer, auto ref const Args args) +{ + pragma(inline, true); + + alias NormalisedArgs = NormaliseArgs!Args; + + static if (Args.length == 0) + return buffer.ptr[0 .. 0]; else - { - enum n = num_string_args!Args; + return concat_impl(buffer, normalise_args(forward!args)); +} - static if (n == Args.length) +import urt.meta.tuple : Tuple; +char[] concat_impl(Args...)(char[] buffer, Tuple!Args args) +{ + size_t[Args.length] lens = void; + size_t length = 0; + static foreach (i; 0 .. Args.length) + { + static if (is(Args[i] == char)) + length += 1; + else static if (is(Args[i] == Arg)) { - size_t[Args.length] lens = void; - size_t length = 0; - static foreach (i; 0 .. Args.length) - { - static if (is(Args[i] == char)) - length += 1; - else - { - lens[i] = args[i].length; - length += lens[i]; - } - } - if (buffer.ptr) - { - if (length > buffer.length) - return null; - char* p = buffer.ptr; - static foreach (i; 0 .. Args.length) - { - static if (is(Args[i] == char)) - *p++ = args[i]; - else - { - p[0..lens[i]] = args[i].ptr[0..lens[i]]; - p += lens[i]; - } - } - } - return buffer.ptr[0 .. length]; + lens[i] = args[i].fn(null, null, null); + length += lens[i]; } - else static if (Args.length == 1) + else static if (is(Args[i] == Format)) { - ptrdiff_t r = get_to_string_func(args[0])(buffer, null, null); - if (r < 0) - return null; - return buffer.ptr[0..r]; + lens[i] = args[i].fn(null, args[i].fmt, null); + length += lens[i]; } - else static if (n > 2) + else { - char[] r = concat(buffer, args[0 .. n]); - if (buffer.ptr && !r.ptr) - return null; - char[] r2 = concat(buffer.ptr ? buffer.ptr[r.length .. buffer.length] : null, args[n .. $]); - if (buffer.ptr && !r2.ptr) - return null; - return buffer.ptr[0 .. r.length + r2.length]; + lens[i] = args[i].length; + length += lens[i]; } - else + } + if (buffer.ptr) + { + if (length > buffer.length) + return null; + char* p = buffer.ptr; + static foreach (i; 0 .. Args.length) { - // this implementation handles all the other kinds of things! - debug if (!__ctfe) InFormatFunction = true; - StringifyFunc[Args.length] arg_funcs = void; - static foreach(i; 0 .. args.length) - arg_funcs[i] = get_to_string_func(args[i]); - char[] r = concat_impl(buffer, arg_funcs); - debug if (!__ctfe) InFormatFunction = false; - return r; + static if (is(Args[i] == char)) + *p++ = args[i]; + else static if (is(Args[i] == Arg)) + { + args[i].fn(p[0..lens[i]], null, null); + p += lens[i]; + } + else static if (is(Args[i] == Format)) + { + args[i].fn(p[0..lens[i]], args[i].fmt, null); + p += lens[i]; + } + else + { + p[0..lens[i]] = args[i].ptr[0..lens[i]]; + p += lens[i]; + } } } + return buffer.ptr[0 .. length]; } -char[] format(Args...)(char[] buffer, const(char)[] fmt, ref Args args) +char[] format(Args...)(char[] buffer, const(char)[] fmt, auto ref Args args) { debug InFormatFunction = true; FormatArg[Args.length] argArr = void; @@ -172,7 +195,17 @@ template NormaliseArgs(Args...) import urt.meta : AliasSeq; alias NormaliseArgs = AliasSeq!(); static foreach (Arg; Args) - NormaliseArgs = AliasSeq!(NormaliseArgs, NormaliseConst!Arg); + NormaliseArgs = AliasSeq!(NormaliseArgs, NormaliseOthers!(NormaliseConst!Arg)); +} + +template NormaliseOthers(T) +{ + static if (is(T : const(char)[]) || is(T == char)) + alias NormaliseOthers = T; + else static if (is(T == String) || is(T == Array!char) || is(T == MutableString!N, size_t N)) + alias NormaliseOthers = const(char)[]; + else + alias NormaliseOthers = Arg; } template NormaliseConst(T) @@ -182,7 +215,7 @@ template NormaliseConst(T) else static if (is(T == immutable(U)[], U) || is(T == U[], U)) alias NormaliseConst = const(U)[]; else static if (is(T == U[N], U, size_t N)) - alias NormaliseConst = const(U)[N]; + alias NormaliseConst = const(U)[]; else alias NormaliseConst = T; } @@ -249,7 +282,10 @@ StringifyFunc get_to_string_func(T)(ref T value) else static if (is(method_type : StringifyFuncReduced) || is(method_type : StringifyFuncReduced2)) { StringifyFunc d; - d.ptr = &value; + static if (is(T == struct)) + d.ptr = &value; + else + d.ptr = cast(void*)value; static if (is(method_type : StringifyFuncReduced)) d.funcptr = &ToStringShim.shim1!T; else @@ -261,6 +297,34 @@ StringifyFunc get_to_string_func(T)(ref T value) return null; } + else static if (is_unsigned_int!T && T.sizeof <= size_t.sizeof) + { + StringifyFunc fn; + fn.ptr = cast(void*)size_t(value); + fn.funcptr = &DefFormat!size_t.toString; + return fn; + } + else static if (is_signed_int!T && T.sizeof <= size_t.sizeof) + { + StringifyFunc fn; + fn.ptr = cast(void*)cast(size_t)ptrdiff_t(value); + fn.funcptr = &DefFormat!ptrdiff_t.toString; + return fn; + } + else static if (is_some_char!T) + { + StringifyFunc fn; + fn.ptr = cast(void*)cast(size_t)dchar(value); + fn.funcptr = &DefFormat!dchar.toString; + return fn; + } + else static if (T.sizeof <= size_t.sizeof) + { + StringifyFunc fn; + *cast(Unqual!T*)&fn.ptr = value; + fn.funcptr = &DefFormat!T.toString; + return fn; + } else return &(cast(DefFormat!T*)&value).toString; } @@ -269,22 +333,35 @@ struct ToStringShim { ptrdiff_t shim1(T)(char[] buffer, const(char)[] format, const(FormatArg)[]) { - ref T _this = *cast(T*)&this; + static if (is(T == struct)) + ref T _this = *cast(T*)&this; + else + T _this = cast(T)cast(void*)&this; return _this.toString(buffer, format); } ptrdiff_t shim2(T)(char[] buffer, const(char)[], const(FormatArg)[]) { - ref T _this = *cast(T*)&this; + static if (is(T == struct)) + ref T _this = *cast(T*)&this; + else + T _this = cast(T)cast(void*)&this; return _this.toString(buffer); } } struct DefInt(T) { - T value; + static if (T.sizeof > size_t.sizeof) + T value; ptrdiff_t to_int() const pure nothrow @nogc { + static if (T.sizeof <= size_t.sizeof) + { + T value = void; + *cast(size_t*)&value = cast(size_t)cast(void*)&this; + } + static if (T.max > ptrdiff_t.max) debug assert(value <= ptrdiff_t.max); return cast(ptrdiff_t)value; @@ -292,7 +369,7 @@ struct DefInt(T) } ptrdiff_t defToString(T)(ref const(T) value, char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) nothrow @nogc - => (cast(DefFormat!T*)&value).toString(buffer, format, formatArgs); + => get_to_string_func(value)(buffer, format, formatArgs); template DefFormat(T) { @@ -304,10 +381,17 @@ template DefFormat(T) { static assert(!is(T == const), "How did this slip through?"); - const T value; + static if (T.sizeof > size_t.sizeof) + const T value; ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { + static if (T.sizeof <= size_t.sizeof) + { + T value = void; + *cast(size_t*)&value = cast(size_t)cast(void*)&this; + } + static if (is(T == U[N], U, size_t N)) return defToString!(U[])(value[], buffer, format, formatArgs); else static if (is(T : String) || is(T : MutableString!N, size_t N) || is(T : Array!(char, N), size_t N)) @@ -404,76 +488,7 @@ template DefFormat(T) } else static if (is(T == ulong) || is(T == long)) { - import urt.conv : format_int, format_uint; - - // TODO: what formats are interesting for ints? - - bool leadingZeroes = false; - bool to_lower = false; - bool varLen = false; - ptrdiff_t padding = 0; - uint base = 10; - - static if (is(T == long)) - { - bool show_sign = false; - if (format.length && format[0] == '+') - { - show_sign = true; - format.popFront; - } - } - if (format.length && format[0] == '0') - { - leadingZeroes = true; - format.popFront; - } - if (format.length && format[0] == '*') - { - varLen = true; - format.popFront; - } - if (format.length && format[0].is_numeric) - { - bool success; - padding = format.parse_int_fast(success); - if (varLen) - { - if (padding < 0 || !formatArgs[padding].canInt) - return -2; - padding = formatArgs[padding].getInt; - } - } - if (format.length) - { - char b = format[0] | 0x20; - if (b == 'x') - { - base = 16; - to_lower = format[0] == 'x' && buffer.ptr; - } - else if (b == 'b') - base = 2; - else if (b == 'o') - base = 8; - else if (b == 'd') - base = 10; - format.popFront; - } - - static if (is(T == long)) - ptrdiff_t len = format_int(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', show_sign); - else - ptrdiff_t len = format_uint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); - - if (to_lower && len > 0) - { - for (size_t i = 0; i < len; ++i) - if (cast(uint)(buffer.ptr[i] - 'A') < 26) - buffer.ptr[i] |= 0x20; - } - - return len; + return format_int(value, is(T == long), buffer, format, formatArgs); } else static if (is(T == ubyte) || is(T == ushort) || is(T == uint)) { @@ -637,68 +652,44 @@ template DefFormat(T) } else static if (is(T B == enum)) { - // TODO: optimise short enums with a TABLE! + import urt.conv : format_int; + import urt.meta.enuminfo : enum_key_from_value; + + const(char)[] key = enum_key_from_value!T(value); // TODO: should probably return FQN ??? - string key = null; - val: switch (value) + if (key) { - static foreach (i, KeyName; __traits(allMembers, T)) + size_t len = T.stringof.length + 1 + key.length; + if (buffer.ptr) { - static if (!EnumKeyIsDuplicate!(T, i)) - { - case __traits(getMember, T, KeyName): - key = KeyName; - break val; - } - } - default: - if (!buffer.ptr) - return T.stringof.length + 2 + defToString!B(cast(B)value, null, null, null); - - if (buffer.length < T.stringof.length + 2) - return -1; - buffer[0 .. T.stringof.length] = T.stringof; - buffer[T.stringof.length] = '('; - ptrdiff_t len = defToString!B(*cast(B*)&value, buffer[T.stringof.length + 1 .. $], null, null); - if (len < 0) - return len; - len = T.stringof.length + 2 + len; if (buffer.length < len) return -1; - buffer[len - 1] = ')'; - return len; + buffer[0 .. T.stringof.length] = T.stringof; + buffer[T.stringof.length] = '.'; + buffer[T.stringof.length + 1 .. len] = key[]; + } + return len; } - size_t len = T.stringof.length + 1 + key.length; - if (!buffer.ptr) + char[24] val = void; + ptrdiff_t len = format_int(long(value), val[]); + if (len <= 0) return len; - - if (buffer.length < len) + size_t total_len = T.stringof.length + 2 + len; + if (!buffer.ptr) + return total_len; + if (buffer.length < total_len) return -1; buffer[0 .. T.stringof.length] = T.stringof; - buffer[T.stringof.length] = '.'; - buffer[T.stringof.length + 1 .. len] = key[]; - return len; + buffer[T.stringof.length] = '('; + buffer[T.stringof.length + 1 .. total_len - 1] = val[0 .. len]; + buffer[total_len - 1] = ')'; + return total_len; } else static if (is(T == class)) { - // HACK: class toString is not @nogc, so we'll just stringify the pointer for right now... - return defToString(cast(void*)value, buffer, format, formatArgs); -/+ - try - { - const(char)[] t = (cast()value).toString(); - if (!buffer.ptr) - return t.length; - if (buffer.length < t.length) - return -1; - buffer[0 .. t.length] = t[]; - return t.length; - } - catch (Exception) - return -1; -+/ + return value.toString(buffer, format, formatArgs); } else static if (is(T == struct)) { @@ -764,6 +755,78 @@ template DefFormat(T) } } +ptrdiff_t format_int(ulong value, bool signed, char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) +{ + import urt.conv : format_int, format_uint; + + // TODO: what formats are interesting for ints? + + bool show_sign = false; + bool leadingZeroes = false; + bool to_lower = false; + bool varLen = false; + ptrdiff_t padding = 0; + uint base = 10; + + if (signed && format.length && format[0] == '+') + { + show_sign = true; + format.popFront; + } + if (format.length && format[0] == '0') + { + leadingZeroes = true; + format.popFront; + } + if (format.length && format[0] == '*') + { + varLen = true; + format.popFront; + } + if (format.length && format[0].is_numeric) + { + bool success; + padding = format.parse_int_fast(success); + if (varLen) + { + if (padding < 0 || !formatArgs[padding].canInt) + return -2; + padding = formatArgs[padding].getInt; + } + } + if (format.length) + { + char b = format[0] | 0x20; + if (b == 'x') + { + base = 16; + to_lower = format[0] == 'x' && buffer.ptr; + } + else if (b == 'b') + base = 2; + else if (b == 'o') + base = 8; + else if (b == 'd') + base = 10; + format.popFront; + } + + ptrdiff_t len; + if (signed) + len = format_int(cast(ptrdiff_t)value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', show_sign); + else + len = format_uint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); + + if (to_lower && len > 0) + { + for (size_t i = 0; i < len; ++i) + if (cast(uint)(buffer.ptr[i] - 'A') < 26) + buffer.ptr[i] |= 0x20; + } + + return len; +} + char[] concat_impl(char[] buffer, const(StringifyFunc)[] args) nothrow @nogc { size_t len = 0; diff --git a/src/urt/string/string.d b/src/urt/string/string.d index ac1d91c..1f8bf8f 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -769,58 +769,35 @@ nothrow @nogc: ref MutableString!Embed append(Things...)(auto ref Things things) { - insert(length(), forward!things); - return this; + import urt.string.format : normalise_args; + return insert_impl(length(), normalise_args(things)); } ref MutableString!Embed append_format(Things...)(const(char)[] format, auto ref Things args) { - insert_format(length(), format, forward!args); - return this; + return insert_format(length(), format, forward!args); } ref MutableString!Embed concat(Things...)(auto ref Things things) { + import urt.string.format : normalise_args; if (ptr) writeLength(0); - insert(0, forward!things); - return this; + return insert_impl(0, normalise_args(things)); } ref MutableString!Embed format(Args...)(const(char)[] format, auto ref Args args) { if (ptr) writeLength(0); - insert_format(0, format, forward!args); - return this; + return insert_format(0, format, forward!args); } ref MutableString!Embed insert(Things...)(size_t offset, auto ref Things things) { - import urt.string.format : _concat = concat; - import urt.util : max, next_power_of_2; - - char* oldPtr = ptr; - size_t oldLen = length(); - - size_t insertLen = _concat(null, things).length; - size_t newLen = oldLen + insertLen; - if (newLen == oldLen) - return this; - debug assert(newLen <= MaxStringLen, "String too long"); - - size_t oldAlloc = allocated(); - ptr = newLen <= oldAlloc ? oldPtr : allocStringBuffer(max(16, cast(ushort)newLen + 4).next_power_of_2 - 4); - memmove(ptr + offset + insertLen, oldPtr + offset, oldLen - offset); - _concat(ptr[offset .. offset + insertLen], forward!things); - writeLength(newLen); - - if (oldPtr && ptr != oldPtr) - { - ptr[0 .. offset] = oldPtr[0 .. offset]; - freeStringBuffer(oldPtr); - } - return this; + import urt.string.format : normalise_args; + auto args = normalise_args(things); + return insert_impl(offset, args); } ref MutableString!Embed insert_format(Things...)(size_t offset, const(char)[] format, auto ref Things args) @@ -945,6 +922,35 @@ private: defaultAllocator().free(buffer[0 .. 4 + *cast(ushort*)buffer]); } + import urt.meta.tuple : Tuple; + ref MutableString!Embed insert_impl(Things...)(size_t offset, Tuple!Things args) + { + import urt.string.format : concat_impl; + import urt.util : max, next_power_of_2; + + char* oldPtr = ptr; + size_t oldLen = length(); + + size_t insertLen = concat_impl(null, args).length; + size_t newLen = oldLen + insertLen; + if (newLen == oldLen) + return this; + debug assert(newLen <= MaxStringLen, "String too long"); + + size_t oldAlloc = allocated(); + ptr = newLen <= oldAlloc ? oldPtr : allocStringBuffer(max(16, cast(ushort)newLen + 4).next_power_of_2 - 4); + memmove(ptr + offset + insertLen, oldPtr + offset, oldLen - offset); + concat_impl(ptr[offset .. offset + insertLen], args); + writeLength(newLen); + + if (oldPtr && ptr != oldPtr) + { + ptr[0 .. offset] = oldPtr[0 .. offset]; + freeStringBuffer(oldPtr); + } + return this; + } + version (Windows) { auto __debugOverview() const pure => ptr ? ptr[0 .. length].debugExcapeString() : null; From 0816321fde5373c41b359a62704dc43fcfc801fa Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 21 Apr 2026 14:37:29 +1000 Subject: [PATCH 125/138] Fix empty array/map's in json formatter. --- src/urt/format/json.d | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/urt/format/json.d b/src/urt/format/json.d index 5cfd62c..3067f76 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -34,6 +34,8 @@ ptrdiff_t write_json(ref const Variant val, char[] buffer, bool dense = false, u { ptrdiff_t len; size_t itemCount = val.type == Variant.Type.Map ? val.count /2 : val.count; + if (itemCount == 0) + return 2; // "[]" / "{}" if (dense) { // open/close brackets + comma-space separators @@ -68,6 +70,12 @@ ptrdiff_t write_json(ref const Variant val, char[] buffer, bool dense = false, u ptrdiff_t written = 0; if (!buffer.append(written, val.type == Variant.Type.Map ? '{' : '[')) return -1; + if (val.count == 0) + { + if (!buffer.append(written, val.type == Variant.Type.Map ? '}' : ']')) + return -1; + return written; + } int inc = val.type == Variant.Type.Map ? 2 : 1; for (uint i = 0; i < val.count; i += inc) { From cbff1d01fca2fdbc8ee20cfb794d4cb2be035715 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 21 Apr 2026 14:36:48 +1000 Subject: [PATCH 126/138] Add function to get pending bytes from a socket. --- src/urt/internal/os.c | 1 + src/urt/internal/sys/windows/winsock2.d | 2 ++ src/urt/socket.d | 36 +++++++++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/src/urt/internal/os.c b/src/urt/internal/os.c index 02bf6cb..4fbd6fc 100644 --- a/src/urt/internal/os.c +++ b/src/urt/internal/os.c @@ -13,6 +13,7 @@ # include # include # include +# include // EWOULDBLOCK is #define EWOULDBLOCK EAGAIN on Linux — ImportC cannot resolve // chained macros, so re-define as a plain integer. diff --git a/src/urt/internal/sys/windows/winsock2.d b/src/urt/internal/sys/windows/winsock2.d index 9ea6983..5ab3a7c 100644 --- a/src/urt/internal/sys/windows/winsock2.d +++ b/src/urt/internal/sys/windows/winsock2.d @@ -36,7 +36,9 @@ alias LPWSADATA = WSADATA*; enum int IOCPARM_MASK = 0x7F; enum int IOC_IN = cast(int)0x80000000; +enum int IOC_OUT = cast(int)0x40000000; enum int FIONBIO = cast(int)(IOC_IN | ((uint.sizeof & IOCPARM_MASK) << 16) | (102 << 8) | 126); +enum int FIONREAD = cast(int)(IOC_OUT | ((uint.sizeof & IOCPARM_MASK) << 16) | (102 << 8) | 127); enum NI_MAXHOST = 1025; enum NI_MAXSERV = 32; diff --git a/src/urt/socket.d b/src/urt/socket.d index 6e2a218..b627832 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -198,6 +198,7 @@ else version (lwIP) } enum FIONBIO = 0x8004667e; // _IOW('f', 126, unsigned long) + enum FIONREAD = 0x4004667f; // _IOR('f', 127, unsigned long) // Aliases so the rest of the codebase uses POSIX names alias _poll = lwip_poll; @@ -686,6 +687,41 @@ Result sendmsg(Socket socket, const InetAddress* address, MsgFlags flags, const( } } +Result pending(Socket socket, out size_t bytes_available) +{ + version (SocketCallbacks) + static assert(false, "TODO: pending() not implemented for SocketCallbacks"); + else + { + version (Windows) + { + import urt.internal.sys.windows.winsock2 : ioctlsocket, FIONREAD; + uint avail; + if (ioctlsocket(socket.handle, FIONREAD, &avail) != 0) + return socket_getlasterror(); + bytes_available = avail; + } + else version (Posix) + { + import urt.internal.os : ioctl; + int avail; + if (ioctl(socket.handle, 0x541B, &avail) < 0) // FIONREAD + return socket_getlasterror(); + bytes_available = avail; + } + else version (lwIP) + { + uint avail; + if (ioctlsocket(socket.handle, FIONREAD, &avail) != 0) + return socket_getlasterror(); + bytes_available = avail; + } + else + static assert(false, "Platform not supported"); + return Result.success; + } +} + Result recv(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.none, size_t* bytes_received) { version (SocketCallbacks) From 94d9e490c6fdeab671efed1b583e3906bd4b4b76 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 21 Apr 2026 15:07:08 +1000 Subject: [PATCH 127/138] Add socket callback for pending. --- src/urt/socket.d | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/urt/socket.d b/src/urt/socket.d index b627832..3a4d6a5 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -27,6 +27,7 @@ version (SocketCallbacks) SocketResult function(Socket, const(InetAddress)*, MsgFlags, const(void[])[], size_t*) sendmsg; SocketResult function(Socket, void[], MsgFlags, size_t*) recv; SocketResult function(Socket, void[], MsgFlags, InetAddress*, size_t*) recvfrom; + SocketResult function(Socket, out size_t) pending; SocketResult function(PollFd[], Duration, out uint) poll; SocketResult function(Socket, SocketOption, const(void)*, size_t) set_option; SocketResult function(Socket, SocketOption, void*, size_t) get_option; @@ -690,7 +691,7 @@ Result sendmsg(Socket socket, const InetAddress* address, MsgFlags flags, const( Result pending(Socket socket, out size_t bytes_available) { version (SocketCallbacks) - static assert(false, "TODO: pending() not implemented for SocketCallbacks"); + return Result(_socket_backend.pending(socket, bytes_available)); else { version (Windows) From 02358fe7ca48dd11a0bbb7c92b4923827725b7a2 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 23 Apr 2026 01:00:59 +1000 Subject: [PATCH 128/138] Reworked unwind and stack trace logic into drivers. --- src/sys/baremetal/exception.d | 149 + src/sys/bl808/{crash.d => exception.d} | 45 +- src/sys/esp32/exception.d | 85 + src/sys/posix/exception.d | 1077 +++++++ src/sys/windows/exception.d | 1590 ++++++++++ src/urt/internal/dwarfeh.d | 779 +++++ src/urt/internal/exception.d | 3799 +++--------------------- 7 files changed, 4035 insertions(+), 3489 deletions(-) create mode 100644 src/sys/baremetal/exception.d rename src/sys/bl808/{crash.d => exception.d} (68%) create mode 100644 src/sys/esp32/exception.d create mode 100644 src/sys/posix/exception.d create mode 100644 src/sys/windows/exception.d create mode 100644 src/urt/internal/dwarfeh.d diff --git a/src/sys/baremetal/exception.d b/src/sys/baremetal/exception.d new file mode 100644 index 0000000..f3e72a2 --- /dev/null +++ b/src/sys/baremetal/exception.d @@ -0,0 +1,149 @@ +/// Bare-metal exception/crash driver. +/// +/// Provides the stack-trace driver interface for bare-metal targets +/// (BK7231, BL618, BL808, RP2350, STM32, ...). No symbol resolution is +/// available on-device - addresses are printed raw; decode offline with +/// `addr2line -e `. +/// +/// Requires the compiler to keep the frame pointer live +/// (`-frame-pointer=all` on LDC). Without it the fp chain is garbage. +module sys.baremetal.exception; + +version (BareMetal): + +import urt.internal.exception : Resolved; + +nothrow @nogc: + +// Linker symbols bounding the valid stack range. Used by the fp-chain +// walker to stop dereferencing junk. Every bare-metal linker script in +// this tree defines both. +extern(C) extern __gshared { + pragma(mangle, "_stack_top") void* _stack_top_ptr; + pragma(mangle, "_bss_end") void* _bss_end_ptr; +} + + +// --- Shared fp-chain walker ------------------------------------------- +// +// RV64 / AArch64 / ARM-AAPCS all share the same prologue layout when +// -frame-pointer=all is in effect: +// fp[-1] = saved return address +// fp[-2] = saved previous frame pointer +// (indices in machine-word units; RV64 uses fp-8 / fp-16 in bytes). +// +// On 32-bit ARM AAPCS the layout is fp[0] = prev fp, fp[+1] = saved lr +// for Thumb prologues - we don't currently target that combo, so stick +// to the standard layout for now. +// +/// Walk the frame-pointer chain starting at `fp`, writing return addresses +/// into `out_addrs`. Returns the number of frames captured. +size_t walk_fp_chain(size_t fp, void*[] out_addrs) @trusted +{ + if (out_addrs.length == 0) + return 0; + + const stack_hi = cast(size_t) _stack_top_ptr; + const stack_lo = cast(size_t) _bss_end_ptr; + + size_t n = 0; + while (n < out_addrs.length) + { + if (fp < stack_lo || fp >= stack_hi || (fp & (size_t.sizeof - 1)) != 0) + break; + + const ret_addr = *cast(size_t*)(fp - size_t.sizeof); + const prev_fp = *cast(size_t*)(fp - 2 * size_t.sizeof); + + if (ret_addr == 0) + break; + + out_addrs[n++] = cast(void*) ret_addr; + + if (prev_fp == 0 || prev_fp <= fp) + break; + fp = prev_fp; + } + return n; +} + + +// --- Read own frame pointer ------------------------------------------- + +private size_t read_fp() @trusted +{ + size_t fp; + version (RISCV64) + asm nothrow @nogc @trusted { "mv %0, s0" : "=r"(fp); } + else version (RISCV32) + asm nothrow @nogc @trusted { "mv %0, s0" : "=r"(fp); } + else version (AArch64) + asm nothrow @nogc @trusted { "mov %0, x29" : "=r"(fp); } + else version (ARM) + asm nothrow @nogc @trusted { "mov %0, r11" : "=r"(fp); } + else version (Xtensa) + asm nothrow @nogc @trusted { "mov %0, a7" : "=r"(fp); } // Xtensa: a7 with -mno-serialize-volatile + else + fp = 0; // unknown arch - capture returns empty + return fp; +} + + +// --- Driver interface ------------------------------------------------- +// +// All three primitives assume they are called through a one-level public +// wrapper in urt.internal.exception (kept non-inlined via pragma(inline, +// false)). The wrapper's frame is accounted for in the skip counts. + +/// Capture the caller's call stack. First entry = return address of +/// the function that called the public `capture_trace` wrapper. +size_t _capture_trace(void*[] addrs) @trusted +{ + auto fp = read_fp(); + if (fp == 0) + return 0; + // fp is _capture_trace's own frame. Step one up to the wrapper's + // frame, then walk_fp_chain reads the wrapper's saved ra (= return + // address in the caller). That becomes the first captured entry. + const prev_fp = *cast(size_t*)(fp - 2 * size_t.sizeof); + if (prev_fp == 0) + return 0; + return walk_fp_chain(prev_fp, addrs); +} + +/// Return the return address of the `skip`-th frame above the public +/// `caller_address` wrapper's caller. +void* _caller_address(uint skip) @trusted +{ + auto fp = read_fp(); + if (fp == 0) + return null; + + // walk_fp_chain starting from _caller_address's own fp produces: + // buf[0] = PC inside the public wrapper (where _caller_address returns) + // buf[1] = PC inside USER (where wrapper returns) + // buf[2] = PC inside USER's caller ← skip=0 wants this + void*[32] buf = void; + const need = skip + 3; + if (need > buf.length) + return null; + const got = walk_fp_chain(fp, buf[0 .. need]); + if (got < need) + return null; + return buf[need - 1]; +} + +/// On bare-metal we have no on-device symbol table. Always returns +/// false; decode the address offline with `addr2line`. +bool _resolve_address(void* addr, out Resolved r) @trusted +{ + return false; +} + +/// No on-device symbols - caller should treat `results[]` as empty. +bool _resolve_batch(const(void*)[] addrs, Resolved[] results) @trusted +{ + cast(void) addrs; + cast(void) results; + return false; +} diff --git a/src/sys/bl808/crash.d b/src/sys/bl808/exception.d similarity index 68% rename from src/sys/bl808/crash.d rename to src/sys/bl808/exception.d index efa5582..8bfd42e 100644 --- a/src/sys/bl808/crash.d +++ b/src/sys/bl808/exception.d @@ -1,20 +1,15 @@ -/// BL808 crash handler +/// BL808 crash handler. /// /// Called from _trap_exception in start.S. Prints exception info, /// register dump, and stack backtrace to UART0. /// Requires -frame-pointer=all for reliable backtrace. -module sys.bl808.crash; +module sys.bl808.exception; +import sys.baremetal.exception : walk_fp_chain; import sys.bl808.uart; private: -// Linker symbols from the linker script -extern(C) extern __gshared { - pragma(mangle, "_stack_top") void* _stack_top_ptr; - pragma(mangle, "_bss_end") void* _bss_end_ptr; -} - immutable const(char)*[16] exception_names = [ "Instruction address misaligned", "Instruction access fault", @@ -79,42 +74,28 @@ extern(C) void _crash_handler(ulong* regs, ulong mcause, ulong mepc, ulong mtval uart0_putc('\n'); } - // Stack backtrace via frame pointer chain - // RV64 convention: fp-8 = saved ra, fp-16 = saved previous fp + // Stack backtrace via the shared fp-chain walker. uart0_print("\nBacktrace:\n [0] "); uart0_hex(mepc); uart0_print(" (faulting PC)\n"); - ulong fp = regs[7]; // s0 = frame pointer - ulong stack_hi = cast(ulong) _stack_top_ptr; - ulong stack_lo = cast(ulong) _bss_end_ptr; + void*[32] addrs = void; + const n = walk_fp_chain(cast(size_t) regs[7], addrs[]); // s0 = frame pointer - foreach (depth; 1 .. 32) + foreach (depth, addr; addrs[0 .. n]) { - if (fp < stack_lo || fp >= stack_hi || (fp & 0x7) != 0) - break; - - ulong ret_addr = *cast(ulong*)(fp - 8); - ulong prev_fp = *cast(ulong*)(fp - 16); - - if (ret_addr == 0) - break; - uart0_print(" ["); - if (depth < 10) - uart0_putc(cast(char)('0' + depth)); + const d = depth + 1; + if (d < 10) + uart0_putc(cast(char)('0' + d)); else { - uart0_putc(cast(char)('0' + depth / 10)); - uart0_putc(cast(char)('0' + depth % 10)); + uart0_putc(cast(char)('0' + d / 10)); + uart0_putc(cast(char)('0' + d % 10)); } uart0_print("] "); - uart0_hex(ret_addr); + uart0_hex(cast(ulong) addr); uart0_putc('\n'); - - if (prev_fp == 0 || prev_fp <= fp) - break; - fp = prev_fp; } uart0_print("\n*** HALTED ***\n"); diff --git a/src/sys/esp32/exception.d b/src/sys/esp32/exception.d new file mode 100644 index 0000000..7295b4e --- /dev/null +++ b/src/sys/esp32/exception.d @@ -0,0 +1,85 @@ +/// Espressif (ESP-IDF + FreeRTOS) exception driver. +/// +/// Stack-trace capture uses libgcc's `_Unwind_Backtrace` (pulled in +/// by the ESP-IDF toolchain). No on-device symbol resolution - +/// decode addresses offline with `xtensa-esp32-elf-addr2line` / +/// `riscv32-esp-elf-addr2line`. +module sys.esp32.exception; + +version (Espressif): + +import urt.internal.exception : Resolved; + +nothrow @nogc: + + +// --- libgcc unwind bindings ------------------------------------------ + +private alias _Unwind_Trace_Fn = extern(C) int function(void* ctx, void* data) nothrow @nogc; +extern(C) private int _Unwind_Backtrace(_Unwind_Trace_Fn, void*) nothrow @nogc; +extern(C) private size_t _Unwind_GetIP(void*) nothrow @nogc; + + +// --- Driver interface ------------------------------------------------ + +// Capture the caller's call stack. First entry = return address of +// the function that called the public `capture_trace` wrapper. +size_t _capture_trace(void*[] addrs) @trusted +{ + if (addrs.length == 0) + return 0; + + struct State + { + void*[] out_addrs; + size_t count; + ubyte skip; + } + + extern(C) static int callback(void* ctx, void* data) nothrow @nogc + { + auto s = cast(State*) data; + if (s.skip > 0) { --s.skip; return 0; } + if (s.count >= s.out_addrs.length) return 1; + auto ip = _Unwind_GetIP(ctx); + if (!ip) return 1; + s.out_addrs[s.count++] = cast(void*) ip; + return 0; + } + + // Skip 2 frames: _Unwind_Backtrace's direct caller (_capture_trace + // itself) + the public wrapper in urt.internal.exception. + State state = State(addrs, 0, 2); + _Unwind_Backtrace(&callback, &state); + return state.count; +} + +// Return the return address of the `skip`-th frame above the public +// `caller_address` wrapper's caller. +void* _caller_address(uint skip) @trusted +{ + void*[32] buf = void; + const n = _capture_trace(buf[]); + // When _capture_trace is reached via _caller_address, _Unwind already + // skipped 2 (itself + wrapper) - but those skips were sized for the + // direct-call path. From _caller_address the buffer layout is: + // buf[0] = PC inside _caller_address (an extra intermediate) + // buf[1] = PC inside USER + // buf[2] = PC inside USER's caller ← skip=0 wants this + const want = skip + 2; + if (n <= want) + return null; + return buf[want]; +} + +// No on-device symbol table. Decode offline with addr2line. +bool _resolve_address(void* addr, out Resolved r) @trusted +{ + return false; +} + +// No on-device symbols - caller should treat `results[]` as empty. +bool _resolve_batch(const(void*)[], Resolved[]) @trusted +{ + return false; +} diff --git a/src/sys/posix/exception.d b/src/sys/posix/exception.d new file mode 100644 index 0000000..0f87694 --- /dev/null +++ b/src/sys/posix/exception.d @@ -0,0 +1,1077 @@ +/// POSIX exception driver - stack-trace capture/print via ELF + DWARF. +/// +/// Uses _Unwind_Backtrace (ARM/AArch64/RISC-V) or inline-asm RBP walking +/// (x86/x86_64) for capture. Symbol resolution via dladdr; file:line via +/// .debug_line (DWARF v3/4/5). Ported from druntime's +/// core.internal.backtrace.{dwarf,elf} and core.internal.elf.{io,dl}. +/// +/// The DWARF exception-handling runtime (_d_throw_exception, personality +/// function, etc.) lives in urt.internal.dwarfeh so it compiles on bare- +/// metal builds too where sys.posix.exception is not in the source list. +module sys.posix.exception; + +// Windows has its own driver; BareMetal has its own. Everything else +// (Linux, macOS, BSD) lands here. Compiled always (not just `debug`) +// so allocation-site tracking works in release builds. +version (Windows) {} else version (BareMetal) {} else: + +import urt.internal.exception : Resolved, StackTraceData, _d_createTrace, _d_isbaseof, terminate; + +import urt.mem : strlen, memcpy; + +import urt.internal.sys.posix; + +// ╔═══════════════════════════════════════════════════════════════════╗ +// ║ !!! TODO !!! THREADING IS NOT SUPPORTED. ║ +// ║ ║ +// ║ dladdr is MT-safe but the static scratch buffers used by the ║ +// ║ DWARF .debug_line decoder (_dir_scratch, _file_scratch) and the ║ +// ║ ELF self-mapping state are not. Add a mutex or per-thread state ║ +// ║ before this program can use threads. ║ +// ╚═══════════════════════════════════════════════════════════════════╝ + +private enum SEEK_END = 2; + +// --- _Unwind_Backtrace capture (ARM, AArch64, RISC-V) ------------ + +version (D_InlineAsm_X86_64) {} else version (D_InlineAsm_X86) {} else +{ + private alias _Unwind_Trace_Fn = extern(C) int function(void* ctx, void* data) nothrow @nogc; + + extern(C) private int _Unwind_Backtrace(_Unwind_Trace_Fn, void*) nothrow @nogc; + extern(C) private size_t _Unwind_GetIP(void*) nothrow @nogc; + + private struct UnwindState { StackTraceData* trace; ubyte skip; } + + extern(C) private int unwind_trace_callback(void* ctx, void* data) nothrow @nogc + { + auto s = cast(UnwindState*) data; + if (s.skip > 0) { s.skip--; return 0; } + if (s.trace.length >= 32) return 1; + auto ip = _Unwind_GetIP(ctx); + if (!ip) return 1; + s.trace.addrs[s.trace.length++] = cast(void*) ip; + return 0; + } + + private void unwind_backtrace(ref StackTraceData trace) nothrow @nogc @trusted + { + UnwindState state = UnwindState(&trace, 2); + _Unwind_Backtrace(&unwind_trace_callback, &state); + } +} + +// --- Minimal ELF types -------------------------------------------- + +version (D_LP64) +{ + private struct Elf_Ehdr + { + ubyte[16] e_ident; + ushort e_type; + ushort e_machine; + uint e_version; + ulong e_entry; + ulong e_phoff; + ulong e_shoff; + uint e_flags; + ushort e_ehsize; + ushort e_phentsize; + ushort e_phnum; + ushort e_shentsize; + ushort e_shnum; + ushort e_shstrndx; + } + + private struct Elf_Shdr + { + uint sh_name; + uint sh_type; + ulong sh_flags; + ulong sh_addr; + ulong sh_offset; + ulong sh_size; + uint sh_link; + uint sh_info; + ulong sh_addralign; + ulong sh_entsize; + } + + private struct Elf_Phdr + { + uint p_type; + uint p_flags; + ulong p_offset; + ulong p_vaddr; + ulong p_paddr; + ulong p_filesz; + ulong p_memsz; + ulong p_align; + } +} +else +{ + private struct Elf_Ehdr + { + ubyte[16] e_ident; + ushort e_type; + ushort e_machine; + uint e_version; + uint e_entry; + uint e_phoff; + uint e_shoff; + uint e_flags; + ushort e_ehsize; + ushort e_phentsize; + ushort e_phnum; + ushort e_shentsize; + ushort e_shnum; + ushort e_shstrndx; + } + + private struct Elf_Shdr + { + uint sh_name; + uint sh_type; + uint sh_flags; + uint sh_addr; + uint sh_offset; + uint sh_size; + uint sh_link; + uint sh_info; + uint sh_addralign; + uint sh_entsize; + } + + private struct Elf_Phdr + { + uint p_type; + uint p_offset; + uint p_vaddr; + uint p_paddr; + uint p_filesz; + uint p_memsz; + uint p_flags; + uint p_align; + } +} + +private enum EI_MAG0 = 0; +private enum EI_CLASS = 4; +private enum EI_DATA = 5; +private enum ELFMAG = "\x7fELF"; +private enum ET_DYN = 3; +private enum SHF_COMPRESSED = 0x800; + +version (D_LP64) + private enum ELFCLASS_NATIVE = 2; // ELFCLASS64 +else + private enum ELFCLASS_NATIVE = 1; // ELFCLASS32 + +version (LittleEndian) + private enum ELFDATA_NATIVE = 1; // ELFDATA2LSB +else + private enum ELFDATA_NATIVE = 2; // ELFDATA2MSB + +// --- dl_iterate_phdr for base address ----------------------------- + +private struct dl_phdr_info +{ + size_t dlpi_addr; + const(char)* dlpi_name; + const(Elf_Phdr)* dlpi_phdr; + ushort dlpi_phnum; +} + +private alias dl_iterate_phdr_callback_t = extern(C) int function(dl_phdr_info*, size_t, void*) nothrow @nogc; + +extern(C) private int dl_iterate_phdr(dl_iterate_phdr_callback_t callback, void* data) nothrow @nogc; + +private size_t get_executable_base_address() nothrow @nogc @trusted +{ + size_t result = 0; + + extern(C) static int callback(dl_phdr_info* info, size_t, void* data) nothrow @nogc + { + // First entry is the executable itself + *cast(size_t*) data = info.dlpi_addr; + return 1; // stop iteration + } + + dl_iterate_phdr(&callback, &result); + return result; +} + +// --- Memory-mapped file region ------------------------------------ + +private struct MappedRegion +{ + const(ubyte)* data; + size_t mapped_size; + +nothrow @nogc @trusted: + + static MappedRegion map(int fd, size_t offset, size_t length) + { + if (fd == -1 || length == 0) + return MappedRegion.init; + + auto pgsz = cast(size_t) sysconf(_SC_PAGE_SIZE); + if (cast(int) pgsz <= 0) + pgsz = 4096; + + const page_off = offset / pgsz; + const diff = offset - page_off * pgsz; + const needed = length + diff; + const pages = (needed + pgsz - 1) / pgsz; + const msize = pages * pgsz; + + auto p = mmap(null, msize, PROT_READ, MAP_PRIVATE, fd, cast(off_t)(page_off * pgsz)); + if (p is MAP_FAILED) + return MappedRegion.init; + + return MappedRegion(cast(const(ubyte)*) p + diff, msize); + } + + void unmap() + { + if (data !is null) + { + auto pgsz = cast(size_t) sysconf(_SC_PAGE_SIZE); + if (cast(int) pgsz <= 0) + pgsz = 4096; + // Align back to page boundary + auto base = cast(void*)(cast(size_t) data & ~(pgsz - 1)); + munmap(base, mapped_size); + data = null; + } + } +} + +// --- ELF self-reader ---------------------------------------------- + +private struct ElfSelf +{ + int fd = -1; + MappedRegion ehdr_region; + const(Elf_Ehdr)* ehdr; + +nothrow @nogc @trusted: + + static ElfSelf open() + { + ElfSelf self; + + // Read /proc/self/exe path + char[512] pathbuf = void; + auto n = readlink("/proc/self/exe", pathbuf.ptr, pathbuf.length - 1); + if (n <= 0) + return self; + pathbuf[n] = 0; + + self.fd = .open(pathbuf.ptr, O_RDONLY); + if (self.fd == -1) + return self; + + // Map the ELF header + self.ehdr_region = MappedRegion.map(self.fd, 0, Elf_Ehdr.sizeof); + if (self.ehdr_region.data is null) + { + .close(self.fd); + self.fd = -1; + return self; + } + + self.ehdr = cast(const(Elf_Ehdr)*) self.ehdr_region.data; + + // Validate ELF magic, class, and byte order + if (self.ehdr.e_ident[0..4] != cast(const(ubyte)[4]) ELFMAG + || self.ehdr.e_ident[EI_CLASS] != ELFCLASS_NATIVE + || self.ehdr.e_ident[EI_DATA] != ELFDATA_NATIVE) + { + self.close(); + return ElfSelf.init; + } + + return self; + } + + void close() + { + ehdr_region.unmap(); + ehdr = null; + if (fd != -1) { .close(fd); fd = -1; } + } + + bool valid() const { return fd != -1 && ehdr !is null; } + + /// Find a section by name, return its offset and size. + bool find_section(const(char)[] name, out size_t offset, out size_t size) + { + if (!valid()) + return false; + + // Map section headers + auto shdr_total = cast(size_t) ehdr.e_shnum * Elf_Shdr.sizeof; + auto shdr_region = MappedRegion.map(fd, cast(size_t) ehdr.e_shoff, shdr_total); + if (shdr_region.data is null) + return false; + scope(exit) shdr_region.unmap(); + + auto shdrs = (cast(const(Elf_Shdr)*) shdr_region.data)[0 .. ehdr.e_shnum]; + + // Map string table section + if (ehdr.e_shstrndx >= ehdr.e_shnum) + return false; + auto strtab_shdr = &shdrs[ehdr.e_shstrndx]; + auto strtab_region = MappedRegion.map(fd, + cast(size_t) strtab_shdr.sh_offset, + cast(size_t) strtab_shdr.sh_size); + if (strtab_region.data is null) + return false; + scope(exit) strtab_region.unmap(); + + auto strtab = cast(const(char)*) strtab_region.data; + + // Search for the named section + foreach (ref shdr; shdrs) + { + if (shdr.sh_name >= strtab_shdr.sh_size) + continue; + auto sec_name = strtab + shdr.sh_name; + auto sec_name_len = strlen(sec_name); + if (sec_name_len == name.length && sec_name[0 .. sec_name_len] == name) + { + if (shdr.sh_flags & SHF_COMPRESSED) + return false; // compressed debug sections not supported + offset = cast(size_t) shdr.sh_offset; + size = cast(size_t) shdr.sh_size; + return true; + } + } + return false; + } +} + +// --- DWARF .debug_line types and constants ------------------------ + +private struct LocationInfo +{ + int file = -1; + int line = -1; +} + +private struct SourceFile +{ + const(char)[] file; + size_t dir_index; // 1-based +} + +private struct LineNumberProgram +{ + ulong unit_length; + ushort dwarf_version; + ubyte address_size; + ubyte segment_selector_size; + ulong header_length; + ubyte minimum_instruction_length; + ubyte maximum_operations_per_instruction; + bool default_is_statement; + byte line_base; + ubyte line_range; + ubyte opcode_base; + const(ubyte)[] standard_opcode_lengths; + // Directory and file tables stored as slices into scratch buffers. + // The caller owns the scratch; the LineNumberProgram just borrows. + const(char)[][] include_directories; + size_t num_dirs; + SourceFile[] source_files; + size_t num_files; + const(ubyte)[] program; +} + +private struct StateMachine +{ + const(void)* address; + uint operation_index = 0; + uint file_index = 1; + int line = 1; + uint column = 0; + bool is_statement; + bool is_end_sequence = false; +} + +private enum StandardOpcode : ubyte +{ + extended_op = 0, + copy = 1, + advance_pc = 2, + advance_line = 3, + set_file = 4, + set_column = 5, + negate_statement = 6, + set_basic_block = 7, + const_add_pc = 8, + fixed_advance_pc = 9, + set_prologue_end = 10, + set_epilogue_begin = 11, + set_isa = 12, +} + +private enum ExtendedOpcode : ubyte +{ + end_sequence = 1, + set_address = 2, + define_file = 3, + set_discriminator = 4, +} + +// --- LEB128 and DWARF helpers ------------------------------------- + +private T dw_read(T)(ref const(ubyte)[] buf) nothrow @nogc @trusted +{ + if (buf.length < T.sizeof) + return T.init; + version (X86_64) + T result = *cast(const(T)*) buf.ptr; + else version (X86) + T result = *cast(const(T)*) buf.ptr; + else + { + T result = void; + memcpy(&result, buf.ptr, T.sizeof); + } + buf = buf[T.sizeof .. $]; + return result; +} + +private const(char)[] dw_read_stringz(ref const(ubyte)[] buf) nothrow @nogc @trusted +{ + auto p = cast(const(char)*) buf.ptr; + auto len = strlen(p); + buf = buf[len + 1 .. $]; + return p[0 .. len]; +} + +private ulong dw_read_uleb128(ref const(ubyte)[] buf) nothrow @nogc +{ + ulong val = 0; + uint shift = 0; + while (buf.length > 0) + { + ubyte b = buf[0]; buf = buf[1 .. $]; + val |= cast(ulong)(b & 0x7f) << shift; + if ((b & 0x80) == 0) break; + shift += 7; + } + return val; +} + +private long dw_read_sleb128(ref const(ubyte)[] buf) nothrow @nogc +{ + long val = 0; + uint shift = 0; + ubyte b; + while (buf.length > 0) + { + b = buf[0]; buf = buf[1 .. $]; + val |= cast(long)(b & 0x7f) << shift; + shift += 7; + if ((b & 0x80) == 0) break; + } + if (shift < 64 && (b & 0x40) != 0) + val |= -(cast(long) 1 << shift); + return val; +} + +// --- DWARF v5 entry format ---------------------------------------- + +private enum DW_LNCT : ushort +{ + path = 1, + directory_index = 2, +} + +private enum DW_FORM : ubyte +{ + data1 = 11, + data2 = 5, + data4 = 6, + data8 = 7, + data16 = 30, + string_ = 8, + strp = 14, + line_strp = 31, + udata = 15, + block = 9, + strx = 26, + strx1 = 37, + strx2 = 38, + strx3 = 39, + strx4 = 40, + sec_offset = 23, + sdata = 13, + flag = 12, + flag_present = 25, +} + +private struct EntryFormatPair +{ + DW_LNCT type; + DW_FORM form; +} + +/// Skip a DWARF form value we don't care about. +private void dw_skip_form(ref const(ubyte)[] data, DW_FORM form, bool is64bit) nothrow @nogc +{ + with (DW_FORM) switch (form) + { + case strp, line_strp, sec_offset: + data = data[is64bit ? 8 : 4 .. $]; break; + case data1, strx1, flag, flag_present: + data = data[1 .. $]; break; + case data2, strx2: + data = data[2 .. $]; break; + case strx3: + data = data[3 .. $]; break; + case data4, strx4: + data = data[4 .. $]; break; + case data8: + data = data[8 .. $]; break; + case data16: + data = data[16 .. $]; break; + case udata, strx, sdata: + dw_read_uleb128(data); break; + case block: + auto length = cast(size_t) dw_read_uleb128(data); + data = data[length .. $]; break; + default: + break; + } +} + +// --- Read DWARF line number program header ------------------------ + +// Scratch buffers allocated on the stack for directory/file tables. +// 256 entries each should be more than enough for any compilation unit. +private enum MAX_DIRS = 256; +private enum MAX_FILES = 512; + +private LineNumberProgram dw_read_line_number_program(ref const(ubyte)[] data) nothrow @nogc @trusted +{ + const original_data = data; + LineNumberProgram lp; + + bool is_64bit_dwarf = false; + lp.unit_length = dw_read!uint(data); + if (lp.unit_length == uint.max) + { + is_64bit_dwarf = true; + lp.unit_length = dw_read!ulong(data); + } + + const version_field_offset = cast(size_t)(data.ptr - original_data.ptr); + lp.dwarf_version = dw_read!ushort(data); + + if (lp.dwarf_version >= 5) + { + lp.address_size = dw_read!ubyte(data); + lp.segment_selector_size = dw_read!ubyte(data); + } + + lp.header_length = is_64bit_dwarf ? dw_read!ulong(data) : dw_read!uint(data); + + const min_insn_field_offset = cast(size_t)(data.ptr - original_data.ptr); + lp.minimum_instruction_length = dw_read!ubyte(data); + lp.maximum_operations_per_instruction = (lp.dwarf_version >= 4) ? dw_read!ubyte(data) : 1; + lp.default_is_statement = (dw_read!ubyte(data) != 0); + lp.line_base = dw_read!byte(data); + lp.line_range = dw_read!ubyte(data); + lp.opcode_base = dw_read!ubyte(data); + + lp.standard_opcode_lengths = data[0 .. lp.opcode_base - 1]; + data = data[lp.opcode_base - 1 .. $]; + + if (lp.dwarf_version >= 5) + { + // DWARF v5: directory format + entries + auto num_pairs = dw_read!ubyte(data); + EntryFormatPair[8] dir_fmt = void; + foreach (i; 0 .. num_pairs) + { + if (i < 8) + { + dir_fmt[i].type = cast(DW_LNCT) dw_read_uleb128(data); + dir_fmt[i].form = cast(DW_FORM) dw_read_uleb128(data); + } + } + + lp.num_dirs = cast(size_t) dw_read_uleb128(data); + // Caller must provide scratch buffers; we use __gshared static for simplicity. + foreach (d; 0 .. lp.num_dirs) + { + foreach (p; 0 .. num_pairs) + { + if (p < 8 && dir_fmt[p].type == DW_LNCT.path && dir_fmt[p].form == DW_FORM.string_) + { + if (d < MAX_DIRS) + _dir_scratch[d] = dw_read_stringz(data); + else + dw_read_stringz(data); + } + else if (p < 8) + dw_skip_form(data, dir_fmt[p].form, is_64bit_dwarf); + } + } + if (lp.num_dirs > MAX_DIRS) lp.num_dirs = MAX_DIRS; + lp.include_directories = _dir_scratch[0 .. lp.num_dirs]; + + // File format + entries + num_pairs = dw_read!ubyte(data); + EntryFormatPair[8] file_fmt = void; + foreach (i; 0 .. num_pairs) + { + if (i < 8) + { + file_fmt[i].type = cast(DW_LNCT) dw_read_uleb128(data); + file_fmt[i].form = cast(DW_FORM) dw_read_uleb128(data); + } + } + + lp.num_files = cast(size_t) dw_read_uleb128(data); + foreach (f; 0 .. lp.num_files) + { + SourceFile sf; + sf.file = ""; + foreach (p; 0 .. num_pairs) + { + if (p < 8 && file_fmt[p].type == DW_LNCT.path && file_fmt[p].form == DW_FORM.string_) + sf.file = dw_read_stringz(data); + else if (p < 8 && file_fmt[p].type == DW_LNCT.directory_index) + { + if (file_fmt[p].form == DW_FORM.data1) + sf.dir_index = dw_read!ubyte(data); + else if (file_fmt[p].form == DW_FORM.data2) + sf.dir_index = dw_read!ushort(data); + else if (file_fmt[p].form == DW_FORM.udata) + sf.dir_index = cast(size_t) dw_read_uleb128(data); + else + dw_skip_form(data, file_fmt[p].form, is_64bit_dwarf); + sf.dir_index++; // DWARF v5 indices are 0-based, normalize to 1-based + } + else if (p < 8) + dw_skip_form(data, file_fmt[p].form, is_64bit_dwarf); + } + if (f < MAX_FILES) + _file_scratch[f] = sf; + } + if (lp.num_files > MAX_FILES) lp.num_files = MAX_FILES; + lp.source_files = _file_scratch[0 .. lp.num_files]; + } + else + { + // DWARF v3/v4: NUL-terminated sequences + lp.num_dirs = 0; + while (data.length > 0 && data[0] != 0) + { + auto dir = dw_read_stringz(data); + if (lp.num_dirs < MAX_DIRS) + _dir_scratch[lp.num_dirs++] = dir; + } + if (data.length > 0) data = data[1 .. $]; // skip NUL terminator + lp.include_directories = _dir_scratch[0 .. lp.num_dirs]; + + lp.num_files = 0; + while (data.length > 0 && data[0] != 0) + { + SourceFile sf; + sf.file = dw_read_stringz(data); + sf.dir_index = cast(size_t) dw_read_uleb128(data); + dw_read_uleb128(data); // last modification time + dw_read_uleb128(data); // file length + if (lp.num_files < MAX_FILES) + _file_scratch[lp.num_files++] = sf; + } + if (data.length > 0) data = data[1 .. $]; // skip NUL terminator + lp.source_files = _file_scratch[0 .. lp.num_files]; + } + + const program_start = cast(size_t)(min_insn_field_offset + lp.header_length); + const program_end = cast(size_t)(version_field_offset + lp.unit_length); + if (program_start <= original_data.length && program_end <= original_data.length) + lp.program = original_data[program_start .. program_end]; + + data = (program_end <= original_data.length) ? original_data[program_end .. $] : null; + + return lp; +} + +// Static scratch buffers for DWARF parsing (debug-only, no allocator needed). +private __gshared const(char)[][MAX_DIRS] _dir_scratch; +private __gshared SourceFile[MAX_FILES] _file_scratch; + +// --- DWARF state machine - resolve addresses to file:line --------- + +private struct ResolvedLocation +{ + const(char)[] file; + const(char)[] dir; + int line = -1; +} + +/// Resolve an array of addresses to file:line using .debug_line data. +private void dw_resolve_addresses( + const(ubyte)[] debug_line_data, + const(void*)[] addresses, + ResolvedLocation[] results, + size_t base_address) nothrow @nogc @trusted +{ + size_t found = 0; + const num_addrs = addresses.length; + + while (debug_line_data.length > 0 && found < num_addrs) + { + auto lp = dw_read_line_number_program(debug_line_data); + if (lp.program.length == 0) + break; + + StateMachine machine; + machine.is_statement = lp.default_is_statement; + + LocationInfo last_loc = LocationInfo(-1, -1); + const(void)* last_address; + + const(ubyte)[] prog = lp.program; + while (prog.length > 0) + { + size_t advance_addr(size_t op_advance) + { + const inc = lp.minimum_instruction_length * + ((machine.operation_index + op_advance) / lp.maximum_operations_per_instruction); + machine.address += inc; + machine.operation_index = + (machine.operation_index + op_advance) % lp.maximum_operations_per_instruction; + return inc; + } + + void emit_row(bool is_end) + { + auto addr = machine.address + base_address; + + foreach (idx; 0 .. num_addrs) + { + if (results[idx].line != -1) + continue; + auto target = addresses[idx]; + + void apply_loc(LocationInfo loc) + { + auto file_idx = loc.file - (lp.dwarf_version < 5 ? 1 : 0); + if (file_idx >= 0 && file_idx < lp.num_files) + { + results[idx].file = lp.source_files[file_idx].file; + auto di = lp.source_files[file_idx].dir_index; + if (di > 0 && di <= lp.num_dirs) + results[idx].dir = lp.include_directories[di - 1]; + } + results[idx].line = loc.line; + found++; + } + + if (target == addr) + apply_loc(LocationInfo(machine.file_index, machine.line)); + else if (last_address !is null && target > last_address && target < addr) + apply_loc(last_loc); + } + + if (is_end) + last_address = null; + else + { + last_address = addr; + last_loc = LocationInfo(machine.file_index, machine.line); + } + } + + ubyte opcode = prog[0]; prog = prog[1 .. $]; + + if (opcode >= lp.opcode_base) + { + // Special opcode + opcode -= lp.opcode_base; + advance_addr(opcode / lp.line_range); + machine.line += lp.line_base + (opcode % lp.line_range); + emit_row(false); + } + else if (opcode == 0) + { + // Extended opcode + auto len = cast(size_t) dw_read_uleb128(prog); + if (prog.length == 0) break; + ubyte eopcode = prog[0]; prog = prog[1 .. $]; + + switch (eopcode) + { + case ExtendedOpcode.end_sequence: + machine.is_end_sequence = true; + emit_row(true); + machine = StateMachine.init; + machine.is_statement = lp.default_is_statement; + break; + case ExtendedOpcode.set_address: + machine.address = dw_read!(const(void)*)(prog); + machine.operation_index = 0; + break; + case ExtendedOpcode.set_discriminator: + dw_read_uleb128(prog); + break; + default: + if (len > 1) + prog = prog[len - 1 .. $]; + break; + } + } + else switch (opcode) with (StandardOpcode) + { + case copy: + emit_row(false); + break; + case advance_pc: + advance_addr(cast(size_t) dw_read_uleb128(prog)); + break; + case advance_line: + machine.line += cast(int) dw_read_sleb128(prog); + break; + case set_file: + machine.file_index = cast(uint) dw_read_uleb128(prog); + break; + case set_column: + machine.column = cast(uint) dw_read_uleb128(prog); + break; + case negate_statement: + machine.is_statement = !machine.is_statement; + break; + case set_basic_block: + break; + case const_add_pc: + advance_addr((255 - lp.opcode_base) / lp.line_range); + break; + case fixed_advance_pc: + machine.address += dw_read!ushort(prog); + machine.operation_index = 0; + break; + case set_prologue_end: + case set_epilogue_begin: + break; + case set_isa: + dw_read_uleb128(prog); + break; + default: + // Unknown standard opcode: skip according to standard_opcode_lengths + if (opcode > 0 && opcode <= lp.standard_opcode_lengths.length) + { + foreach (_; 0 .. lp.standard_opcode_lengths[opcode - 1]) + dw_read_uleb128(prog); + } + break; + } + } + } +} + + + + +// --- Driver interface -------------------------------------------------- +// +// All three primitives assume they are called through a one-level public +// wrapper in urt.internal.exception (kept non-inlined via pragma(inline, +// false)). The wrapper's frame is accounted for in the skip counts +// below. Direct callers (like _d_createTrace) get the same semantics +// because their frame substitutes for the wrapper's. + +/// Capture the caller's call stack. First entry = return address of +/// the function that called the public `capture_trace` wrapper. +size_t _capture_trace(void*[] addrs) nothrow @nogc @trusted +{ + if (addrs.length == 0) + return 0; + + version (D_InlineAsm_X86_64) + { + size_t bp; + asm nothrow @nogc { mov bp, RBP; } + // Walk from _capture_trace's own frame up, storing caller frames. + size_t n = 0; + // Skip one: our own frame so first captured is wrapper/caller. + if (bp) + { + auto next = *cast(size_t*) bp; + if (next > bp) bp = next; + else bp = 0; + } + while (n < addrs.length) + { + if (!bp) break; + auto next_bp = *cast(size_t*) bp; + if (!next_bp || next_bp <= bp) break; + auto retaddr = *cast(void**)(bp + size_t.sizeof); + if (!retaddr) break; + addrs[n++] = retaddr; + bp = next_bp; + } + return n; + } + else version (D_InlineAsm_X86) + { + size_t bp; + asm nothrow @nogc { mov bp, EBP; } + size_t n = 0; + if (bp) + { + auto next = *cast(size_t*) bp; + if (next > bp) bp = next; + else bp = 0; + } + while (n < addrs.length) + { + if (!bp) break; + auto next_bp = *cast(size_t*) bp; + if (!next_bp || next_bp <= bp) break; + auto retaddr = *cast(void**)(bp + size_t.sizeof); + if (!retaddr) break; + addrs[n++] = retaddr; + bp = next_bp; + } + return n; + } + else + { + StackTraceData tmp; + unwind_backtrace(tmp); // skips its own frames internally + auto n = tmp.length < addrs.length ? tmp.length : addrs.length; + addrs[0 .. n] = tmp.addrs[0 .. n]; + return n; + } +} + +/// Return the return address of the `skip`-th frame above the public +/// `caller_address` wrapper's caller. +void* _caller_address(uint skip) nothrow @nogc @trusted +{ + void*[32] buf = void; + const n = _capture_trace(buf[]); + // _capture_trace, when invoked from _caller_address, lays out: + // buf[0] = PC inside the public wrapper + // buf[1] = PC inside USER (the caller_address caller) + // buf[2] = PC inside USER's caller ← skip=0 wants this + const want = skip + 2; + if (n <= want) + return null; + return buf[want]; +} + +/// Resolve via dladdr (symbol name + offset). File/line is omitted - +/// per-address DWARF scans would re-parse .debug_line on every call. +/// For full file:line info, use `_resolve_batch`, which amortises one +/// scan across all frames. +/// Returned `name` slice is owned by dladdr-internal storage - consume +/// before the next call. +bool _resolve_address(void* addr, out Resolved r) nothrow @nogc @trusted +{ + Dl_info info = void; + if (!dladdr(addr, &info) || info.dli_sname is null) + return false; + r.name = info.dli_sname[0 .. strlen(info.dli_sname)]; + r.offset = cast(size_t) addr - cast(size_t) info.dli_saddr; + return true; +} + +// Persistent .debug_line mapping. The first _resolve_batch call opens +// /proc/self/exe, finds .debug_line, mmap()s it, and closes the fd. +// We never unmap - the slice handed back to callers via Resolved.file +// and Resolved.dir points into this region, and the API contract +// promises stability until the next _resolve_batch call. Holding the +// mapping for process lifetime is the cheapest way to satisfy that +// (a few MB of read-only file pages the OS can demand-page). +private __gshared bool _elf_init_attempted; +private __gshared const(ubyte)[] _persistent_debug_line; +private __gshared size_t _persistent_base_addr; + +private void ensure_debug_line_mapped() nothrow @nogc @trusted +{ + if (_elf_init_attempted) + return; + _elf_init_attempted = true; + + auto elf = ElfSelf.open(); + scope(exit) elf.close(); + if (!elf.valid()) + return; + + size_t dbg_offset, dbg_size; + if (!elf.find_section(".debug_line", dbg_offset, dbg_size)) + return; + + auto region = MappedRegion.map(elf.fd, dbg_offset, dbg_size); + if (region.data is null) + return; + // Intentionally not unmapped - kept for process lifetime. + + _persistent_debug_line = region.data[0 .. dbg_size]; + _persistent_base_addr = (elf.ehdr.e_type == ET_DYN) + ? get_executable_base_address() : cast(size_t) 0; +} + +/// Resolve many addresses in one pass: dladdr per address for symbol +/// name + offset, then a single DWARF .debug_line scan to fill file / +/// dir / line for all of them at once. +/// +/// String slices point into dladdr-internal storage, the static DWARF +/// scratch buffers (`_dir_scratch`, `_file_scratch`), or the persistent +/// .debug_line mapping. All three remain valid until the next +/// `_resolve_batch` call overwrites the scratch buffers; copy if you +/// need fields to outlive that. +bool _resolve_batch(const(void*)[] addrs, Resolved[] results) nothrow @nogc @trusted +{ + // Symbol + offset via dladdr (per-address; dladdr is O(1)-ish via + // the dynamic linker's hash tables). + bool any_resolved = false; + foreach (i, a; addrs) + { + Dl_info info = void; + if (dladdr(cast(void*) a, &info) && info.dli_sname !is null) + { + results[i].name = info.dli_sname[0 .. strlen(info.dli_sname)]; + results[i].offset = cast(size_t) a - cast(size_t) info.dli_saddr; + any_resolved = true; + } + } + + // File / line via one DWARF .debug_line scan covering all addrs. + // Failures here still leave dladdr-populated name/offset intact. + ensure_debug_line_mapped(); + if (_persistent_debug_line.length == 0) + return any_resolved; + + ResolvedLocation[32] locations; + const n = addrs.length > locations.length ? locations.length : addrs.length; + + dw_resolve_addresses( + _persistent_debug_line, + addrs[0 .. n], + locations[0 .. n], + _persistent_base_addr); + + foreach (i; 0 .. n) + { + if (locations[i].line >= 0) + { + results[i].file = locations[i].file; + results[i].dir = locations[i].dir; + results[i].line = cast(uint) locations[i].line; + any_resolved = true; + } + } + return any_resolved; +} diff --git a/src/sys/windows/exception.d b/src/sys/windows/exception.d new file mode 100644 index 0000000..75d62a8 --- /dev/null +++ b/src/sys/windows/exception.d @@ -0,0 +1,1590 @@ +/// Windows exception driver. +/// +/// Three compiler/arch flavours of EH runtime coexist, selected by inner +/// version blocks: +/// version (LDC) - MSVC C++ SEH (RaiseException + table-based unwind) +/// version (Win32) - DMD SEH (_d_framehandler chain walk) +/// version (Win64) - DMD DEH (RBP-chain + ._deh section tables) +/// +/// Stack-trace capture / symbol resolution uses DbgHelp (debug-only). +/// Ported from druntime. +module sys.windows.exception; + +version (Windows): + +import urt.internal.exception : ClassInfo, Resolved, _d_isbaseof, _d_createTrace, terminate; + +nothrow @nogc: + + +// ══════════════════════════════════════════════════════════════════════ +// Stack trace support (DbgHelp, debug only) +// ══════════════════════════════════════════════════════════════════════ +// +// ╔═══════════════════════════════════════════════════════════════════╗ +// ║ !!! TODO !!! THREADING IS NOT SUPPORTED. ║ +// ║ ║ +// ║ DbgHelp is NOT thread-safe. All calls to SymFromAddr, ║ +// ║ SymGetLineFromAddr64, SymInitialize, and StackWalk64 must be ║ +// ║ serialized with a CRITICAL_SECTION (or equivalent) before this ║ +// ║ program can use threads. The static scratch buffer inside ║ +// ║ _resolve_address is also shared across callers - replace with ║ +// ║ per-thread storage or a mutex when threading lands. ║ +// ╚═══════════════════════════════════════════════════════════════════╝ + +// --- DbgHelp types --------------------------------------------------- + +private struct SYMBOL_INFOA +{ + uint SizeOfStruct; + uint TypeIndex; + ulong[2] Reserved; + uint Index; + uint Size; + ulong ModBase; + uint Flags; + ulong Value; + ulong Address; + uint Register; + uint Scope; + uint Tag; + uint NameLen; + uint MaxNameLen; + char[1] Name; // variable-length +} + +private struct IMAGEHLP_LINEA64 +{ + uint SizeOfStruct; + void* Key; + uint LineNumber; + const(char)* FileName; + ulong Address; +} + +// --- StackWalk64 types ----------------------------------------------- + +private alias HANDLE = void*; +private enum AddrModeFlat = 3; + +private struct ADDRESS64 +{ + ulong Offset; + ushort Segment; + uint Mode; +} + +private struct STACKFRAME64 +{ + ADDRESS64 AddrPC; + ADDRESS64 AddrReturn; + ADDRESS64 AddrFrame; + ADDRESS64 AddrStack; + ADDRESS64 AddrBStore; + void* FuncTableEntry; + ulong[4] Params; + int Far; + int Virtual; + ulong[3] Reserved; + ubyte[96] KdHelp; // KDHELP64, opaque +} + +// CONTEXT - opaque aligned buffer, accessed via offset constants. +version (Win64) +{ + private enum CONTEXT_SIZE = 1232; + private enum CTX_FLAGS_OFF = 48; + private enum CTX_IP_OFF = 248; // Rip + private enum CTX_SP_OFF = 152; // Rsp + private enum CTX_FP_OFF = 160; // Rbp + private enum CTX_FULL = 0x10000B; + private enum MACHINE_TYPE = 0x8664; // IMAGE_FILE_MACHINE_AMD64 +} +else +{ + private enum CONTEXT_SIZE = 716; + private enum CTX_FLAGS_OFF = 0; + private enum CTX_IP_OFF = 184; // Eip + private enum CTX_SP_OFF = 196; // Esp + private enum CTX_FP_OFF = 180; // Ebp + private enum CTX_FULL = 0x10007; + private enum MACHINE_TYPE = 0x014C; // IMAGE_FILE_MACHINE_I386 +} + +// --- Function pointer types ------------------------------------------ + +extern(Windows) nothrow @nogc +{ + private alias SymInitializeFn = int function(HANDLE, const(char)*, int); + private alias SymSetOptionsFn = uint function(uint); + private alias SymFromAddrFn = int function(HANDLE, ulong, ulong*, SYMBOL_INFOA*); + private alias SymGetLineFromAddr64Fn = int function(HANDLE, ulong, uint*, IMAGEHLP_LINEA64*); + private alias FuncTableAccessFn = void* function(HANDLE, ulong); + private alias GetModuleBaseFn = ulong function(HANDLE, ulong); + private alias StackWalk64Fn = int function( + uint machineType, HANDLE hProcess, HANDLE hThread, + STACKFRAME64* stackFrame, void* contextRecord, + void* readMemory, FuncTableAccessFn funcTableAccess, + GetModuleBaseFn getModuleBase, void* translateAddress); +} + +// --- Globals --------------------------------------------------------- + +private __gshared bool _dbg_inited; +private __gshared bool _dbg_available; +private __gshared HANDLE _dbg_process; +private __gshared SymFromAddrFn _sym_from_addr; +private __gshared SymGetLineFromAddr64Fn _sym_get_line; +private __gshared StackWalk64Fn _stack_walk64; +private __gshared FuncTableAccessFn _func_table_access64; +private __gshared GetModuleBaseFn _get_module_base64; + +// --- Initialization -------------------------------------------------- + +private void dbghelp_init() @trusted +{ + if (_dbg_inited) + return; + _dbg_inited = true; + + auto hDbg = loadLibraryA("dbghelp.dll"); + if (hDbg is null) + return; + + auto sym_init = cast(SymInitializeFn) getProcAddress(hDbg, "SymInitialize"); + auto sym_set_opt = cast(SymSetOptionsFn) getProcAddress(hDbg, "SymSetOptions"); + _sym_from_addr = cast(SymFromAddrFn) getProcAddress(hDbg, "SymFromAddr"); + _sym_get_line = cast(SymGetLineFromAddr64Fn) getProcAddress(hDbg, "SymGetLineFromAddr64"); + _stack_walk64 = cast(StackWalk64Fn) getProcAddress(hDbg, "StackWalk64"); + _func_table_access64 = cast(FuncTableAccessFn) getProcAddress(hDbg, "SymFunctionTableAccess64"); + _get_module_base64 = cast(GetModuleBaseFn) getProcAddress(hDbg, "SymGetModuleBase64"); + + if (sym_init is null || sym_set_opt is null || _sym_from_addr is null) + return; + + // SYMOPT_DEFERRED_LOAD | SYMOPT_LOAD_LINES + sym_set_opt(0x00000004 | 0x00000010); + + _dbg_process = cast(HANDLE) cast(size_t)-1; // GetCurrentProcess() pseudo-handle + if (!sym_init(_dbg_process, null, 1)) // fInvadeProcess = TRUE + return; + + _dbg_available = true; +} + +// --- Kernel32/ntdll imports ------------------------------------------ + +pragma(mangle, "LoadLibraryA") +extern(Windows) private void* loadLibraryA(const(char)*); + +pragma(mangle, "GetProcAddress") +extern(Windows) private void* getProcAddress(void*, const(char)*); + +pragma(mangle, "RtlCaptureStackBackTrace") +extern(Windows) private ushort rtlCaptureStackBackTrace( + uint FramesToSkip, uint FramesToCapture, void** BackTrace, uint* BackTraceHash); + +pragma(mangle, "RtlCaptureContext") +extern(Windows) private void rtlCaptureContext(void* contextRecord); + +pragma(mangle, "GetCurrentThread") +extern(Windows) private HANDLE getCurrentThread(); + +// --- StackWalk64 fallback -------------------------------------------- + +private size_t stack_walk64_capture(ref void*[32] addrs) @trusted +{ + dbghelp_init(); + + if (_stack_walk64 is null || _func_table_access64 is null || _get_module_base64 is null) + return 0; + + align(16) ubyte[CONTEXT_SIZE] ctx = 0; + *cast(uint*)(ctx.ptr + CTX_FLAGS_OFF) = CTX_FULL; + rtlCaptureContext(ctx.ptr); + + STACKFRAME64 sf; + version (Win64) + { + sf.AddrPC.Offset = *cast(ulong*)(ctx.ptr + CTX_IP_OFF); + sf.AddrFrame.Offset = *cast(ulong*)(ctx.ptr + CTX_FP_OFF); + sf.AddrStack.Offset = *cast(ulong*)(ctx.ptr + CTX_SP_OFF); + } + else + { + sf.AddrPC.Offset = *cast(uint*)(ctx.ptr + CTX_IP_OFF); + sf.AddrFrame.Offset = *cast(uint*)(ctx.ptr + CTX_FP_OFF); + sf.AddrStack.Offset = *cast(uint*)(ctx.ptr + CTX_SP_OFF); + } + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrFrame.Mode = AddrModeFlat; + sf.AddrStack.Mode = AddrModeFlat; + + auto hThread = getCurrentThread(); + size_t n = 0; + + // Skip internal frames (_d_createTrace + stack_walk64_capture + RtlCaptureContext) + uint skip = 3; + + while (n < 32) + { + if (!_stack_walk64(MACHINE_TYPE, _dbg_process, hThread, + &sf, ctx.ptr, null, _func_table_access64, _get_module_base64, null)) + break; + if (sf.AddrPC.Offset == 0) + break; + if (skip > 0) + { + --skip; + continue; + } + addrs[n++] = cast(void*) cast(size_t) sf.AddrPC.Offset; + } + return n; +} + + +// --- Driver interface ------------------------------------------------- +// +// All three primitives assume they are called through a one-level public +// wrapper in urt.internal.exception (kept non-inlined via pragma(inline, +// false)). That wrapper's frame is accounted for in the skip counts +// below. Direct callers (like _d_createTrace) get the same semantics +// because their frame substitutes for the missing wrapper. + +/// Capture the caller's call stack. First entry = return address of the +/// function that called the public `capture_trace` wrapper. +size_t _capture_trace(void*[] addrs) @trusted +{ + if (addrs.length == 0) + return 0; + auto count = cast(uint) addrs.length; + // +2: skip _capture_trace itself + the public wrapper. addrs[0] + // is then the PC inside USER (where USER called capture_trace). + auto n = rtlCaptureStackBackTrace(2, count, addrs.ptr, null); + if (n == 0 && addrs.length >= 32) + { + void*[32] scratch = void; + auto k = stack_walk64_capture(scratch); + auto copy = k < addrs.length ? k : addrs.length; + addrs[0 .. copy] = scratch[0 .. copy]; + n = cast(ushort) copy; + } + return n; +} + +/// Return the return address of the `skip`-th frame above the public +/// `caller_address` wrapper's caller. `skip=0` is the call site of +/// that caller - useful from inside an allocator to find the alloc +/// site. +void* _caller_address(uint skip) @trusted +{ + void* addr; + // +3: skip _caller_address itself + public wrapper + USER's own + // frame. With skip=0 the returned PC is in USER's caller - the + // semantic the doc promises ("call site of the caller", useful for + // allocation-site tracking). + if (rtlCaptureStackBackTrace(skip + 3, 1, &addr, null) == 0) + return null; + return addr; +} + +/// Resolve addr to symbol + optional file + line via DbgHelp. +/// Returned slices are owned by a static buffer - copy if you need +/// them across another call. +bool _resolve_address(void* addr, out Resolved r) @trusted +{ + import urt.mem : strlen; + + dbghelp_init(); + if (!_dbg_available) + return false; + + static align(8) ubyte[SYMBOL_INFOA.sizeof + 256] sym_buf; + sym_buf[] = 0; + auto p_sym = cast(SYMBOL_INFOA*) sym_buf.ptr; + p_sym.SizeOfStruct = SYMBOL_INFOA.sizeof; + p_sym.MaxNameLen = 256; + + ulong disp; + if (!_sym_from_addr(_dbg_process, cast(ulong) addr, &disp, p_sym)) + return false; + r.name = p_sym.Name.ptr[0 .. strlen(p_sym.Name.ptr)]; + r.offset = cast(size_t) disp; + + uint disp32; + IMAGEHLP_LINEA64 line_info; + line_info.SizeOfStruct = IMAGEHLP_LINEA64.sizeof; + if (_sym_get_line !is null && + _sym_get_line(_dbg_process, cast(ulong) addr, &disp32, &line_info)) + { + r.file = line_info.FileName[0 .. strlen(line_info.FileName)]; + r.line = line_info.LineNumber; + } + return true; +} + +/// Resolve many addresses. DbgHelp has no batch primitive - loop. +/// Returns false if DbgHelp is unavailable; otherwise true (individual +/// failed entries stay `Resolved.init` thanks to `out` auto-zeroing). +bool _resolve_batch(const(void*)[] addrs, Resolved[] results) @trusted +{ + dbghelp_init(); + if (!_dbg_available) + return false; + foreach (i, a; addrs) + cast(void) _resolve_address(cast(void*) a, results[i]); + return true; +} + +// ══════════════════════════════════════════════════════════════════════ +// Exception-handling runtime (compiler-specific) +// ══════════════════════════════════════════════════════════════════════ + +version (GDC) +{ + static assert(false, "GDC exception runtime not ported"); +} +else version (LDC) +{ + +// ---------------------------------------------------------------------- +// LDC MSVC SEH exception handling +// ---------------------------------------------------------------------- + + +// --- Windows ABI types --------------------------------------------------- + +private alias DWORD = uint; +private alias ULONG_PTR = size_t; + +extern(Windows) void RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, ULONG_PTR* lpArguments); + +// --- MSVC SEH structures ------------------------------------------------- + +// On Win64, type info pointers are 32-bit offsets from a heap base. +version (Win64) + private struct ImgPtr(T) { uint offset; } +else + private alias ImgPtr(T) = T*; + +private alias PMFN = ImgPtr!(void function(void*)); + +private struct TypeDescriptor +{ + uint hash; + void* spare; + char[1] name; // variable-size, zero-terminated +} + +private struct PMD +{ + int mdisp; + int pdisp; + int vdisp; +} + +private struct CatchableType +{ + uint properties; + ImgPtr!TypeDescriptor pType; + PMD thisDisplacement; + int sizeOrOffset; + PMFN copyFunction; +} + +private enum CT_IsSimpleType = 0x00000001; + +private struct CatchableTypeArray +{ + int nCatchableTypes; + ImgPtr!CatchableType[1] arrayOfCatchableTypes; // variable size +} + +private struct _ThrowInfo +{ + uint attributes; + PMFN pmfnUnwind; + PMFN pForwardCompat; + ImgPtr!CatchableTypeArray pCatchableTypeArray; +} + +private struct CxxExceptionInfo +{ + size_t Magic; + Throwable* pThrowable; + _ThrowInfo* ThrowInfo; + version (Win64) void* ImgBase; +} + +private enum int STATUS_MSC_EXCEPTION = 0xe0000000 | ('m' << 16) | ('s' << 8) | ('c' << 0); +private enum EXCEPTION_NONCONTINUABLE = 0x01; +private enum EH_MAGIC_NUMBER1 = 0x19930520; + + +// --- EH Heap (image-relative pointer support) ---------------------------- + +version (Win64) +{ + private struct EHHeap + { + nothrow @nogc: + + void* base; + size_t capacity; + size_t length; + + void initialize(size_t initial_capacity) + { + import urt.mem : alloc; + base = alloc(initial_capacity).ptr; + capacity = initial_capacity; + length = size_t.sizeof; // offset 0 reserved (null sentinel) + } + + size_t alloc(size_t size) + { + import urt.mem : alloc, memcpy; + auto offset = length; + enum align_mask = size_t.sizeof - 1; + auto new_length = (length + size + align_mask) & ~align_mask; + auto new_capacity = capacity; + while (new_length > new_capacity) + new_capacity *= 2; + if (new_capacity != capacity) + { + auto new_base = alloc(new_capacity).ptr; + memcpy(new_base, base, length); + // Old base leaks - may be referenced by in-flight exceptions. + base = new_base; + capacity = new_capacity; + } + length = new_length; + return offset; + } + } + + private __gshared EHHeap _eh_heap; + + private ImgPtr!T eh_malloc(T)(size_t size = T.sizeof) + { + return ImgPtr!T(cast(uint) _eh_heap.alloc(size)); + } + + private T* to_pointer(T)(ImgPtr!T img_ptr) + { + return cast(T*)(cast(ubyte*) _eh_heap.base + img_ptr.offset); + } +} +else // Win32 +{ + private ImgPtr!T eh_malloc(T)(size_t size = T.sizeof) + { + import urt.mem : alloc; + return cast(T*)alloc(size).ptr; + } + + private T* to_pointer(T)(T* img_ptr) + { + return img_ptr; + } +} + + +// --- ThrowInfo cache (simple linear-scan arrays) ------------------------- +// No mutex - OpenWatt's exception paths are single-threaded. + +private enum EH_CACHE_SIZE = 64; + +private __gshared ClassInfo[EH_CACHE_SIZE] _throw_info_keys; +private __gshared ImgPtr!_ThrowInfo[EH_CACHE_SIZE] _throw_info_vals; +private __gshared size_t _throw_info_len; + +private __gshared ClassInfo[EH_CACHE_SIZE] _catchable_keys; +private __gshared ImgPtr!CatchableType[EH_CACHE_SIZE] _catchable_vals; +private __gshared size_t _catchable_len; + +private __gshared bool _eh_initialized; + +private void ensure_eh_init() +{ + if (_eh_initialized) + return; + version (Win64) + _eh_heap.initialize(0x10000); + _eh_initialized = true; +} + + +// --- ThrowInfo generation ------------------------------------------------ + +private ImgPtr!CatchableType get_catchable_type(ClassInfo ti) +{ + import urt.mem : memcpy; + + foreach (i; 0 .. _catchable_len) + if (_catchable_keys[i] is ti) + return _catchable_vals[i]; + + const sz = TypeDescriptor.sizeof + ti.name.length + 1; + auto td = eh_malloc!TypeDescriptor(sz); + auto ptd = td.to_pointer; + + ptd.hash = 0; + ptd.spare = null; + ptd.name.ptr[0] = 'D'; + memcpy(ptd.name.ptr + 1, ti.name.ptr, ti.name.length); + ptd.name.ptr[ti.name.length + 1] = 0; + + auto ct = eh_malloc!CatchableType(); + ct.to_pointer[0] = CatchableType( + CT_IsSimpleType, td, PMD(0, -1, 0), + cast(int) size_t.sizeof, PMFN.init); + + if (_catchable_len < EH_CACHE_SIZE) + { + _catchable_keys[_catchable_len] = ti; + _catchable_vals[_catchable_len] = ct; + ++_catchable_len; + } + return ct; +} + +private ImgPtr!_ThrowInfo get_throw_info(ClassInfo ti) +{ + foreach (i; 0 .. _throw_info_len) + if (_throw_info_keys[i] is ti) + return _throw_info_vals[i]; + + int classes = 0; + for (ClassInfo tic = ti; tic !is null; tic = tic.base) + ++classes; + + const arr_size = int.sizeof + classes * ImgPtr!(CatchableType).sizeof; + ImgPtr!CatchableTypeArray cta = eh_malloc!CatchableTypeArray(arr_size); + to_pointer(cta).nCatchableTypes = classes; + + size_t c = 0; + for (ClassInfo tic = ti; tic !is null; tic = tic.base) + cta.to_pointer.arrayOfCatchableTypes.ptr[c++] = get_catchable_type(tic); + + auto tinf = eh_malloc!_ThrowInfo(); + *(tinf.to_pointer) = _ThrowInfo(0, PMFN.init, PMFN.init, cta); + + if (_throw_info_len < EH_CACHE_SIZE) + { + _throw_info_keys[_throw_info_len] = ti; + _throw_info_vals[_throw_info_len] = tinf; + ++_throw_info_len; + } + return tinf; +} + + +// --- Exception stack (thread-local) -------------------------------------- + +private struct ExceptionStack +{ +nothrow @nogc: + + void push(Throwable e) + { + if (_length == _cap) + grow(); + _p[_length++] = e; + } + + Throwable pop() + { + return _p[--_length]; + } + + void shrink(size_t sz) + { + while (_length > sz) + _p[--_length] = null; + } + + ref inout(Throwable) opIndex(size_t idx) inout + { + return _p[idx]; + } + + size_t find(Throwable e) + { + for (size_t i = _length; i > 0;) + if (_p[--i] is e) + return i; + return ~cast(size_t) 0; + } + + @property size_t length() const { return _length; } + +private: + + void grow() + { + import urt.mem : alloc, free, memcpy; + immutable ncap = _cap ? 2 * _cap : 16; + auto p = cast(Throwable*)alloc(ncap * size_t.sizeof).ptr; + if (_length > 0) + memcpy(p, _p, _length * size_t.sizeof); + if (_p !is null) + free((cast(void*)_p)[0 .. _cap * size_t.sizeof]); + _p = p; + _cap = ncap; + } + + size_t _length; + Throwable* _p; + size_t _cap; +} + +private ExceptionStack _exception_stack; + + +// --- Exception chaining -------------------------------------------------- + +private Throwable chain_exceptions(Throwable e, Throwable t) +{ + if (!cast(Error) e) + if (auto err = cast(Error) t) + { + err.bypassedException = e; + return err; + } + return Throwable.chainTogether(e, t); +} + + +// --- Core SEH API -------------------------------------------------------- + +extern(C) void _d_throw_exception(Throwable throwable) +{ + if (throwable is null || typeid(throwable) is null) + { + terminate(); + assert(0); + } + + auto refcount = throwable.refcount(); + if (refcount) + throwable.refcount() = refcount + 1; + + ensure_eh_init(); + + _exception_stack.push(throwable); + _d_createTrace(throwable, null); + + CxxExceptionInfo info; + info.Magic = EH_MAGIC_NUMBER1; + info.pThrowable = &throwable; + info.ThrowInfo = get_throw_info(typeid(throwable)).to_pointer; + version (Win64) + info.ImgBase = _eh_heap.base; + + RaiseException(STATUS_MSC_EXCEPTION, EXCEPTION_NONCONTINUABLE, + info.sizeof / size_t.sizeof, cast(ULONG_PTR*) &info); +} + +extern(C) Throwable _d_eh_enter_catch(void* ptr, ClassInfo catch_type) +{ + if (ptr is null) + return null; + + auto e = *(cast(Throwable*) ptr); + size_t pos = _exception_stack.find(e); + if (pos >= _exception_stack.length()) + return null; // not a D exception + + auto caught = e; + + // Chain inner unhandled exceptions as collateral + for (size_t p = pos + 1; p < _exception_stack.length(); ++p) + e = chain_exceptions(e, _exception_stack[p]); + _exception_stack.shrink(pos); + + if (e !is caught) + { + if (_d_isbaseof(typeid(e), catch_type)) + *cast(Throwable*) ptr = e; + else + _d_throw_exception(e); // rethrow collateral + } + return e; +} + +extern(C) bool _d_enter_cleanup(void* ptr) @trusted +{ + // Prevents LLVM from optimizing away cleanup (finally) blocks. + return true; +} + +extern(C) void _d_leave_cleanup(void* ptr) @trusted +{ +} + +} // version (LDC) +else version (Win32) +{ + +// ---------------------------------------------------------------------- +// DMD Win32 SEH-based exception handling +// ---------------------------------------------------------------------- + + +// ---------------------------------------------------------------------- +// Win32 SEH-based exception handling +// ---------------------------------------------------------------------- + +// Windows types (inlined to avoid core.sys.windows dependency) +alias DWORD = uint; +alias BYTE = ubyte; +alias PVOID = void*; +alias ULONG_PTR = size_t; + +enum size_t EXCEPTION_MAXIMUM_PARAMETERS = 15; +enum DWORD EXCEPTION_NONCONTINUABLE = 1; +enum EXCEPTION_UNWIND = 6; +enum EXCEPTION_COLLATERAL = 0x100; + +enum DWORD STATUS_DIGITAL_MARS_D_EXCEPTION = (3 << 30) | (1 << 29) | (0 << 28) | ('D' << 16) | 1; + +struct EXCEPTION_RECORD +{ + DWORD ExceptionCode; + DWORD ExceptionFlags; + EXCEPTION_RECORD* ExceptionRecord; + PVOID ExceptionAddress; + DWORD NumberParameters; + ULONG_PTR[EXCEPTION_MAXIMUM_PARAMETERS] ExceptionInformation; +} + +enum MAXIMUM_SUPPORTED_EXTENSION = 512; + +struct FLOATING_SAVE_AREA +{ + DWORD ControlWord, StatusWord, TagWord; + DWORD ErrorOffset, ErrorSelector; + DWORD DataOffset, DataSelector; + BYTE[80] RegisterArea; + DWORD Cr0NpxState; +} + +struct CONTEXT +{ + DWORD ContextFlags; + DWORD Dr0, Dr1, Dr2, Dr3, Dr6, Dr7; + FLOATING_SAVE_AREA FloatSave; + DWORD SegGs, SegFs, SegEs, SegDs; + DWORD Edi, Esi, Ebx, Edx, Ecx, Eax; + DWORD Ebp, Eip, SegCs, EFlags, Esp, SegSs; + BYTE[MAXIMUM_SUPPORTED_EXTENSION] ExtendedRegisters; +} + +struct EXCEPTION_POINTERS +{ + EXCEPTION_RECORD* ExceptionRecord; + CONTEXT* ContextRecord; +} + +enum EXCEPTION_DISPOSITION +{ + ExceptionContinueExecution, + ExceptionContinueSearch, + ExceptionNestedException, + ExceptionCollidedUnwind, +} + +alias LanguageSpecificHandler = extern(C) + EXCEPTION_DISPOSITION function( + EXCEPTION_RECORD* exceptionRecord, + DEstablisherFrame* frame, + CONTEXT* context, + void* dispatcherContext); + +extern(Windows) void RaiseException(DWORD, DWORD, DWORD, void*); +extern(Windows) void RtlUnwind(void* targetFrame, void* targetIp, EXCEPTION_RECORD* pExceptRec, void* valueForEAX); + +extern(C) extern __gshared DWORD _except_list; // FS:[0] + +// Data structures - compiler-generated exception handler tables (Win32) + +struct DEstablisherFrame +{ + DEstablisherFrame* prev; + LanguageSpecificHandler handler; + DWORD table_index; + DWORD ebp; +} + +struct DHandlerInfo +{ + int prev_index; + uint cioffset; // offset to DCatchInfo data from start of table + void* finally_code; // pointer to finally code (!=null if try-finally) +} + +struct DHandlerTable +{ + void* fptr; // pointer to start of function + uint espoffset; + uint retoffset; + DHandlerInfo[1] handler_info; +} + +struct DCatchBlock +{ + ClassInfo type; + uint bpoffset; // EBP offset of catch var + void* code; // catch handler code pointer +} + +struct DCatchInfo +{ + uint ncatches; + DCatchBlock[1] catch_block; +} + +// InFlight exception list (per-stack, swapped on fiber switches) + +EXCEPTION_RECORD* inflight_exception_list = null; + +extern(C) void* _d_eh_swapContext(void* newContext) +{ + auto old = inflight_exception_list; + inflight_exception_list = cast(EXCEPTION_RECORD*) newContext; + return old; +} + +private EXCEPTION_RECORD* skip_collateral_exceptions(EXCEPTION_RECORD* n) @trusted +{ + while (n.ExceptionRecord && n.ExceptionFlags & EXCEPTION_COLLATERAL) + n = n.ExceptionRecord; + return n; +} + +// SEH to D exception translation + +private Throwable _d_translate_se_to_d_exception(EXCEPTION_RECORD* exceptionRecord, CONTEXT*) @trusted +{ + if (exceptionRecord.ExceptionCode == STATUS_DIGITAL_MARS_D_EXCEPTION) + return cast(Throwable) cast(void*)(exceptionRecord.ExceptionInformation[0]); + + // Non-D (hardware) exceptions: cannot create Error objects without GC. + terminate(); + assert(0); +} + +// _d_throwc - throw a D exception via Windows SEH + +private void throw_impl(Throwable h) @trusted +{ + auto refcount = h.refcount(); + if (refcount) + h.refcount() = refcount + 1; + + _d_createTrace(h, null); + RaiseException(STATUS_DIGITAL_MARS_D_EXCEPTION, EXCEPTION_NONCONTINUABLE, 1, cast(void*)&h); +} + +extern(C) void _d_throwc(Throwable h) @trusted +{ + version (D_InlineAsm_X86) + asm @nogc nothrow + { + naked; + enter 0, 0; + mov EAX, [EBP + 8]; + call throw_impl; + leave; + ret; + } +} + +// _d_framehandler - SEH frame handler called by OS for each frame. +// The handler table address is passed in EAX by compiler-generated thunks. + +extern(C) EXCEPTION_DISPOSITION _d_framehandler( + EXCEPTION_RECORD* exceptionRecord, + DEstablisherFrame* frame, + CONTEXT* context, + void* dispatcherContext) @trusted +{ + DHandlerTable* handler_table; + asm @nogc nothrow { mov handler_table, EAX; } + + if (exceptionRecord.ExceptionFlags & EXCEPTION_UNWIND) + { + // Unwind phase: call all finally blocks in this frame + _d_local_unwind(handler_table, frame, -1, &unwindCollisionExceptionHandler); + } + else + { + // Search phase: look for a matching catch handler + + EXCEPTION_RECORD* master = null; + ClassInfo master_class_info = null; + + int prev_ndx; + for (auto ndx = frame.table_index; ndx != -1; ndx = prev_ndx) + { + auto phi = &handler_table.handler_info.ptr[ndx]; + prev_ndx = phi.prev_index; + + if (phi.cioffset) + { + auto pci = cast(DCatchInfo*)(cast(ubyte*) handler_table + phi.cioffset); + auto ncatches = pci.ncatches; + + foreach (i; 0 .. ncatches) + { + auto pcb = &pci.catch_block.ptr[i]; + + // Walk the collateral exception chain to find the master + EXCEPTION_RECORD* er = exceptionRecord; + master = null; + master_class_info = null; + + for (;;) + { + if (er.ExceptionCode == STATUS_DIGITAL_MARS_D_EXCEPTION) + { + ClassInfo ci = (**(cast(ClassInfo**)(er.ExceptionInformation[0]))); + if (!master && !(er.ExceptionFlags & EXCEPTION_COLLATERAL)) + { + master = er; + master_class_info = ci; + break; + } + if (_d_isbaseof(ci, typeid(Error))) + { + master = er; + master_class_info = ci; + } + } + else + { + // Non-D exception - cannot translate without GC + terminate(); + } + + if (!(er.ExceptionFlags & EXCEPTION_COLLATERAL)) + break; + + if (er.ExceptionRecord) + er = er.ExceptionRecord; + else + er = inflight_exception_list; + } + + if (_d_isbaseof(master_class_info, pcb.type)) + { + // Found matching catch handler + + auto original_exception = skip_collateral_exceptions(exceptionRecord); + if (original_exception.ExceptionRecord is null + && !(exceptionRecord is inflight_exception_list)) + original_exception.ExceptionRecord = inflight_exception_list; + inflight_exception_list = exceptionRecord; + + // Global unwind: call finally blocks in intervening frames + _d_global_unwind(frame, exceptionRecord); + + // Local unwind: call finally blocks skipped in this frame + _d_local_unwind(handler_table, frame, ndx, + &searchCollisionExceptionHandler); + + frame.table_index = prev_ndx; + + // Build D exception chain from SEH records + EXCEPTION_RECORD* z = exceptionRecord; + Throwable prev = null; + Error master_error = null; + + for (;;) + { + Throwable w = _d_translate_se_to_d_exception(z, context); + if (z == master && (z.ExceptionFlags & EXCEPTION_COLLATERAL)) + master_error = cast(Error) w; + prev = Throwable.chainTogether(w, prev); + if (!(z.ExceptionFlags & EXCEPTION_COLLATERAL)) + break; + z = z.ExceptionRecord; + } + + Throwable pti; + if (master_error) + { + master_error.bypassedException = prev; + pti = master_error; + } + else + pti = prev; + + inflight_exception_list = z.ExceptionRecord; + + // Initialize catch variable and jump to handler + int regebp = cast(int)&frame.ebp; + *cast(Object*)(regebp + pcb.bpoffset) = pti; + + { + uint catch_esp; + alias fp_t = void function(); + fp_t catch_addr = cast(fp_t) pcb.code; + catch_esp = regebp - handler_table.espoffset - fp_t.sizeof; + asm @nogc nothrow + { + mov EAX, catch_esp; + mov ECX, catch_addr; + mov [EAX], ECX; + mov EBP, regebp; + mov ESP, EAX; + ret; + } + } + } + } + } + } + } + return EXCEPTION_DISPOSITION.ExceptionContinueSearch; +} + +// Exception filter for __try/__except around Dmain + +extern(C) int _d_exception_filter(EXCEPTION_POINTERS* eptrs, int retval, Object* exception_object) @trusted +{ + *exception_object = _d_translate_se_to_d_exception(eptrs.ExceptionRecord, eptrs.ContextRecord); + return retval; +} + +// Collision exception handlers + +extern(C) EXCEPTION_DISPOSITION searchCollisionExceptionHandler( + EXCEPTION_RECORD* exceptionRecord, + DEstablisherFrame*, + CONTEXT*, + void* dispatcherContext) @trusted +{ + if (!(exceptionRecord.ExceptionFlags & EXCEPTION_UNWIND)) + { + auto n = skip_collateral_exceptions(exceptionRecord); + n.ExceptionFlags |= EXCEPTION_COLLATERAL; + return EXCEPTION_DISPOSITION.ExceptionContinueSearch; + } + // Collision during SEARCH phase - restart from 'frame' + *(cast(void**) dispatcherContext) = dispatcherContext; // frame + return EXCEPTION_DISPOSITION.ExceptionCollidedUnwind; +} + +extern(C) EXCEPTION_DISPOSITION unwindCollisionExceptionHandler( + EXCEPTION_RECORD* exceptionRecord, + DEstablisherFrame* frame, + CONTEXT*, + void* dispatcherContext) @trusted +{ + if (!(exceptionRecord.ExceptionFlags & EXCEPTION_UNWIND)) + { + auto n = skip_collateral_exceptions(exceptionRecord); + n.ExceptionFlags |= EXCEPTION_COLLATERAL; + return EXCEPTION_DISPOSITION.ExceptionContinueSearch; + } + // Collision during UNWIND phase - restart from 'frame.prev' + *(cast(void**) dispatcherContext) = frame.prev; + return EXCEPTION_DISPOSITION.ExceptionCollidedUnwind; +} + +// Local unwind - run finally blocks in the current frame + +extern(C) void _d_local_unwind(DHandlerTable* handler_table, DEstablisherFrame* frame, int stop_index, LanguageSpecificHandler collision_handler) @trusted +{ + // Install collision handler on SEH chain + asm @nogc nothrow + { + push dword ptr -1; + push dword ptr 0; + push collision_handler; + push dword ptr FS:_except_list; + mov FS:_except_list, ESP; + } + + for (auto i = frame.table_index; i != -1 && i != stop_index;) + { + auto phi = &handler_table.handler_info.ptr[i]; + i = phi.prev_index; + + if (phi.finally_code) + { + auto catch_ebp = &frame.ebp; + auto blockaddr = phi.finally_code; + asm @nogc nothrow + { + push EBX; + mov EBX, blockaddr; + push EBP; + mov EBP, catch_ebp; + call EBX; + pop EBP; + pop EBX; + } + } + } + + // Remove collision handler from SEH chain + asm @nogc nothrow + { + pop FS:_except_list; + add ESP, 12; + } +} + +// Global unwind - thin wrapper around RtlUnwind + +extern(C) int _d_global_unwind(DEstablisherFrame* pFrame, EXCEPTION_RECORD* eRecord) @trusted +{ + asm @nogc nothrow + { + naked; + push EBP; + mov EBP, ESP; + push ECX; + push EBX; + push ESI; + push EDI; + push EBP; + push 0; + push dword ptr 12[EBP]; // eRecord + call __system_unwind; + jmp __unwind_exit; + __system_unwind: + push dword ptr 8[EBP]; // pFrame + call RtlUnwind; + __unwind_exit: + pop EBP; + pop EDI; + pop ESI; + pop EBX; + pop ECX; + mov ESP, EBP; + pop EBP; + ret; + } +} + +// Local unwind for goto/return across finally blocks + +extern(C) void _d_local_unwind2() @trusted +{ + asm @nogc nothrow + { + naked; + jmp _d_localUnwindForGoto; + } +} + +extern(C) void _d_localUnwindForGoto(DHandlerTable* handler_table, DEstablisherFrame* frame, int stop_index) @trusted +{ + _d_local_unwind(handler_table, frame, stop_index, &searchCollisionExceptionHandler); +} + +// Monitor handler stubs (for synchronized blocks) + +extern(C) EXCEPTION_DISPOSITION _d_monitor_handler(EXCEPTION_RECORD* exceptionRecord, DEstablisherFrame*, CONTEXT*, void*) @trusted +{ + if (exceptionRecord.ExceptionFlags & EXCEPTION_UNWIND) + { + // TODO: _d_monitorexit(cast(Object)cast(void*)frame.table_index); + } + return EXCEPTION_DISPOSITION.ExceptionContinueSearch; +} + +extern(C) void _d_monitor_prolog(void*, void*, Object) @trusted +{ + // TODO: _d_monitorenter(h); +} + +extern(C) void _d_monitor_epilog(void*, void*, Object) @trusted +{ + // TODO: _d_monitorexit(h); +} + + + +} // version (Win32) +else version (Win64) +{ + +// ---------------------------------------------------------------------- +// DMD Win64 - RBP-chain walking with ._deh section tables +// ---------------------------------------------------------------------- + + +// ---------------------------------------------------------------------- +// Win64 - RBP-chain walking with ._deh section tables +// ---------------------------------------------------------------------- + +// Data structures - compiler-generated exception handler tables + +struct DHandlerInfo +{ + uint offset; // offset from function address to start of guarded section + uint endoffset; // offset of end of guarded section + int prev_index; // previous table index + uint cioffset; // offset to DCatchInfo data from start of table (!=0 if try-catch) + size_t finally_offset; // offset to finally code to execute (!=0 if try-finally) +} + +struct DHandlerTable +{ + uint espoffset; // offset of ESP from EBP + uint retoffset; // offset from start of function to return code + size_t nhandlers; // dimension of handler_info[] + DHandlerInfo[1] handler_info; +} + +struct DCatchBlock +{ + ClassInfo type; // catch type + size_t bpoffset; // EBP offset of catch var + size_t codeoffset; // catch handler offset +} + +struct DCatchInfo +{ + size_t ncatches; // number of catch blocks + DCatchBlock[1] catch_block; +} + +struct FuncTable +{ + void* fptr; // pointer to start of function + DHandlerTable* handlertable; + uint fsize; // size of function in bytes +} + +// InFlight exception tracking (per-stack, swapped on fiber switches) + +private struct InFlight +{ + InFlight* next; + void* addr; + Throwable t; +} + +private __gshared InFlight* __inflight = null; + +extern(C) void* _d_eh_swapContext(void* newContext) +{ + auto old = __inflight; + __inflight = cast(InFlight*) newContext; + return old; +} + +// DEH section scanning - find exception handler tables in the binary + +version (Windows) + extern(C) extern __gshared ubyte __ImageBase; + +private __gshared immutable(FuncTable)* _deh_start; +private __gshared immutable(FuncTable)* _deh_end; + +private void ensure_deh_loaded() @trusted +{ + if (_deh_start !is null) + return; + + version (Windows) + { + auto section = find_pe_section(cast(void*) &__ImageBase, "._deh\0\0\0"); + if (section.length) + { + _deh_start = cast(immutable(FuncTable)*) section.ptr; + _deh_end = cast(immutable(FuncTable)*)(section.ptr + section.length); + } + } + else version (linux) + { + // TODO: ELF section scanning for .deh + } +} + +/// PE section lookup - duplicated from urt.package to avoid import cycle. +private void[] find_pe_section(void* imageBase, string name) @trusted +{ + if (name.length > 8) + return null; + + auto base = cast(ubyte*) imageBase; + if (base[0] != 0x4D || base[1] != 0x5A) + return null; + + auto lfanew = *cast(int*)(base + 0x3C); + auto pe = base + lfanew; + if (pe[0] != 'P' || pe[1] != 'E' || pe[2] != 0 || pe[3] != 0) + return null; + + auto fileHeader = pe + 4; + ushort numSections = *cast(ushort*)(fileHeader + 2); + ushort optHeaderSize = *cast(ushort*)(fileHeader + 16); + auto sections = fileHeader + 20 + optHeaderSize; + + foreach (i; 0 .. numSections) + { + auto sec = sections + i * 40; + auto secName = (cast(char*) sec)[0 .. 8]; + bool match = true; + foreach (j; 0 .. 8) + { + if (secName[j] != name[j]) + { + match = false; + break; + } + } + if (match) + { + auto virtualSize = *cast(uint*)(sec + 8); + auto virtualAddress = *cast(uint*)(sec + 12); + return (base + virtualAddress)[0 .. virtualSize]; + } + } + return null; +} + +// Handler table lookup + +immutable(FuncTable)* __eh_finddata(void* address) @trusted +{ + ensure_deh_loaded(); + if (_deh_start is null) + return null; + return __eh_finddata_range(address, _deh_start, _deh_end); +} + +immutable(FuncTable)* __eh_finddata_range(void* address, immutable(FuncTable)* pstart, immutable(FuncTable)* pend) @trusted +{ + for (auto ft = pstart; ; ft++) + { + Lagain: + if (ft >= pend) + break; + + version (Win64) + { + // MS Linker sometimes inserts zero padding between .obj sections + if (ft.fptr == null) + { + ft = cast(immutable(FuncTable)*)(cast(void**) ft + 1); + goto Lagain; + } + } + + immutable(void)* fptr = ft.fptr; + version (Win64) + { + // Follow JMP indirection from /DEBUG linker + if ((cast(ubyte*) fptr)[0] == 0xE9) + fptr = fptr + 5 + *cast(int*)(fptr + 1); + } + + if (fptr <= address && address < cast(void*)(cast(char*) fptr + ft.fsize)) + return ft; + } + return null; +} + +// Stack frame walking + +size_t __eh_find_caller(size_t regbp, size_t* pretaddr) @trusted +{ + size_t bp = *cast(size_t*) regbp; + + if (bp) + { + // Stack grows downward - new BP must be above old + if (bp <= regbp) + terminate(); + + *pretaddr = *cast(size_t*)(regbp + size_t.sizeof); + } + return bp; +} + +// _d_throwc - the core throw implementation (RBP-chain walking) + +alias fp_t = int function(); + +extern(C) void _d_throwc(Throwable h) @trusted +{ + size_t regebp; + + version (D_InlineAsm_X86) + asm nothrow @nogc { mov regebp, EBP; } + else version (D_InlineAsm_X86_64) + asm nothrow @nogc { mov regebp, RBP; } + + // Increment reference count if refcounted + auto refcount = h.refcount(); + if (refcount) + h.refcount() = refcount + 1; + + _d_createTrace(h, null); + + while (1) + { + size_t retaddr; + + regebp = __eh_find_caller(regebp, &retaddr); + if (!regebp) + break; + + auto func_table = __eh_finddata(cast(void*) retaddr); + auto handler_table = func_table ? func_table.handlertable : null; + if (!handler_table) + continue; + + auto funcoffset = cast(size_t) func_table.fptr; + version (Win64) + { + // Follow JMP indirection from /DEBUG linker + if ((cast(ubyte*) funcoffset)[0] == 0xE9) + funcoffset = funcoffset + 5 + *cast(int*)(funcoffset + 1); + } + + // Find start index for retaddr in handler table + auto dim = handler_table.nhandlers; + auto index = -1; + for (uint i = 0; i < dim; i++) + { + auto phi = &handler_table.handler_info.ptr[i]; + if (retaddr > funcoffset + phi.offset && + retaddr <= funcoffset + phi.endoffset) + index = i; + } + + // Handle inflight exception chaining + if (dim) + { + auto phi = &handler_table.handler_info.ptr[index + 1]; + auto prev = cast(InFlight*) &__inflight; + auto curr = prev.next; + + if (curr !is null && curr.addr == cast(void*)(funcoffset + phi.finally_offset)) + { + auto e = cast(Error) h; + if (e !is null && (cast(Error) curr.t) is null) + { + e.bypassedException = curr.t; + prev.next = curr.next; + } + else + { + h = Throwable.chainTogether(curr.t, h); + prev.next = curr.next; + } + } + } + + // Walk handler table entries + int prev_ndx; + for (auto ndx = index; ndx != -1; ndx = prev_ndx) + { + auto phi = &handler_table.handler_info.ptr[ndx]; + prev_ndx = phi.prev_index; + + if (phi.cioffset) + { + // Catch handler + auto pci = cast(DCatchInfo*)(cast(char*) handler_table + phi.cioffset); + auto ncatches = pci.ncatches; + + for (uint i = 0; i < ncatches; i++) + { + auto ci = **cast(ClassInfo**) h; + auto pcb = &pci.catch_block.ptr[i]; + + if (_d_isbaseof(ci, pcb.type)) + { + // Initialize catch variable + *cast(void**)(regebp + pcb.bpoffset) = cast(void*) h; + + // Jump to catch block - does not return + { + size_t catch_esp; + fp_t catch_addr; + + catch_addr = cast(fp_t)(funcoffset + pcb.codeoffset); + catch_esp = regebp - handler_table.espoffset - fp_t.sizeof; + + version (D_InlineAsm_X86) + asm nothrow @nogc + { + mov EAX, catch_esp; + mov ECX, catch_addr; + mov [EAX], ECX; + mov EBP, regebp; + mov ESP, EAX; + ret; + } + else version (D_InlineAsm_X86_64) + asm nothrow @nogc + { + mov RAX, catch_esp; + mov RCX, catch_esp; + mov RCX, catch_addr; + mov [RAX], RCX; + mov RBP, regebp; + mov RSP, RAX; + ret; + } + } + } + } + } + else if (phi.finally_offset) + { + // Finally block + auto blockaddr = cast(void*)(funcoffset + phi.finally_offset); + InFlight inflight; + + inflight.addr = blockaddr; + inflight.next = __inflight; + inflight.t = h; + __inflight = &inflight; + + version (D_InlineAsm_X86) + asm nothrow @nogc + { + push EBX; + mov EBX, blockaddr; + push EBP; + mov EBP, regebp; + call EBX; + pop EBP; + pop EBX; + } + else version (D_InlineAsm_X86_64) + asm nothrow @nogc + { + sub RSP, 8; + push RBX; + mov RBX, blockaddr; + push RBP; + mov RBP, regebp; + call RBX; + pop RBP; + pop RBX; + add RSP, 8; + } + + if (__inflight is &inflight) + __inflight = __inflight.next; + } + } + } + terminate(); +} + + +} // version (Win64) diff --git a/src/urt/internal/dwarfeh.d b/src/urt/internal/dwarfeh.d new file mode 100644 index 0000000..1ae55d6 --- /dev/null +++ b/src/urt/internal/dwarfeh.d @@ -0,0 +1,779 @@ +/// DWARF / Itanium-ABI exception-handling runtime. +/// +/// Shared between DMD-Linux and LDC on every non-Windows target +/// (including bare-metal LDC like BL808). pragma(mangle) picks the +/// compiler-specific symbol names: +/// DMD: _d_throwdwarf, __dmd_begin_catch, __dmd_personality_v0 +/// LDC: _d_throw_exception, _d_eh_enter_catch, _d_eh_personality +/// +/// Lives in urt.internal (not sys.posix) so it compiles on bare-metal +/// builds whose Makefile source list excludes sys/posix/**. +/// +/// Ported from druntime rt/dwarfeh.d. +module urt.internal.dwarfeh; + +version (Windows) {} else: + +import urt.internal.exception : ClassInfo, _d_isbaseof, _d_createTrace; + + +// --------------------------------------------------------------------- +// DWARF exception handling (shared by DMD and LDC) +// +// Uses the GCC/DWARF unwinder (libgcc_s / libunwind). +// pragma(mangle) selects the compiler-specific symbol names: +// DMD: _d_throwdwarf, __dmd_begin_catch, __dmd_personality_v0 +// LDC: _d_throw_exception, _d_eh_enter_catch, _d_eh_personality +// +// Ported from druntime rt/dwarfeh.d. +// Copyright: Digital Mars 2015-2016 (original), uRT authors (port). +// License: Boost Software License 1.0 +// --------------------------------------------------------------------- + +// --- libunwind / libgcc_s bindings ----------------------------------- + +private: + +alias _Unwind_Ptr = size_t; +alias _Unwind_Word = size_t; +alias _Unwind_Exception_Class = ulong; +alias _uleb128_t = size_t; +alias _sleb128_t = ptrdiff_t; + +alias _Unwind_Reason_Code = int; +enum +{ + _URC_NO_REASON = 0, + _URC_FOREIGN_EXCEPTION_CAUGHT = 1, + _URC_FATAL_PHASE2_ERROR = 2, + _URC_FATAL_PHASE1_ERROR = 3, + _URC_NORMAL_STOP = 4, + _URC_END_OF_STACK = 5, + _URC_HANDLER_FOUND = 6, + _URC_INSTALL_CONTEXT = 7, + _URC_CONTINUE_UNWIND = 8, +} + +alias _Unwind_Action = int; +enum : _Unwind_Action +{ + _UA_SEARCH_PHASE = 1, + _UA_CLEANUP_PHASE = 2, + _UA_HANDLER_FRAME = 4, + _UA_FORCE_UNWIND = 8, +} + +alias _Unwind_Exception_Cleanup_Fn = extern(C) void function( + _Unwind_Reason_Code, _Unwind_Exception*); + +version (X86_64) +{ + align(16) struct _Unwind_Exception + { + _Unwind_Exception_Class exception_class; + _Unwind_Exception_Cleanup_Fn exception_cleanup; + _Unwind_Word private_1; + _Unwind_Word private_2; + } +} +else +{ + align(8) struct _Unwind_Exception + { + _Unwind_Exception_Class exception_class; + _Unwind_Exception_Cleanup_Fn exception_cleanup; + _Unwind_Word private_1; + _Unwind_Word private_2; + } +} + +struct _Unwind_Context; + +extern(C) nothrow @nogc +{ + _Unwind_Reason_Code _Unwind_RaiseException(_Unwind_Exception*); + void _Unwind_DeleteException(_Unwind_Exception*); + _Unwind_Word _Unwind_GetGR(_Unwind_Context*, int); + void _Unwind_SetGR(_Unwind_Context*, int, _Unwind_Word); + _Unwind_Ptr _Unwind_GetIP(_Unwind_Context*); + _Unwind_Ptr _Unwind_GetIPInfo(_Unwind_Context*, int*); + void _Unwind_SetIP(_Unwind_Context*, _Unwind_Ptr); + void* _Unwind_GetLanguageSpecificData(_Unwind_Context*); + _Unwind_Ptr _Unwind_GetRegionStart(_Unwind_Context*); +} + + +// --- ARM EABI unwinder types ---------------------------------------- + +version (ARM) +{ + alias _Unwind_State = int; + enum : _Unwind_State + { + _US_VIRTUAL_UNWIND_FRAME = 0, + _US_UNWIND_FRAME_STARTING = 1, + _US_UNWIND_FRAME_RESUME = 2, + _US_ACTION_MASK = 3, + _US_FORCE_UNWIND = 8, + } + + extern(C) void _Unwind_Complete(_Unwind_Exception*) nothrow @nogc; +} + +// --- EH register numbers (architecture-specific) -------------------- + +version (X86_64) +{ + enum eh_exception_regno = 0; + enum eh_selector_regno = 1; +} +else version (X86) +{ + enum eh_exception_regno = 0; + enum eh_selector_regno = 2; +} +else version (AArch64) +{ + enum eh_exception_regno = 0; + enum eh_selector_regno = 1; +} +else version (ARM) +{ + enum eh_exception_regno = 0; + enum eh_selector_regno = 1; +} +else version (RISCV64) +{ + enum eh_exception_regno = 10; + enum eh_selector_regno = 11; +} +else version (RISCV32) +{ + enum eh_exception_regno = 10; + enum eh_selector_regno = 11; +} +else version (Xtensa) +{ + enum eh_exception_regno = 2; // a2 + enum eh_selector_regno = 3; // a3 +} +else + static assert(0, "Unknown EH register numbers for this architecture"); + +// --- DWARF encoding constants --------------------------------------- + +enum +{ + DW_EH_PE_FORMAT_MASK = 0x0F, + DW_EH_PE_APPL_MASK = 0x70, + DW_EH_PE_indirect = 0x80, + + DW_EH_PE_omit = 0xFF, + DW_EH_PE_ptr = 0x00, + DW_EH_PE_uleb128 = 0x01, + DW_EH_PE_udata2 = 0x02, + DW_EH_PE_udata4 = 0x03, + DW_EH_PE_udata8 = 0x04, + DW_EH_PE_sleb128 = 0x09, + DW_EH_PE_sdata2 = 0x0A, + DW_EH_PE_sdata4 = 0x0B, + DW_EH_PE_sdata8 = 0x0C, + + DW_EH_PE_absptr = 0x00, + DW_EH_PE_pcrel = 0x10, + DW_EH_PE_textrel = 0x20, + DW_EH_PE_datarel = 0x30, + DW_EH_PE_funcrel = 0x40, + DW_EH_PE_aligned = 0x50, +} + +// --- DMD exception class identifier --------------------------------- + +enum _Unwind_Exception_Class dmd_exception_class = + (cast(_Unwind_Exception_Class)'D' << 56) | + (cast(_Unwind_Exception_Class)'M' << 48) | + (cast(_Unwind_Exception_Class)'D' << 40) | + (cast(_Unwind_Exception_Class)'D' << 24); + +// --- Compiler-specific symbol names --------------------------------- + +version (LDC) +{ + private enum throw_mangle = "_d_throw_exception"; + private enum catch_mangle = "_d_eh_enter_catch"; + private enum personality_mangle = "_d_eh_personality"; +} +else +{ + private enum throw_mangle = "_d_throwdwarf"; + private enum catch_mangle = "__dmd_begin_catch"; + private enum personality_mangle = "__dmd_personality_v0"; +} + +// --- ExceptionHeader ------------------------------------------------ + +struct ExceptionHeader +{ + Throwable object; + _Unwind_Exception exception_object; + + int handler; + const(ubyte)* language_specific_data; + _Unwind_Ptr landing_pad; + + ExceptionHeader* next; + + static ExceptionHeader* stack; // thread-local chain + static ExceptionHeader ehstorage; // pre-allocated (one per thread) + + static ExceptionHeader* create(Throwable o) nothrow @nogc + { + import urt.mem.alloc : alloc; + auto eh = &ehstorage; + if (eh.object) + { + eh = cast(ExceptionHeader*)alloc(ExceptionHeader.sizeof).ptr; + if (!eh) + dwarf_terminate(__LINE__); + } + eh.object = o; + eh.exception_object.exception_class = dmd_exception_class; + return eh; + } + + static void release(ExceptionHeader* eh) nothrow @nogc + { + import urt.mem.alloc : free; + *eh = ExceptionHeader.init; + if (eh != &ehstorage) + free(eh[0..1]); + } + + void push() nothrow @nogc + { + next = stack; + stack = &this; + } + + static ExceptionHeader* pop() nothrow @nogc + { + auto eh = stack; + stack = eh.next; + return eh; + } + + static ExceptionHeader* to_exception_header(_Unwind_Exception* eo) nothrow @nogc + { + return cast(ExceptionHeader*)(cast(void*) eo - ExceptionHeader.exception_object.offsetof); + } +} + +// --- Helpers -------------------------------------------------------- + +_Unwind_Ptr read_unaligned(T, bool consume)(ref const(ubyte)* p) nothrow @nogc @trusted +{ + import urt.processor : SupportUnalignedLoadStore; + static if (SupportUnalignedLoadStore) + T value = *cast(T*) p; + else + { + import urt.mem : memcpy; + T value = void; + memcpy(&value, p, T.sizeof); + } + + static if (consume) + p += T.sizeof; + return cast(_Unwind_Ptr) value; +} + +_uleb128_t u_leb128(const(ubyte)** p) nothrow @nogc +{ + auto q = *p; + _uleb128_t result = 0; + uint shift = 0; + while (1) + { + ubyte b = *q++; + result |= cast(_uleb128_t)(b & 0x7F) << shift; + if ((b & 0x80) == 0) + break; + shift += 7; + } + *p = q; + return result; +} + +_sleb128_t s_leb128(const(ubyte)** p) nothrow @nogc +{ + auto q = *p; + ubyte b; + _sleb128_t result = 0; + uint shift = 0; + while (1) + { + b = *q++; + result |= cast(_sleb128_t)(b & 0x7F) << shift; + shift += 7; + if ((b & 0x80) == 0) + break; + } + if (shift < result.sizeof * 8 && (b & 0x40)) + result |= -(cast(_sleb128_t)1 << shift); + *p = q; + return result; +} + +void dwarf_terminate(uint line) nothrow @nogc +{ + import urt.io : writef_to, WriteTarget; + import urt.internal.stdc.stdlib : abort; + writef_to!(WriteTarget.stderr, true)("dwarfeh({0}) fatal error", line); + abort(); +} + +// --- LSDA scanning -------------------------------------------------- + +enum LsdaResult +{ + not_found, + foreign, + corrupt, + no_action, + cleanup, + handler, +} + +ClassInfo get_class_info(_Unwind_Exception* exception_object, const(ubyte)* current_lsd) nothrow @nogc +{ + ExceptionHeader* eh = ExceptionHeader.to_exception_header(exception_object); + Throwable ehobject = eh.object; + for (ExceptionHeader* ehn = eh.next; ehn; ehn = ehn.next) + { + if (current_lsd != ehn.language_specific_data) + break; + Error e = cast(Error) ehobject; + if (e is null || (cast(Error) ehn.object) !is null) + ehobject = ehn.object; + } + return typeid(ehobject); +} + +int action_table_lookup(_Unwind_Exception* exception_object, uint action_record_ptr, const(ubyte)* p_action_table, const(ubyte)* tt, + ubyte ttype, _Unwind_Exception_Class exception_class, const(ubyte)* lsda) nothrow @nogc +{ + ClassInfo thrown_type; + if (exception_class == dmd_exception_class) + thrown_type = get_class_info(exception_object, lsda); + + for (auto ap = p_action_table + action_record_ptr - 1; 1; ) + { + auto type_filter = s_leb128(&ap); + auto apn = ap; + auto next_record_ptr = s_leb128(&ap); + + if (type_filter <= 0) + return -1; + + _Unwind_Ptr entry; + const(ubyte)* tt2; + switch (ttype & DW_EH_PE_FORMAT_MASK) + { + case DW_EH_PE_sdata2: entry = read_unaligned!(short, false)(tt2 = tt - type_filter * 2); break; + case DW_EH_PE_udata2: entry = read_unaligned!(ushort, false)(tt2 = tt - type_filter * 2); break; + case DW_EH_PE_sdata4: entry = read_unaligned!(int, false)(tt2 = tt - type_filter * 4); break; + case DW_EH_PE_udata4: entry = read_unaligned!(uint, false)(tt2 = tt - type_filter * 4); break; + case DW_EH_PE_sdata8: entry = read_unaligned!(long, false)(tt2 = tt - type_filter * 8); break; + case DW_EH_PE_udata8: entry = read_unaligned!(ulong, false)(tt2 = tt - type_filter * 8); break; + case DW_EH_PE_ptr: if (size_t.sizeof == 8) + goto case DW_EH_PE_udata8; + else + goto case DW_EH_PE_udata4; + default: + return -1; + } + if (!entry) + return -1; + + switch (ttype & DW_EH_PE_APPL_MASK) + { + case DW_EH_PE_absptr: + break; + case DW_EH_PE_pcrel: + entry += cast(_Unwind_Ptr) tt2; + break; + default: + return -1; + } + if (ttype & DW_EH_PE_indirect) + entry = *cast(_Unwind_Ptr*) entry; + + ClassInfo ci = cast(ClassInfo) cast(void*) entry; + + // D exception - check class hierarchy + if (exception_class == dmd_exception_class && _d_isbaseof(thrown_type, ci)) + return cast(int) type_filter; + + if (!next_record_ptr) + return 0; + + ap = apn + next_record_ptr; + } + assert(0); // unreachable - all paths return inside the loop +} + +LsdaResult scan_lsda(const(ubyte)* lsda, _Unwind_Ptr ip, _Unwind_Exception_Class exception_class, bool cleanups_only, bool prefer_handler, + _Unwind_Exception* exception_object, out _Unwind_Ptr landing_pad, out int handler) nothrow @nogc +{ + auto p = lsda; + if (!p) + return LsdaResult.no_action; + + _Unwind_Ptr dw_pe_value(ubyte pe) + { + switch (pe) + { + case DW_EH_PE_sdata2: return read_unaligned!(short, true)(p); + case DW_EH_PE_udata2: return read_unaligned!(ushort, true)(p); + case DW_EH_PE_sdata4: return read_unaligned!(int, true)(p); + case DW_EH_PE_udata4: return read_unaligned!(uint, true)(p); + case DW_EH_PE_sdata8: return read_unaligned!(long, true)(p); + case DW_EH_PE_udata8: return read_unaligned!(ulong, true)(p); + case DW_EH_PE_sleb128: return cast(_Unwind_Ptr) s_leb128(&p); + case DW_EH_PE_uleb128: return cast(_Unwind_Ptr) u_leb128(&p); + case DW_EH_PE_ptr: if (size_t.sizeof == 8) + goto case DW_EH_PE_udata8; + else + goto case DW_EH_PE_udata4; + default: + dwarf_terminate(__LINE__); + return 0; + } + } + + ubyte lp_start = *p++; + + _Unwind_Ptr lp_base = 0; + if (lp_start != DW_EH_PE_omit) + lp_base = dw_pe_value(lp_start); + + ubyte ttype = *p++; + _Unwind_Ptr tt_base = 0; + _Unwind_Ptr tt_offset = 0; + if (ttype != DW_EH_PE_omit) + { + tt_base = u_leb128(&p); + tt_offset = (p - lsda) + tt_base; + } + + ubyte call_site_format = *p++; + _Unwind_Ptr call_site_table_size = dw_pe_value(DW_EH_PE_uleb128); + + _Unwind_Ptr ip_offset = ip - lp_base; + bool no_action = false; + auto tt = lsda + tt_offset; + const(ubyte)* p_action_table = p + call_site_table_size; + + while (1) + { + if (p >= p_action_table) + { + if (p == p_action_table) + break; + return LsdaResult.corrupt; + } + + _Unwind_Ptr call_site_start = dw_pe_value(call_site_format); + _Unwind_Ptr call_site_range = dw_pe_value(call_site_format); + _Unwind_Ptr call_site_lp = dw_pe_value(call_site_format); + _uleb128_t action_record_ptr_val = u_leb128(&p); + + if (ip_offset < call_site_start) + break; + + if (ip_offset < call_site_start + call_site_range) + { + if (action_record_ptr_val) + { + if (cleanups_only) + continue; + + auto h = action_table_lookup(exception_object, cast(uint) action_record_ptr_val, + p_action_table, tt, ttype, exception_class, lsda); + if (h < 0) + return LsdaResult.corrupt; + if (h == 0) + continue; + + no_action = false; + landing_pad = call_site_lp; + handler = h; + } + else if (call_site_lp) + { + if (prefer_handler && handler) + continue; + no_action = false; + landing_pad = call_site_lp; + handler = 0; + } + else + no_action = true; + } + } + + if (no_action) + return LsdaResult.no_action; + + if (landing_pad) + return handler ? LsdaResult.handler : LsdaResult.cleanup; + + return LsdaResult.not_found; +} + +// --- Public API ------------------------------------------------------ + +pragma(mangle, catch_mangle) +extern(C) Throwable dwarfeh_begin_catch(_Unwind_Exception* exception_object) nothrow @nogc +{ + version (ARM) version (LDC) + _Unwind_Complete(exception_object); + + ExceptionHeader* eh = ExceptionHeader.to_exception_header(exception_object); + auto o = eh.object; + eh.object = null; + + if (eh != ExceptionHeader.pop()) + dwarf_terminate(__LINE__); + + _Unwind_DeleteException(&eh.exception_object); + return o; +} + +extern(C) void* _d_eh_swapContextDwarf(void* newContext) nothrow @nogc +{ + auto old = ExceptionHeader.stack; + ExceptionHeader.stack = cast(ExceptionHeader*) newContext; + return old; +} + +pragma(mangle, throw_mangle) +extern(C) void dwarfeh_throw(Throwable o) +{ + import urt.io : writeln_err, writef_to, WriteTarget; + import urt.internal.stdc.stdlib : abort; + + ExceptionHeader* eh = ExceptionHeader.create(o); + eh.push(); + + auto refcount = o.refcount(); + if (refcount) + o.refcount() = refcount + 1; + + extern(C) static void exception_cleanup(_Unwind_Reason_Code reason, + _Unwind_Exception* eo) nothrow @nogc + { + switch (reason) + { + case _URC_FOREIGN_EXCEPTION_CAUGHT: + case _URC_NO_REASON: + ExceptionHeader.release(ExceptionHeader.to_exception_header(eo)); + break; + default: + dwarf_terminate(__LINE__); + } + } + + eh.exception_object.exception_cleanup = &exception_cleanup; + _d_createTrace(o, null); + + auto r = _Unwind_RaiseException(&eh.exception_object); + + // Should not return - if it did, the exception was not caught. + dwarfeh_begin_catch(&eh.exception_object); + writeln_err("uncaught exception reached top of stack"); + auto msg = o.msg; + if (msg.length) + writef_to!(WriteTarget.stderr, true)(" {0}", msg); + abort(); +} + +// Common personality implementation. +_Unwind_Reason_Code dwarfeh_personality_common(_Unwind_Action actions, _Unwind_Exception_Class exception_class, _Unwind_Exception* exception_object, _Unwind_Context* context) nothrow @nogc +{ + const(ubyte)* language_specific_data; + int handler; + _Unwind_Ptr landing_pad; + + language_specific_data = cast(const(ubyte)*) _Unwind_GetLanguageSpecificData(context); + auto Start = _Unwind_GetRegionStart(context); + + // Get IP; use _Unwind_GetIPInfo to handle signal frames correctly. + int ip_before_insn; + auto ip = _Unwind_GetIPInfo(context, &ip_before_insn); + if (!ip_before_insn) + --ip; + + auto result = scan_lsda(language_specific_data, ip - Start, exception_class, + (actions & _UA_FORCE_UNWIND) != 0, + (actions & _UA_SEARCH_PHASE) != 0, + exception_object, + landing_pad, + handler); + landing_pad += Start; + + final switch (result) + { + case LsdaResult.not_found: + dwarf_terminate(__LINE__); + assert(0); + + case LsdaResult.foreign: + dwarf_terminate(__LINE__); + assert(0); + + case LsdaResult.corrupt: + dwarf_terminate(__LINE__); + assert(0); + + case LsdaResult.no_action: + return _URC_CONTINUE_UNWIND; + + case LsdaResult.cleanup: + if (actions & _UA_SEARCH_PHASE) + return _URC_CONTINUE_UNWIND; + break; + + case LsdaResult.handler: + if (actions & _UA_SEARCH_PHASE) + { + if (exception_class == dmd_exception_class) + { + auto eh = ExceptionHeader.to_exception_header(exception_object); + eh.handler = handler; + eh.language_specific_data = language_specific_data; + eh.landing_pad = landing_pad; + } + return _URC_HANDLER_FOUND; + } + break; + } + + // Multiple exceptions in flight - chain them + if (exception_class == dmd_exception_class) + { + auto eh = ExceptionHeader.to_exception_header(exception_object); + auto current_lsd = language_specific_data; + bool bypassed = false; + + while (eh.next) + { + ExceptionHeader* ehn = eh.next; + + Error e = cast(Error) eh.object; + if (e !is null && !cast(Error) ehn.object) + { + current_lsd = ehn.language_specific_data; + eh = ehn; + bypassed = true; + continue; + } + + if (current_lsd != ehn.language_specific_data) + break; + + eh.object = Throwable.chainTogether(ehn.object, eh.object); + + if (ehn.handler != handler && !bypassed) + { + handler = ehn.handler; + eh.handler = handler; + eh.language_specific_data = language_specific_data; + eh.landing_pad = landing_pad; + } + + eh.next = ehn.next; + _Unwind_DeleteException(&ehn.exception_object); + } + + if (bypassed) + { + eh = ExceptionHeader.to_exception_header(exception_object); + Error e = cast(Error) eh.object; + auto ehn = eh.next; + e.bypassedException = ehn.object; + eh.next = ehn.next; + _Unwind_DeleteException(&ehn.exception_object); + } + } + + _Unwind_SetGR(context, eh_exception_regno, cast(_Unwind_Word) exception_object); + _Unwind_SetGR(context, eh_selector_regno, handler); + _Unwind_SetIP(context, landing_pad); + + return _URC_INSTALL_CONTEXT; +} + +// Personality function entry points. +// ARM EABI uses a different calling convention than the standard Itanium ABI. +// Bare-metal ARM uses DWARF EH, not ARM EHABI, so use the standard personality. +version (ARM) +{ + version (Beken) + enum UseArmEhabi = true; + else version (BareMetal) + enum UseArmEhabi = false; + else version (LDC) + enum UseArmEhabi = true; + else + enum UseArmEhabi = false; +} +else + enum UseArmEhabi = false; + +static if (UseArmEhabi) +{ + extern(C) _Unwind_Reason_Code _d_eh_personality(_Unwind_State state, _Unwind_Exception* exception_object, _Unwind_Context* context) nothrow @nogc + { + _Unwind_Action actions; + switch (state & _US_ACTION_MASK) + { + case _US_VIRTUAL_UNWIND_FRAME: + actions = _UA_SEARCH_PHASE; + break; + case _US_UNWIND_FRAME_STARTING: + actions = _UA_CLEANUP_PHASE; + break; + case _US_UNWIND_FRAME_RESUME: + return _URC_CONTINUE_UNWIND; + default: + dwarf_terminate(__LINE__); + return _URC_FATAL_PHASE1_ERROR; + } + if (state & _US_FORCE_UNWIND) + actions |= _UA_FORCE_UNWIND; + + return dwarfeh_personality_common(actions, exception_object.exception_class, + exception_object, context); + } +} +else +{ + pragma(mangle, personality_mangle) + extern(C) _Unwind_Reason_Code dwarfeh_personality(int ver, _Unwind_Action actions, _Unwind_Exception_Class exception_class, _Unwind_Exception* exception_object, _Unwind_Context* context) nothrow @nogc + { + if (ver != 1) + return _URC_FATAL_PHASE1_ERROR; + + return dwarfeh_personality_common(actions, exception_class, + exception_object, context); + } +} + +// LDC-only: trivial cleanup hooks (DWARF handles cleanup via personality). +version (LDC) +{ + extern(C) bool _d_enter_cleanup(void* ptr) nothrow @nogc @trusted => true; + extern(C) void _d_leave_cleanup(void* ptr) nothrow @nogc @trusted {} +} + diff --git a/src/urt/internal/exception.d b/src/urt/internal/exception.d index 0d5684f..15b2b11 100644 --- a/src/urt/internal/exception.d +++ b/src/urt/internal/exception.d @@ -1,3621 +1,506 @@ -/** - * D exception handling — throw, catch, finally. - * - * Ported from druntime for use in uRT. - * Two implementations: - * - Win32 (x86): SEH-based, ported from rt/deh_win32.d - * - Win64/POSIX (x86_64): RBP-chain walking, ported from rt/deh_win64_posix.d - * - * Copyright: Digital Mars 1999-2013 (original), uRT authors (port). - * License: Boost Software License 1.0 - */ module urt.internal.exception; -// No top-level gate — individual version blocks guard platform-specific code. - -// ══════════════════════════════════════════════════════════════════════ -// Shared declarations -// ══════════════════════════════════════════════════════════════════════ - -alias ClassInfo = TypeInfo_Class; - -extern(C) int _d_isbaseof(scope ClassInfo oc, scope const ClassInfo c) nothrow @nogc pure @trusted -{ - if (oc is c) - return true; - - do - { - if (oc.base is c) - return true; - - foreach (iface; oc.interfaces) - { - if (iface.classinfo is c || _d_isbaseof(iface.classinfo, c)) - return true; - } - - oc = oc.base; - } - while (oc); - - return false; -} - -// Thread-local trace buffer. _d_createTrace captures here silently; -// terminate() and _d_printLastTrace() read it back for display. -private struct StackTraceData -{ - void*[32] addrs; - ubyte length; -} - -private StackTraceData _tls_trace; // static = TLS in D - -extern(C) void _d_createTrace(Throwable t, void*) nothrow @nogc @trusted -{ - // Capture return addresses into the TLS buffer. No output — - // the trace is only printed on unhandled exceptions (terminate) - // or when explicitly requested via _d_printLastTrace(). - debug - { - _tls_trace.length = 0; - - version (Windows) - { - auto n = rtlCaptureStackBackTrace(2, 32, _tls_trace.addrs.ptr, null); - if (n == 0) - n = cast(ushort) stack_walk64_capture(_tls_trace.addrs); - _tls_trace.length = cast(ubyte) n; - } - else version (D_InlineAsm_X86_64) - { - size_t bp; - asm nothrow @nogc { mov bp, RBP; } - - ubyte n = 0; - foreach (_; 0 .. 32) - { - if (!bp) - break; - auto next_bp = *cast(size_t*) bp; - if (!next_bp || next_bp <= bp) - break; - auto retaddr = *cast(void**)(bp + size_t.sizeof); - if (!retaddr) - break; - _tls_trace.addrs[n++] = retaddr; - bp = next_bp; - } - _tls_trace.length = n; - } - else version (D_InlineAsm_X86) - { - size_t bp; - asm nothrow @nogc { mov bp, EBP; } - - ubyte n = 0; - foreach (_; 0 .. 32) - { - if (!bp) - break; - auto next_bp = *cast(size_t*) bp; - if (!next_bp || next_bp <= bp) - break; - auto retaddr = *cast(void**)(bp + size_t.sizeof); - if (!retaddr) - break; - _tls_trace.addrs[n++] = retaddr; - bp = next_bp; - } - _tls_trace.length = n; - } - else version (FreeStanding) - { - // No stack trace on bare-metal - } - else - { - // ARM, AArch64, RISC-V, etc. — use _Unwind_Backtrace - unwind_backtrace(_tls_trace); - } - } -} - -/// Print the stack trace from the most recent throw on this thread. -/// Safe to call from catch blocks, assert handlers, or anywhere else. -extern(C) void _d_printLastTrace(Throwable t) nothrow @nogc @trusted -{ - debug - { - import urt.io : writeln_err, writef_to, WriteTarget; - - if (_tls_trace.length == 0) - return; - - if (t !is null) - { - auto msg = t.msg; - writef_to!(WriteTarget.stderr, true)("Exception: {0}", msg); - } - - writeln_err(" stack trace:"); - - version (Windows) - dbghelp_print_trace(_tls_trace.addrs[0 .. _tls_trace.length]); - else version (FreeStanding) - {} - else - posix_print_trace(_tls_trace.addrs[0 .. _tls_trace.length]); - } -} - -// ── Stack trace support (Windows, debug only) ──────────────────────── -// -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// TODO: DbgHelp is NOT thread-safe. All calls to SymFromAddr, -// SymGetLineFromAddr64, SymInitialize, and StackWalk64 must be -// serialized with a CRITICAL_SECTION (or equivalent) once this -// program uses threads. Without this, concurrent exceptions from -// multiple threads WILL crash or corrupt DbgHelp's internal state. -// Both DMD and LDC druntime protect all DbgHelp access with a mutex. -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// -version (Windows) debug -{ - // --- DbgHelp types --------------------------------------------------- - - private struct SYMBOL_INFOA - { - uint SizeOfStruct; - uint TypeIndex; - ulong[2] Reserved; - uint Index; - uint Size; - ulong ModBase; - uint Flags; - ulong Value; - ulong Address; - uint Register; - uint Scope; - uint Tag; - uint NameLen; - uint MaxNameLen; - char[1] Name; // variable-length - } - - private struct IMAGEHLP_LINEA64 - { - uint SizeOfStruct; - void* Key; - uint LineNumber; - const(char)* FileName; - ulong Address; - } - - // --- StackWalk64 types ----------------------------------------------- - - private alias HANDLE = void*; - private enum AddrModeFlat = 3; - - private struct ADDRESS64 - { - ulong Offset; - ushort Segment; - uint Mode; - } - - private struct STACKFRAME64 - { - ADDRESS64 AddrPC; - ADDRESS64 AddrReturn; - ADDRESS64 AddrFrame; - ADDRESS64 AddrStack; - ADDRESS64 AddrBStore; - void* FuncTableEntry; - ulong[4] Params; - int Far; - int Virtual; - ulong[3] Reserved; - ubyte[96] KdHelp; // KDHELP64, opaque - } - - // CONTEXT — opaque aligned buffer, accessed via offset constants. - version (Win64) - { - private enum CONTEXT_SIZE = 1232; - private enum CTX_FLAGS_OFF = 48; - private enum CTX_IP_OFF = 248; // Rip - private enum CTX_SP_OFF = 152; // Rsp - private enum CTX_FP_OFF = 160; // Rbp - private enum CTX_FULL = 0x10000B; - private enum MACHINE_TYPE = 0x8664; // IMAGE_FILE_MACHINE_AMD64 - } - else - { - private enum CONTEXT_SIZE = 716; - private enum CTX_FLAGS_OFF = 0; - private enum CTX_IP_OFF = 184; // Eip - private enum CTX_SP_OFF = 196; // Esp - private enum CTX_FP_OFF = 180; // Ebp - private enum CTX_FULL = 0x10007; - private enum MACHINE_TYPE = 0x014C; // IMAGE_FILE_MACHINE_I386 - } - - // --- Function pointer types ------------------------------------------ - - extern (Windows) nothrow @nogc - { - private alias SymInitializeFn = int function(HANDLE, const(char)*, int); - private alias SymSetOptionsFn = uint function(uint); - private alias SymFromAddrFn = int function(HANDLE, ulong, ulong*, SYMBOL_INFOA*); - private alias SymGetLineFromAddr64Fn = int function(HANDLE, ulong, uint*, IMAGEHLP_LINEA64*); - private alias FuncTableAccessFn = void* function(HANDLE, ulong); - private alias GetModuleBaseFn = ulong function(HANDLE, ulong); - private alias StackWalk64Fn = int function( - uint machineType, HANDLE hProcess, HANDLE hThread, - STACKFRAME64* stackFrame, void* contextRecord, - void* readMemory, FuncTableAccessFn funcTableAccess, - GetModuleBaseFn getModuleBase, void* translateAddress); - } - - // --- Globals --------------------------------------------------------- - - private __gshared bool _dbg_inited; - private __gshared bool _dbg_available; - private __gshared HANDLE _dbg_process; - private __gshared SymFromAddrFn _sym_from_addr; - private __gshared SymGetLineFromAddr64Fn _sym_get_line; - private __gshared StackWalk64Fn _stack_walk64; - private __gshared FuncTableAccessFn _func_table_access64; - private __gshared GetModuleBaseFn _get_module_base64; - - // --- Initialization -------------------------------------------------- - - private void dbghelp_init() nothrow @nogc @trusted - { - if (_dbg_inited) - return; - _dbg_inited = true; - - auto hDbg = loadLibraryA("dbghelp.dll"); - if (hDbg is null) - return; - - auto sym_init = cast(SymInitializeFn) getProcAddress(hDbg, "SymInitialize"); - auto sym_set_opt = cast(SymSetOptionsFn) getProcAddress(hDbg, "SymSetOptions"); - _sym_from_addr = cast(SymFromAddrFn) getProcAddress(hDbg, "SymFromAddr"); - _sym_get_line = cast(SymGetLineFromAddr64Fn) getProcAddress(hDbg, "SymGetLineFromAddr64"); - _stack_walk64 = cast(StackWalk64Fn) getProcAddress(hDbg, "StackWalk64"); - _func_table_access64 = cast(FuncTableAccessFn) getProcAddress(hDbg, "SymFunctionTableAccess64"); - _get_module_base64 = cast(GetModuleBaseFn) getProcAddress(hDbg, "SymGetModuleBase64"); - - if (sym_init is null || sym_set_opt is null || _sym_from_addr is null) - return; - - // SYMOPT_DEFERRED_LOAD | SYMOPT_LOAD_LINES - sym_set_opt(0x00000004 | 0x00000010); - - _dbg_process = cast(HANDLE) cast(size_t)-1; // GetCurrentProcess() pseudo-handle - if (!sym_init(_dbg_process, null, 1)) // fInvadeProcess = TRUE - return; - - _dbg_available = true; - } - - // --- Kernel32/ntdll imports ------------------------------------------ - - pragma(mangle, "LoadLibraryA") - extern (Windows) private void* loadLibraryA(const(char)*) nothrow @nogc; - - pragma(mangle, "GetProcAddress") - extern (Windows) private void* getProcAddress(void*, const(char)*) nothrow @nogc; - - pragma(mangle, "RtlCaptureStackBackTrace") - extern (Windows) private ushort rtlCaptureStackBackTrace( - uint FramesToSkip, uint FramesToCapture, void** BackTrace, uint* BackTraceHash) nothrow @nogc; - - pragma(mangle, "RtlCaptureContext") - extern (Windows) private void rtlCaptureContext(void* contextRecord) nothrow @nogc; - - pragma(mangle, "GetCurrentThread") - extern (Windows) private HANDLE getCurrentThread() nothrow @nogc; - - // --- StackWalk64 fallback -------------------------------------------- - - private size_t stack_walk64_capture(ref void*[32] addrs) nothrow @nogc @trusted - { - dbghelp_init(); - - if (_stack_walk64 is null || _func_table_access64 is null || _get_module_base64 is null) - return 0; - - align(16) ubyte[CONTEXT_SIZE] ctx = 0; - *cast(uint*)(ctx.ptr + CTX_FLAGS_OFF) = CTX_FULL; - rtlCaptureContext(ctx.ptr); - - STACKFRAME64 sf; - version (Win64) - { - sf.AddrPC.Offset = *cast(ulong*)(ctx.ptr + CTX_IP_OFF); - sf.AddrFrame.Offset = *cast(ulong*)(ctx.ptr + CTX_FP_OFF); - sf.AddrStack.Offset = *cast(ulong*)(ctx.ptr + CTX_SP_OFF); - } - else - { - sf.AddrPC.Offset = *cast(uint*)(ctx.ptr + CTX_IP_OFF); - sf.AddrFrame.Offset = *cast(uint*)(ctx.ptr + CTX_FP_OFF); - sf.AddrStack.Offset = *cast(uint*)(ctx.ptr + CTX_SP_OFF); - } - sf.AddrPC.Mode = AddrModeFlat; - sf.AddrFrame.Mode = AddrModeFlat; - sf.AddrStack.Mode = AddrModeFlat; - - auto hThread = getCurrentThread(); - size_t n = 0; - - // Skip internal frames (_d_createTrace + stack_walk64_capture + RtlCaptureContext) - uint skip = 3; - - while (n < 32) - { - if (!_stack_walk64(MACHINE_TYPE, _dbg_process, hThread, - &sf, ctx.ptr, null, _func_table_access64, _get_module_base64, null)) - break; - if (sf.AddrPC.Offset == 0) - break; - if (skip > 0) - { - --skip; - continue; - } - addrs[n++] = cast(void*) cast(size_t) sf.AddrPC.Offset; - } - return n; - } - - // --- Symbol resolution & printing ------------------------------------ - - private void dbghelp_print_trace(void*[] addrs) nothrow @nogc @trusted - { - import urt.io : writef_to, writeln_err, WriteTarget; - import urt.mem : strlen; - import urt.string : endsWith; - - dbghelp_init(); - - // Skip frames up through _d_throw_exception (internal machinery). - // LDC druntime does the same — clears accumulated frames when it - // hits _d_throw_exception, so the trace starts at the throw site. - size_t start = 0; - if (_dbg_available) - { - foreach (i, addr; addrs) - { - align(8) ubyte[SYMBOL_INFOA.sizeof + 256] sym_buf = 0; - auto p_sym = cast(SYMBOL_INFOA*) sym_buf.ptr; - p_sym.SizeOfStruct = SYMBOL_INFOA.sizeof; - p_sym.MaxNameLen = 256; - - ulong disp; - if (_sym_from_addr(_dbg_process, cast(ulong) addr, &disp, p_sym)) - { - auto name = p_sym.Name.ptr[0 .. strlen(p_sym.Name.ptr)]; - if (name.endsWith("_d_throw_exception")) - start = i + 1; - } - } - } - - enum addr_fmt = size_t.sizeof == 4 ? "08x" : "016x"; - - foreach (addr; addrs[start .. $]) - { - if (_dbg_available) - { - align(8) ubyte[SYMBOL_INFOA.sizeof + 256] sym_buf = 0; - auto p_sym = cast(SYMBOL_INFOA*) sym_buf.ptr; - p_sym.SizeOfStruct = SYMBOL_INFOA.sizeof; - p_sym.MaxNameLen = 256; - - ulong displacement64; - auto a = cast(ulong) addr; - if (_sym_from_addr(_dbg_process, a, &displacement64, p_sym)) - { - auto name = p_sym.Name.ptr[0 .. strlen(p_sym.Name.ptr)]; - - uint displacement32; - IMAGEHLP_LINEA64 line_info; - line_info.SizeOfStruct = IMAGEHLP_LINEA64.sizeof; - - if (_sym_get_line !is null && - _sym_get_line(_dbg_process, a, &displacement32, &line_info)) - { - auto fname = line_info.FileName[0 .. strlen(line_info.FileName)]; - writef_to!(WriteTarget.stderr, true)(" {0}+0x{1:x} [{2}:{3}]", - name, displacement64, fname, line_info.LineNumber); - } - else - { - writef_to!(WriteTarget.stderr, true)(" {0}+0x{1:x}", name, displacement64); - } - continue; - } - } - - // Fallback: raw address - writef_to!(WriteTarget.stderr, true)(" 0x{0:" ~ addr_fmt ~ "}", cast(size_t)addr); - } - } -} - -// ── Stack trace support (POSIX, debug only) ────────────────────────── -// -// Uses dladdr for symbol names and DWARF .debug_line for file:line info. -// Ported from druntime's core.internal.backtrace.{dwarf,elf} and -// core.internal.elf.{io,dl}. -// -version (Windows) {} else version (FreeStanding) {} else debug -{ - import urt.io : write_err, writeln_err, writef_to, WriteTarget; - import urt.mem : strlen, memcpy; - - import urt.internal.sys.posix; - - private enum SEEK_END = 2; - - // ── _Unwind_Backtrace capture (ARM, AArch64, RISC-V) ──────────── - - version (D_InlineAsm_X86_64) {} else version (D_InlineAsm_X86) {} else - { - private alias _Unwind_Trace_Fn = extern(C) int function(void* ctx, void* data) nothrow @nogc; - - extern(C) private int _Unwind_Backtrace(_Unwind_Trace_Fn, void*) nothrow @nogc; - extern(C) private size_t _Unwind_GetIP(void*) nothrow @nogc; - - private struct UnwindState { StackTraceData* trace; ubyte skip; } - - extern(C) private int unwind_trace_callback(void* ctx, void* data) nothrow @nogc - { - auto s = cast(UnwindState*) data; - if (s.skip > 0) { s.skip--; return 0; } - if (s.trace.length >= 32) return 1; - auto ip = _Unwind_GetIP(ctx); - if (!ip) return 1; - s.trace.addrs[s.trace.length++] = cast(void*) ip; - return 0; - } - - private void unwind_backtrace(ref StackTraceData trace) nothrow @nogc @trusted - { - UnwindState state = UnwindState(&trace, 2); - _Unwind_Backtrace(&unwind_trace_callback, &state); - } - } - - // ── Minimal ELF types ──────────────────────────────────────────── - - version (D_LP64) - { - private struct Elf_Ehdr - { - ubyte[16] e_ident; - ushort e_type; - ushort e_machine; - uint e_version; - ulong e_entry; - ulong e_phoff; - ulong e_shoff; - uint e_flags; - ushort e_ehsize; - ushort e_phentsize; - ushort e_phnum; - ushort e_shentsize; - ushort e_shnum; - ushort e_shstrndx; - } - - private struct Elf_Shdr - { - uint sh_name; - uint sh_type; - ulong sh_flags; - ulong sh_addr; - ulong sh_offset; - ulong sh_size; - uint sh_link; - uint sh_info; - ulong sh_addralign; - ulong sh_entsize; - } - - private struct Elf_Phdr - { - uint p_type; - uint p_flags; - ulong p_offset; - ulong p_vaddr; - ulong p_paddr; - ulong p_filesz; - ulong p_memsz; - ulong p_align; - } - } - else - { - private struct Elf_Ehdr - { - ubyte[16] e_ident; - ushort e_type; - ushort e_machine; - uint e_version; - uint e_entry; - uint e_phoff; - uint e_shoff; - uint e_flags; - ushort e_ehsize; - ushort e_phentsize; - ushort e_phnum; - ushort e_shentsize; - ushort e_shnum; - ushort e_shstrndx; - } - - private struct Elf_Shdr - { - uint sh_name; - uint sh_type; - uint sh_flags; - uint sh_addr; - uint sh_offset; - uint sh_size; - uint sh_link; - uint sh_info; - uint sh_addralign; - uint sh_entsize; - } - - private struct Elf_Phdr - { - uint p_type; - uint p_offset; - uint p_vaddr; - uint p_paddr; - uint p_filesz; - uint p_memsz; - uint p_flags; - uint p_align; - } - } - - private enum EI_MAG0 = 0; - private enum EI_CLASS = 4; - private enum EI_DATA = 5; - private enum ELFMAG = "\x7fELF"; - private enum ET_DYN = 3; - private enum SHF_COMPRESSED = 0x800; - - version (D_LP64) - private enum ELFCLASS_NATIVE = 2; // ELFCLASS64 - else - private enum ELFCLASS_NATIVE = 1; // ELFCLASS32 - - version (LittleEndian) - private enum ELFDATA_NATIVE = 1; // ELFDATA2LSB - else - private enum ELFDATA_NATIVE = 2; // ELFDATA2MSB - - // ── dl_iterate_phdr for base address ───────────────────────────── - - private struct dl_phdr_info - { - size_t dlpi_addr; - const(char)* dlpi_name; - const(Elf_Phdr)* dlpi_phdr; - ushort dlpi_phnum; - } - - private alias dl_iterate_phdr_callback_t = extern(C) int function(dl_phdr_info*, size_t, void*) nothrow @nogc; - - extern(C) private int dl_iterate_phdr(dl_iterate_phdr_callback_t callback, void* data) nothrow @nogc; - - private size_t get_executable_base_address() nothrow @nogc @trusted - { - size_t result = 0; - - extern(C) static int callback(dl_phdr_info* info, size_t, void* data) nothrow @nogc - { - // First entry is the executable itself - *cast(size_t*) data = info.dlpi_addr; - return 1; // stop iteration - } - - dl_iterate_phdr(&callback, &result); - return result; - } - - // ── Memory-mapped file region ──────────────────────────────────── - - private struct MappedRegion - { - const(ubyte)* data; - size_t mapped_size; - - nothrow @nogc @trusted: - - static MappedRegion map(int fd, size_t offset, size_t length) - { - if (fd == -1 || length == 0) - return MappedRegion.init; - - auto pgsz = cast(size_t) sysconf(_SC_PAGE_SIZE); - if (cast(int) pgsz <= 0) - pgsz = 4096; - - const page_off = offset / pgsz; - const diff = offset - page_off * pgsz; - const needed = length + diff; - const pages = (needed + pgsz - 1) / pgsz; - const msize = pages * pgsz; - - auto p = mmap(null, msize, PROT_READ, MAP_PRIVATE, fd, cast(off_t)(page_off * pgsz)); - if (p is MAP_FAILED) - return MappedRegion.init; - - return MappedRegion(cast(const(ubyte)*) p + diff, msize); - } - - void unmap() - { - if (data !is null) - { - auto pgsz = cast(size_t) sysconf(_SC_PAGE_SIZE); - if (cast(int) pgsz <= 0) - pgsz = 4096; - // Align back to page boundary - auto base = cast(void*)(cast(size_t) data & ~(pgsz - 1)); - munmap(base, mapped_size); - data = null; - } - } - } - - // ── ELF self-reader ────────────────────────────────────────────── - - private struct ElfSelf - { - int fd = -1; - MappedRegion ehdr_region; - const(Elf_Ehdr)* ehdr; - - nothrow @nogc @trusted: - - static ElfSelf open() - { - ElfSelf self; - - // Read /proc/self/exe path - char[512] pathbuf = void; - auto n = readlink("/proc/self/exe", pathbuf.ptr, pathbuf.length - 1); - if (n <= 0) - return self; - pathbuf[n] = 0; - - self.fd = .open(pathbuf.ptr, O_RDONLY); - if (self.fd == -1) - return self; - - // Map the ELF header - self.ehdr_region = MappedRegion.map(self.fd, 0, Elf_Ehdr.sizeof); - if (self.ehdr_region.data is null) - { - .close(self.fd); - self.fd = -1; - return self; - } - - self.ehdr = cast(const(Elf_Ehdr)*) self.ehdr_region.data; - - // Validate ELF magic, class, and byte order - if (self.ehdr.e_ident[0..4] != cast(const(ubyte)[4]) ELFMAG - || self.ehdr.e_ident[EI_CLASS] != ELFCLASS_NATIVE - || self.ehdr.e_ident[EI_DATA] != ELFDATA_NATIVE) - { - self.close(); - return ElfSelf.init; - } - - return self; - } - - void close() - { - ehdr_region.unmap(); - ehdr = null; - if (fd != -1) { .close(fd); fd = -1; } - } - - bool valid() const { return fd != -1 && ehdr !is null; } - - /// Find a section by name, return its offset and size. - bool find_section(const(char)[] name, out size_t offset, out size_t size) - { - if (!valid()) - return false; - - // Map section headers - auto shdr_total = cast(size_t) ehdr.e_shnum * Elf_Shdr.sizeof; - auto shdr_region = MappedRegion.map(fd, cast(size_t) ehdr.e_shoff, shdr_total); - if (shdr_region.data is null) - return false; - scope(exit) shdr_region.unmap(); - - auto shdrs = (cast(const(Elf_Shdr)*) shdr_region.data)[0 .. ehdr.e_shnum]; - - // Map string table section - if (ehdr.e_shstrndx >= ehdr.e_shnum) - return false; - auto strtab_shdr = &shdrs[ehdr.e_shstrndx]; - auto strtab_region = MappedRegion.map(fd, - cast(size_t) strtab_shdr.sh_offset, - cast(size_t) strtab_shdr.sh_size); - if (strtab_region.data is null) - return false; - scope(exit) strtab_region.unmap(); - - auto strtab = cast(const(char)*) strtab_region.data; - - // Search for the named section - foreach (ref shdr; shdrs) - { - if (shdr.sh_name >= strtab_shdr.sh_size) - continue; - auto sec_name = strtab + shdr.sh_name; - auto sec_name_len = strlen(sec_name); - if (sec_name_len == name.length && sec_name[0 .. sec_name_len] == name) - { - if (shdr.sh_flags & SHF_COMPRESSED) - return false; // compressed debug sections not supported - offset = cast(size_t) shdr.sh_offset; - size = cast(size_t) shdr.sh_size; - return true; - } - } - return false; - } - } - - // ── DWARF .debug_line types and constants ──────────────────────── - - private struct LocationInfo - { - int file = -1; - int line = -1; - } - - private struct SourceFile - { - const(char)[] file; - size_t dir_index; // 1-based - } - - private struct LineNumberProgram - { - ulong unit_length; - ushort dwarf_version; - ubyte address_size; - ubyte segment_selector_size; - ulong header_length; - ubyte minimum_instruction_length; - ubyte maximum_operations_per_instruction; - bool default_is_statement; - byte line_base; - ubyte line_range; - ubyte opcode_base; - const(ubyte)[] standard_opcode_lengths; - // Directory and file tables stored as slices into scratch buffers. - // The caller owns the scratch; the LineNumberProgram just borrows. - const(char)[][] include_directories; - size_t num_dirs; - SourceFile[] source_files; - size_t num_files; - const(ubyte)[] program; - } - - private struct StateMachine - { - const(void)* address; - uint operation_index = 0; - uint file_index = 1; - int line = 1; - uint column = 0; - bool is_statement; - bool is_end_sequence = false; - } - - private enum StandardOpcode : ubyte - { - extended_op = 0, - copy = 1, - advance_pc = 2, - advance_line = 3, - set_file = 4, - set_column = 5, - negate_statement = 6, - set_basic_block = 7, - const_add_pc = 8, - fixed_advance_pc = 9, - set_prologue_end = 10, - set_epilogue_begin = 11, - set_isa = 12, - } - - private enum ExtendedOpcode : ubyte - { - end_sequence = 1, - set_address = 2, - define_file = 3, - set_discriminator = 4, - } - - // ── LEB128 and DWARF helpers ───────────────────────────────────── - - private T dw_read(T)(ref const(ubyte)[] buf) nothrow @nogc @trusted - { - if (buf.length < T.sizeof) - return T.init; - version (X86_64) - T result = *cast(const(T)*) buf.ptr; - else version (X86) - T result = *cast(const(T)*) buf.ptr; - else - { - T result = void; - memcpy(&result, buf.ptr, T.sizeof); - } - buf = buf[T.sizeof .. $]; - return result; - } - - private const(char)[] dw_read_stringz(ref const(ubyte)[] buf) nothrow @nogc @trusted - { - auto p = cast(const(char)*) buf.ptr; - auto len = strlen(p); - buf = buf[len + 1 .. $]; - return p[0 .. len]; - } - - private ulong dw_read_uleb128(ref const(ubyte)[] buf) nothrow @nogc - { - ulong val = 0; - uint shift = 0; - while (buf.length > 0) - { - ubyte b = buf[0]; buf = buf[1 .. $]; - val |= cast(ulong)(b & 0x7f) << shift; - if ((b & 0x80) == 0) break; - shift += 7; - } - return val; - } - - private long dw_read_sleb128(ref const(ubyte)[] buf) nothrow @nogc - { - long val = 0; - uint shift = 0; - ubyte b; - while (buf.length > 0) - { - b = buf[0]; buf = buf[1 .. $]; - val |= cast(long)(b & 0x7f) << shift; - shift += 7; - if ((b & 0x80) == 0) break; - } - if (shift < 64 && (b & 0x40) != 0) - val |= -(cast(long) 1 << shift); - return val; - } - - // ── DWARF v5 entry format ──────────────────────────────────────── - - private enum DW_LNCT : ushort - { - path = 1, - directory_index = 2, - } - - private enum DW_FORM : ubyte - { - data1 = 11, - data2 = 5, - data4 = 6, - data8 = 7, - data16 = 30, - string_ = 8, - strp = 14, - line_strp = 31, - udata = 15, - block = 9, - strx = 26, - strx1 = 37, - strx2 = 38, - strx3 = 39, - strx4 = 40, - sec_offset = 23, - sdata = 13, - flag = 12, - flag_present = 25, - } - - private struct EntryFormatPair - { - DW_LNCT type; - DW_FORM form; - } - - /// Skip a DWARF form value we don't care about. - private void dw_skip_form(ref const(ubyte)[] data, DW_FORM form, bool is64bit) nothrow @nogc - { - with (DW_FORM) switch (form) - { - case strp, line_strp, sec_offset: - data = data[is64bit ? 8 : 4 .. $]; break; - case data1, strx1, flag, flag_present: - data = data[1 .. $]; break; - case data2, strx2: - data = data[2 .. $]; break; - case strx3: - data = data[3 .. $]; break; - case data4, strx4: - data = data[4 .. $]; break; - case data8: - data = data[8 .. $]; break; - case data16: - data = data[16 .. $]; break; - case udata, strx, sdata: - dw_read_uleb128(data); break; - case block: - auto length = cast(size_t) dw_read_uleb128(data); - data = data[length .. $]; break; - default: - break; - } - } - - // ── Read DWARF line number program header ──────────────────────── - - // Scratch buffers allocated on the stack for directory/file tables. - // 256 entries each should be more than enough for any compilation unit. - private enum MAX_DIRS = 256; - private enum MAX_FILES = 512; - - private LineNumberProgram dw_read_line_number_program(ref const(ubyte)[] data) nothrow @nogc @trusted - { - const original_data = data; - LineNumberProgram lp; - - bool is_64bit_dwarf = false; - lp.unit_length = dw_read!uint(data); - if (lp.unit_length == uint.max) - { - is_64bit_dwarf = true; - lp.unit_length = dw_read!ulong(data); - } - - const version_field_offset = cast(size_t)(data.ptr - original_data.ptr); - lp.dwarf_version = dw_read!ushort(data); - - if (lp.dwarf_version >= 5) - { - lp.address_size = dw_read!ubyte(data); - lp.segment_selector_size = dw_read!ubyte(data); - } - - lp.header_length = is_64bit_dwarf ? dw_read!ulong(data) : dw_read!uint(data); - - const min_insn_field_offset = cast(size_t)(data.ptr - original_data.ptr); - lp.minimum_instruction_length = dw_read!ubyte(data); - lp.maximum_operations_per_instruction = (lp.dwarf_version >= 4) ? dw_read!ubyte(data) : 1; - lp.default_is_statement = (dw_read!ubyte(data) != 0); - lp.line_base = dw_read!byte(data); - lp.line_range = dw_read!ubyte(data); - lp.opcode_base = dw_read!ubyte(data); - - lp.standard_opcode_lengths = data[0 .. lp.opcode_base - 1]; - data = data[lp.opcode_base - 1 .. $]; - - if (lp.dwarf_version >= 5) - { - // DWARF v5: directory format + entries - auto num_pairs = dw_read!ubyte(data); - EntryFormatPair[8] dir_fmt = void; - foreach (i; 0 .. num_pairs) - { - if (i < 8) - { - dir_fmt[i].type = cast(DW_LNCT) dw_read_uleb128(data); - dir_fmt[i].form = cast(DW_FORM) dw_read_uleb128(data); - } - } - - lp.num_dirs = cast(size_t) dw_read_uleb128(data); - // Caller must provide scratch buffers; we use __gshared static for simplicity. - foreach (d; 0 .. lp.num_dirs) - { - foreach (p; 0 .. num_pairs) - { - if (p < 8 && dir_fmt[p].type == DW_LNCT.path && dir_fmt[p].form == DW_FORM.string_) - { - if (d < MAX_DIRS) - _dir_scratch[d] = dw_read_stringz(data); - else - dw_read_stringz(data); - } - else if (p < 8) - dw_skip_form(data, dir_fmt[p].form, is_64bit_dwarf); - } - } - if (lp.num_dirs > MAX_DIRS) lp.num_dirs = MAX_DIRS; - lp.include_directories = _dir_scratch[0 .. lp.num_dirs]; - - // File format + entries - num_pairs = dw_read!ubyte(data); - EntryFormatPair[8] file_fmt = void; - foreach (i; 0 .. num_pairs) - { - if (i < 8) - { - file_fmt[i].type = cast(DW_LNCT) dw_read_uleb128(data); - file_fmt[i].form = cast(DW_FORM) dw_read_uleb128(data); - } - } - - lp.num_files = cast(size_t) dw_read_uleb128(data); - foreach (f; 0 .. lp.num_files) - { - SourceFile sf; - sf.file = ""; - foreach (p; 0 .. num_pairs) - { - if (p < 8 && file_fmt[p].type == DW_LNCT.path && file_fmt[p].form == DW_FORM.string_) - sf.file = dw_read_stringz(data); - else if (p < 8 && file_fmt[p].type == DW_LNCT.directory_index) - { - if (file_fmt[p].form == DW_FORM.data1) - sf.dir_index = dw_read!ubyte(data); - else if (file_fmt[p].form == DW_FORM.data2) - sf.dir_index = dw_read!ushort(data); - else if (file_fmt[p].form == DW_FORM.udata) - sf.dir_index = cast(size_t) dw_read_uleb128(data); - else - dw_skip_form(data, file_fmt[p].form, is_64bit_dwarf); - sf.dir_index++; // DWARF v5 indices are 0-based, normalize to 1-based - } - else if (p < 8) - dw_skip_form(data, file_fmt[p].form, is_64bit_dwarf); - } - if (f < MAX_FILES) - _file_scratch[f] = sf; - } - if (lp.num_files > MAX_FILES) lp.num_files = MAX_FILES; - lp.source_files = _file_scratch[0 .. lp.num_files]; - } - else - { - // DWARF v3/v4: NUL-terminated sequences - lp.num_dirs = 0; - while (data.length > 0 && data[0] != 0) - { - auto dir = dw_read_stringz(data); - if (lp.num_dirs < MAX_DIRS) - _dir_scratch[lp.num_dirs++] = dir; - } - if (data.length > 0) data = data[1 .. $]; // skip NUL terminator - lp.include_directories = _dir_scratch[0 .. lp.num_dirs]; - - lp.num_files = 0; - while (data.length > 0 && data[0] != 0) - { - SourceFile sf; - sf.file = dw_read_stringz(data); - sf.dir_index = cast(size_t) dw_read_uleb128(data); - dw_read_uleb128(data); // last modification time - dw_read_uleb128(data); // file length - if (lp.num_files < MAX_FILES) - _file_scratch[lp.num_files++] = sf; - } - if (data.length > 0) data = data[1 .. $]; // skip NUL terminator - lp.source_files = _file_scratch[0 .. lp.num_files]; - } - - const program_start = cast(size_t)(min_insn_field_offset + lp.header_length); - const program_end = cast(size_t)(version_field_offset + lp.unit_length); - if (program_start <= original_data.length && program_end <= original_data.length) - lp.program = original_data[program_start .. program_end]; - - data = (program_end <= original_data.length) ? original_data[program_end .. $] : null; - - return lp; - } - - // Static scratch buffers for DWARF parsing (debug-only, no allocator needed). - private __gshared const(char)[][MAX_DIRS] _dir_scratch; - private __gshared SourceFile[MAX_FILES] _file_scratch; - - // ── DWARF state machine — resolve addresses to file:line ───────── - - private struct ResolvedLocation - { - const(char)[] file; - const(char)[] dir; - int line = -1; - } - - /// Resolve an array of addresses to file:line using .debug_line data. - private void dw_resolve_addresses( - const(ubyte)[] debug_line_data, - void*[] addresses, - ResolvedLocation[] results, - size_t base_address) nothrow @nogc @trusted - { - size_t found = 0; - const num_addrs = addresses.length; - - while (debug_line_data.length > 0 && found < num_addrs) - { - auto lp = dw_read_line_number_program(debug_line_data); - if (lp.program.length == 0) - break; - - StateMachine machine; - machine.is_statement = lp.default_is_statement; - - LocationInfo last_loc = LocationInfo(-1, -1); - const(void)* last_address; - - const(ubyte)[] prog = lp.program; - while (prog.length > 0) - { - size_t advance_addr(size_t op_advance) - { - const inc = lp.minimum_instruction_length * - ((machine.operation_index + op_advance) / lp.maximum_operations_per_instruction); - machine.address += inc; - machine.operation_index = - (machine.operation_index + op_advance) % lp.maximum_operations_per_instruction; - return inc; - } - - void emit_row(bool is_end) - { - auto addr = machine.address + base_address; - - foreach (idx; 0 .. num_addrs) - { - if (results[idx].line != -1) - continue; - auto target = addresses[idx]; - - void apply_loc(LocationInfo loc) - { - auto file_idx = loc.file - (lp.dwarf_version < 5 ? 1 : 0); - if (file_idx >= 0 && file_idx < lp.num_files) - { - results[idx].file = lp.source_files[file_idx].file; - auto di = lp.source_files[file_idx].dir_index; - if (di > 0 && di <= lp.num_dirs) - results[idx].dir = lp.include_directories[di - 1]; - } - results[idx].line = loc.line; - found++; - } - - if (target == addr) - apply_loc(LocationInfo(machine.file_index, machine.line)); - else if (last_address !is null && target > last_address && target < addr) - apply_loc(last_loc); - } - - if (is_end) - last_address = null; - else - { - last_address = addr; - last_loc = LocationInfo(machine.file_index, machine.line); - } - } - - ubyte opcode = prog[0]; prog = prog[1 .. $]; - - if (opcode >= lp.opcode_base) - { - // Special opcode - opcode -= lp.opcode_base; - advance_addr(opcode / lp.line_range); - machine.line += lp.line_base + (opcode % lp.line_range); - emit_row(false); - } - else if (opcode == 0) - { - // Extended opcode - auto len = cast(size_t) dw_read_uleb128(prog); - if (prog.length == 0) break; - ubyte eopcode = prog[0]; prog = prog[1 .. $]; - - switch (eopcode) - { - case ExtendedOpcode.end_sequence: - machine.is_end_sequence = true; - emit_row(true); - machine = StateMachine.init; - machine.is_statement = lp.default_is_statement; - break; - case ExtendedOpcode.set_address: - machine.address = dw_read!(const(void)*)(prog); - machine.operation_index = 0; - break; - case ExtendedOpcode.set_discriminator: - dw_read_uleb128(prog); - break; - default: - if (len > 1) - prog = prog[len - 1 .. $]; - break; - } - } - else switch (opcode) with (StandardOpcode) - { - case copy: - emit_row(false); - break; - case advance_pc: - advance_addr(cast(size_t) dw_read_uleb128(prog)); - break; - case advance_line: - machine.line += cast(int) dw_read_sleb128(prog); - break; - case set_file: - machine.file_index = cast(uint) dw_read_uleb128(prog); - break; - case set_column: - machine.column = cast(uint) dw_read_uleb128(prog); - break; - case negate_statement: - machine.is_statement = !machine.is_statement; - break; - case set_basic_block: - break; - case const_add_pc: - advance_addr((255 - lp.opcode_base) / lp.line_range); - break; - case fixed_advance_pc: - machine.address += dw_read!ushort(prog); - machine.operation_index = 0; - break; - case set_prologue_end: - case set_epilogue_begin: - break; - case set_isa: - dw_read_uleb128(prog); - break; - default: - // Unknown standard opcode: skip according to standard_opcode_lengths - if (opcode > 0 && opcode <= lp.standard_opcode_lengths.length) - { - foreach (_; 0 .. lp.standard_opcode_lengths[opcode - 1]) - dw_read_uleb128(prog); - } - break; - } - } - } - } - - // ── Minimal D symbol demangler ───────────────────────────────────── - // - // Extracts the dot-separated qualified name from a D mangled symbol. - // Does not decode type signatures — just returns e.g. "module.Class.func". - - private const(char)[] demangle_symbol( - const(char)[] mangled, return ref char[512] buf) nothrow @nogc @trusted - { - import urt.array : beginsWith; - import urt.conv : parse_uint; - - if (mangled.length < 3 || !mangled.beginsWith("_D")) - return mangled; - - auto src = mangled[2 .. $]; - size_t pos = 0; - bool first = true; - - while (src.length > 0) - { - auto ch = src[0]; - - if (ch >= '1' && ch <= '9') - { - // LName: decimal length followed by that many characters - size_t taken; - size_t len = cast(size_t)parse_uint(src, &taken); - src = src[taken .. $]; - if (len > src.length || pos + len + 1 > buf.length) - break; - - if (!first) - buf[pos++] = '.'; - first = false; - - buf[pos .. pos + len] = src[0 .. len]; - pos += len; - src = src[len .. $]; - } - else if (ch == 'Q') - { - // Back reference: base-26 offset pointing to an earlier LName. - auto q_pos = cast(size_t)(src.ptr - mangled.ptr); - src = src[1 .. $]; - - size_t ref_val = 0; - while (src.length > 0 && src[0] >= 'A' && src[0] <= 'Z') - { - ref_val = ref_val * 26 + (src[0] - 'A'); - src = src[1 .. $]; - } - if (src.length > 0 && src[0] >= 'a' && src[0] <= 'z') - { - ref_val = ref_val * 26 + (src[0] - 'a'); - src = src[1 .. $]; - } - else - break; // malformed - - if (ref_val >= q_pos) - break; - auto target = mangled[q_pos - ref_val .. $]; - if (target.length == 0 || target[0] < '1' || target[0] > '9') - break; - - // Parse LName at target - size_t taken; - size_t len = cast(size_t)parse_uint(target, &taken); - target = target[taken .. $]; - if (len > target.length || pos + len + 1 > buf.length) - break; - - if (!first) - buf[pos++] = '.'; - first = false; - - buf[pos .. pos + len] = target[0 .. len]; - pos += len; - } - else if (ch == '_' && src.length >= 3 && src[1] == '_' - && (src[2] == 'T' || src[2] == 'U')) - { - // Template instance __T/__U: extract name, skip args until Z - src = src[3 .. $]; - - if (src.length > 0 && src[0] >= '1' && src[0] <= '9') - { - size_t taken; - size_t len = cast(size_t)parse_uint(src, &taken); - src = src[taken .. $]; - if (len <= src.length && pos + len + 1 <= buf.length) - { - if (!first) - buf[pos++] = '.'; - first = false; - - buf[pos .. pos + len] = src[0 .. len]; - pos += len; - src = src[len .. $]; - } - } - - // Skip template args until matching Z - int depth = 1; - while (src.length > 0 && depth > 0) - { - if (src[0] == 'Z') - --depth; - else if (src.length >= 3 && src[0] == '_' && src[1] == '_' - && (src[2] == 'T' || src[2] == 'U')) - { - ++depth; - src = src[2 .. $]; - } - src = src[1 .. $]; - } - } - else if (ch == '0') - src = src[1 .. $]; // anonymous — skip - else - break; // type signature — done - } - - if (pos == 0) - return mangled; - - // Append $TypeSignature if there's anything left - if (src.length > 0 && pos + 1 + src.length <= buf.length) - { - buf[pos++] = '$'; - buf[pos .. pos + src.length] = src[]; - pos += src.length; - } - - return buf[0 .. pos]; - } - - // ── Print formatted trace ──────────────────────────────────────── - - private void posix_print_trace(void*[] addrs) nothrow @nogc @trusted - { - if (addrs.length == 0) - return; - - // Skip internal frames (find _d_throw_exception / _d_throwdwarf) - import urt.string : endsWith; - size_t start = 0; - foreach (i, addr; addrs) - { - Dl_info info = void; - if (dladdr(addr, &info) && info.dli_sname !is null) - { - auto sym = info.dli_sname[0 .. strlen(info.dli_sname)]; - if (sym.endsWith("_d_throw_exception") || sym.endsWith("_d_throwdwarf")) - start = i + 1; - } - } - - // Try DWARF .debug_line resolution - ResolvedLocation[32] locations; - foreach (ref loc; locations) - loc = ResolvedLocation.init; - - auto elf = ElfSelf.open(); - scope(exit) elf.close(); - - if (elf.valid()) - { - size_t dbg_offset, dbg_size; - if (elf.find_section(".debug_line", dbg_offset, dbg_size)) - { - auto dbg_region = MappedRegion.map(elf.fd, dbg_offset, dbg_size); - if (dbg_region.data !is null) - { - scope(exit) dbg_region.unmap(); - - auto base_addr = (elf.ehdr.e_type == ET_DYN) - ? get_executable_base_address() : cast(size_t) 0; - - auto dbg_data = dbg_region.data[0 .. dbg_size]; - dw_resolve_addresses(dbg_data, - addrs[0 .. addrs.length], - locations[0 .. addrs.length], - base_addr); - } - } - } - - // Print each frame - foreach (i; start .. addrs.length) - { - auto addr = addrs[i]; - ref loc = locations[i]; - - // Symbol name via dladdr - Dl_info info = void; - const(char)* symname = null; - size_t sym_offset = 0; - if (dladdr(addr, &info) && info.dli_sname !is null) - { - symname = info.dli_sname; - sym_offset = cast(size_t) addr - cast(size_t) info.dli_saddr; - } - - // Format: file:line symbol+0xoffset [0xaddress] - // or: ??:? symbol+0xoffset [0xaddress] - // or: ??:? 0xaddress - - if (loc.line >= 0) - { - // Have file:line - if (loc.dir.length > 0 && loc.dir[$ - 1] != '/') - writef_to!(WriteTarget.stderr, false)(" {0}/{1}:{2}", loc.dir, loc.file, loc.line); - else if (loc.dir.length > 0) - writef_to!(WriteTarget.stderr, false)(" {0}{1}:{2}", loc.dir, loc.file, loc.line); - else - writef_to!(WriteTarget.stderr, false)(" {0}:{1}", loc.file, loc.line); - } - else - write_err(" ??:?"); - - if (symname !is null) - { - auto sym = symname[0 .. strlen(symname)]; - char[512] dbuf = void; - auto demangled = demangle_symbol(sym, dbuf); - writef_to!(WriteTarget.stderr, false)(" {0}+0x{1:x}", demangled, sym_offset); - } - - enum addr_fmt = size_t.sizeof == 4 ? "08x" : "016x"; - writef_to!(WriteTarget.stderr, true)(" [0x{0:" ~ addr_fmt ~ "}]", cast(size_t)addr); - - // Stop at _Dmain - if (symname !is null) - { - auto sym = symname[0 .. strlen(symname)]; - if (sym == "_Dmain") - break; - } - } - } -} // version (!Windows) debug - -private void terminate() nothrow @nogc @trusted -{ - import urt.io : writeln_err; - writeln_err("Unhandled exception -- no catch handler found, terminating."); - - debug - { - if (_tls_trace.length > 0) - { - writeln_err(" stack trace:"); - version (Windows) - dbghelp_print_trace(_tls_trace.addrs[0 .. _tls_trace.length]); - else version (FreeStanding) - {} - else - posix_print_trace(_tls_trace.addrs[0 .. _tls_trace.length]); - } - } - - version (D_InlineAsm_X86_64) - asm nothrow @nogc { hlt; } - else version (D_InlineAsm_X86) - asm nothrow @nogc { hlt; } - else - { - import urt.internal.stdc.stdlib : abort; - abort(); - } -} - -// ══════════════════════════════════════════════════════════════════════ -// Platform-specific implementations -// ══════════════════════════════════════════════════════════════════════ - -version (GDC) -{ - static assert(false, "!!"); -} -else version (LDC) -{ - -// ────────────────────────────────────────────────────────────────────── -// LDC MSVC SEH exception handling -// -// LDC on Windows uses the MSVC C++ exception infrastructure. -// _d_throw_exception builds MSVC-compatible type metadata and calls -// RaiseException(). The MSVC CRT's table-based SEH unwinds the stack -// and delivers exceptions to catch landing pads, which call -// _d_eh_enter_catch to extract the D Throwable from the SEH record. -// -// Ported from ldc/eh_msvc.d in LDC's druntime. -// ────────────────────────────────────────────────────────────────────── - -version (Windows) -{ - -// --- Windows ABI types --------------------------------------------------- - -private alias DWORD = uint; -private alias ULONG_PTR = size_t; - -extern (Windows) void RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, - DWORD nNumberOfArguments, ULONG_PTR* lpArguments) nothrow; - -// --- MSVC SEH structures ------------------------------------------------- - -// On Win64, type info pointers are 32-bit offsets from a heap base. -version (Win64) - private struct ImgPtr(T) { uint offset; } -else - private alias ImgPtr(T) = T*; - -private alias PMFN = ImgPtr!(void function(void*)); - -private struct TypeDescriptor -{ - uint hash; - void* spare; - char[1] name; // variable-size, zero-terminated -} - -private struct PMD -{ - int mdisp; - int pdisp; - int vdisp; -} - -private struct CatchableType -{ - uint properties; - ImgPtr!TypeDescriptor pType; - PMD thisDisplacement; - int sizeOrOffset; - PMFN copyFunction; -} - -private enum CT_IsSimpleType = 0x00000001; - -private struct CatchableTypeArray -{ - int nCatchableTypes; - ImgPtr!CatchableType[1] arrayOfCatchableTypes; // variable size -} - -private struct _ThrowInfo -{ - uint attributes; - PMFN pmfnUnwind; - PMFN pForwardCompat; - ImgPtr!CatchableTypeArray pCatchableTypeArray; -} - -private struct CxxExceptionInfo -{ - size_t Magic; - Throwable* pThrowable; - _ThrowInfo* ThrowInfo; - version (Win64) void* ImgBase; -} - -private enum int STATUS_MSC_EXCEPTION = 0xe0000000 | ('m' << 16) | ('s' << 8) | ('c' << 0); -private enum EXCEPTION_NONCONTINUABLE = 0x01; -private enum EH_MAGIC_NUMBER1 = 0x19930520; - - -// --- EH Heap (image-relative pointer support) ---------------------------- - -version (Win64) -{ - private struct EHHeap - { - void* base; - size_t capacity; - size_t length; - - void initialize(size_t initial_capacity) nothrow @nogc - { - import urt.mem : alloc; - base = alloc(initial_capacity).ptr; - capacity = initial_capacity; - length = size_t.sizeof; // offset 0 reserved (null sentinel) - } - - size_t alloc(size_t size) nothrow @nogc - { - import urt.mem : alloc, memcpy; - auto offset = length; - enum align_mask = size_t.sizeof - 1; - auto new_length = (length + size + align_mask) & ~align_mask; - auto new_capacity = capacity; - while (new_length > new_capacity) - new_capacity *= 2; - if (new_capacity != capacity) - { - auto new_base = alloc(new_capacity).ptr; - memcpy(new_base, base, length); - // Old base leaks — may be referenced by in-flight exceptions. - base = new_base; - capacity = new_capacity; - } - length = new_length; - return offset; - } - } - - private __gshared EHHeap _eh_heap; - - private ImgPtr!T eh_malloc(T)(size_t size = T.sizeof) nothrow @nogc - { - return ImgPtr!T(cast(uint) _eh_heap.alloc(size)); - } - - private T* to_pointer(T)(ImgPtr!T img_ptr) nothrow @nogc - { - return cast(T*)(cast(ubyte*) _eh_heap.base + img_ptr.offset); - } -} -else // Win32 -{ - private ImgPtr!T eh_malloc(T)(size_t size = T.sizeof) nothrow @nogc - { - import urt.mem : alloc; - return cast(T*)alloc(size).ptr; - } - - private T* to_pointer(T)(T* img_ptr) nothrow @nogc - { - return img_ptr; - } -} - - -// --- ThrowInfo cache (simple linear-scan arrays) ------------------------- -// No mutex — OpenWatt's exception paths are single-threaded. - -private enum EH_CACHE_SIZE = 64; - -private __gshared ClassInfo[EH_CACHE_SIZE] _throw_info_keys; -private __gshared ImgPtr!_ThrowInfo[EH_CACHE_SIZE] _throw_info_vals; -private __gshared size_t _throw_info_len; - -private __gshared ClassInfo[EH_CACHE_SIZE] _catchable_keys; -private __gshared ImgPtr!CatchableType[EH_CACHE_SIZE] _catchable_vals; -private __gshared size_t _catchable_len; - -private __gshared bool _eh_initialized; - -private void ensure_eh_init() nothrow @nogc -{ - if (_eh_initialized) - return; - version (Win64) - _eh_heap.initialize(0x10000); - _eh_initialized = true; -} - - -// --- ThrowInfo generation ------------------------------------------------ - -private ImgPtr!CatchableType get_catchable_type(ClassInfo ti) nothrow @nogc -{ - import urt.mem : memcpy; - - foreach (i; 0 .. _catchable_len) - if (_catchable_keys[i] is ti) - return _catchable_vals[i]; - - const sz = TypeDescriptor.sizeof + ti.name.length + 1; - auto td = eh_malloc!TypeDescriptor(sz); - auto ptd = td.to_pointer; - - ptd.hash = 0; - ptd.spare = null; - ptd.name.ptr[0] = 'D'; - memcpy(ptd.name.ptr + 1, ti.name.ptr, ti.name.length); - ptd.name.ptr[ti.name.length + 1] = 0; - - auto ct = eh_malloc!CatchableType(); - ct.to_pointer[0] = CatchableType( - CT_IsSimpleType, td, PMD(0, -1, 0), - cast(int) size_t.sizeof, PMFN.init); - - if (_catchable_len < EH_CACHE_SIZE) - { - _catchable_keys[_catchable_len] = ti; - _catchable_vals[_catchable_len] = ct; - ++_catchable_len; - } - return ct; -} - -private ImgPtr!_ThrowInfo get_throw_info(ClassInfo ti) nothrow @nogc -{ - foreach (i; 0 .. _throw_info_len) - if (_throw_info_keys[i] is ti) - return _throw_info_vals[i]; - - int classes = 0; - for (ClassInfo tic = ti; tic !is null; tic = tic.base) - ++classes; - - const arr_size = int.sizeof + classes * ImgPtr!(CatchableType).sizeof; - ImgPtr!CatchableTypeArray cta = eh_malloc!CatchableTypeArray(arr_size); - to_pointer(cta).nCatchableTypes = classes; - - size_t c = 0; - for (ClassInfo tic = ti; tic !is null; tic = tic.base) - cta.to_pointer.arrayOfCatchableTypes.ptr[c++] = get_catchable_type(tic); - - auto tinf = eh_malloc!_ThrowInfo(); - *(tinf.to_pointer) = _ThrowInfo(0, PMFN.init, PMFN.init, cta); - - if (_throw_info_len < EH_CACHE_SIZE) - { - _throw_info_keys[_throw_info_len] = ti; - _throw_info_vals[_throw_info_len] = tinf; - ++_throw_info_len; - } - return tinf; -} - - -// --- Exception stack (thread-local) -------------------------------------- - -private struct ExceptionStack -{ -nothrow @nogc: - - void push(Throwable e) - { - if (_length == _cap) - grow(); - _p[_length++] = e; - } - - Throwable pop() - { - return _p[--_length]; - } - - void shrink(size_t sz) - { - while (_length > sz) - _p[--_length] = null; - } - - ref inout(Throwable) opIndex(size_t idx) inout - { - return _p[idx]; - } - - size_t find(Throwable e) - { - for (size_t i = _length; i > 0;) - if (_p[--i] is e) - return i; - return ~cast(size_t) 0; - } - - @property size_t length() const { return _length; } - -private: - - void grow() - { - import urt.mem : alloc, free, memcpy; - immutable ncap = _cap ? 2 * _cap : 16; - auto p = cast(Throwable*)alloc(ncap * size_t.sizeof).ptr; - if (_length > 0) - memcpy(p, _p, _length * size_t.sizeof); - if (_p !is null) - free((cast(void*)_p)[0 .. _cap * size_t.sizeof]); - _p = p; - _cap = ncap; - } - - size_t _length; - Throwable* _p; - size_t _cap; -} - -private ExceptionStack _exception_stack; - - -// --- Exception chaining -------------------------------------------------- - -private Throwable chain_exceptions(Throwable e, Throwable t) nothrow @nogc -{ - if (!cast(Error) e) - if (auto err = cast(Error) t) - { - err.bypassedException = e; - return err; - } - return Throwable.chainTogether(e, t); -} - - -// --- Core SEH API -------------------------------------------------------- - -extern (C) void _d_throw_exception(Throwable throwable) -{ - if (throwable is null || typeid(throwable) is null) - { - terminate(); - assert(0); - } - - auto refcount = throwable.refcount(); - if (refcount) - throwable.refcount() = refcount + 1; - - ensure_eh_init(); - - _exception_stack.push(throwable); - _d_createTrace(throwable, null); - - CxxExceptionInfo info; - info.Magic = EH_MAGIC_NUMBER1; - info.pThrowable = &throwable; - info.ThrowInfo = get_throw_info(typeid(throwable)).to_pointer; - version (Win64) - info.ImgBase = _eh_heap.base; - - RaiseException(STATUS_MSC_EXCEPTION, EXCEPTION_NONCONTINUABLE, - info.sizeof / size_t.sizeof, cast(ULONG_PTR*) &info); -} - -extern (C) Throwable _d_eh_enter_catch(void* ptr, ClassInfo catch_type) -{ - if (ptr is null) - return null; - - auto e = *(cast(Throwable*) ptr); - size_t pos = _exception_stack.find(e); - if (pos >= _exception_stack.length()) - return null; // not a D exception - - auto caught = e; - - // Chain inner unhandled exceptions as collateral - for (size_t p = pos + 1; p < _exception_stack.length(); ++p) - e = chain_exceptions(e, _exception_stack[p]); - _exception_stack.shrink(pos); - - if (e !is caught) - { - if (_d_isbaseof(typeid(e), catch_type)) - *cast(Throwable*) ptr = e; - else - _d_throw_exception(e); // rethrow collateral - } - return e; -} - -extern (C) bool _d_enter_cleanup(void* ptr) nothrow @nogc @trusted -{ - // Prevents LLVM from optimizing away cleanup (finally) blocks. - return true; -} - -extern (C) void _d_leave_cleanup(void* ptr) nothrow @nogc @trusted -{ -} - -} // version (Windows) -else -{ -// Non-Windows LDC: DWARF EH is in the shared block below. -} // else (non-Windows LDC) - -} -else version (Win32) // DMD Win32 -{ - -// ────────────────────────────────────────────────────────────────────── -// Win32 SEH-based exception handling -// ────────────────────────────────────────────────────────────────────── - -// Windows types (inlined to avoid core.sys.windows dependency) -alias DWORD = uint; -alias BYTE = ubyte; -alias PVOID = void*; -alias ULONG_PTR = size_t; - -enum size_t EXCEPTION_MAXIMUM_PARAMETERS = 15; -enum DWORD EXCEPTION_NONCONTINUABLE = 1; -enum EXCEPTION_UNWIND = 6; -enum EXCEPTION_COLLATERAL = 0x100; - -enum DWORD STATUS_DIGITAL_MARS_D_EXCEPTION = - (3 << 30) | (1 << 29) | (0 << 28) | ('D' << 16) | 1; - -struct EXCEPTION_RECORD -{ - DWORD ExceptionCode; - DWORD ExceptionFlags; - EXCEPTION_RECORD* ExceptionRecord; - PVOID ExceptionAddress; - DWORD NumberParameters; - ULONG_PTR[EXCEPTION_MAXIMUM_PARAMETERS] ExceptionInformation; -} - -enum MAXIMUM_SUPPORTED_EXTENSION = 512; - -struct FLOATING_SAVE_AREA -{ - DWORD ControlWord, StatusWord, TagWord; - DWORD ErrorOffset, ErrorSelector; - DWORD DataOffset, DataSelector; - BYTE[80] RegisterArea; - DWORD Cr0NpxState; -} - -struct CONTEXT -{ - DWORD ContextFlags; - DWORD Dr0, Dr1, Dr2, Dr3, Dr6, Dr7; - FLOATING_SAVE_AREA FloatSave; - DWORD SegGs, SegFs, SegEs, SegDs; - DWORD Edi, Esi, Ebx, Edx, Ecx, Eax; - DWORD Ebp, Eip, SegCs, EFlags, Esp, SegSs; - BYTE[MAXIMUM_SUPPORTED_EXTENSION] ExtendedRegisters; -} - -struct EXCEPTION_POINTERS -{ - EXCEPTION_RECORD* ExceptionRecord; - CONTEXT* ContextRecord; -} - -enum EXCEPTION_DISPOSITION -{ - ExceptionContinueExecution, - ExceptionContinueSearch, - ExceptionNestedException, - ExceptionCollidedUnwind, -} - -alias LanguageSpecificHandler = extern(C) - EXCEPTION_DISPOSITION function( - EXCEPTION_RECORD* exceptionRecord, - DEstablisherFrame* frame, - CONTEXT* context, - void* dispatcherContext); - -extern (Windows) void RaiseException(DWORD, DWORD, DWORD, void*); -extern (Windows) void RtlUnwind(void* targetFrame, void* targetIp, - EXCEPTION_RECORD* pExceptRec, void* valueForEAX); +version (Windows) + import sys.windows.exception : _capture_trace, _caller_address, _resolve_address, _resolve_batch; +else version (Espressif) + import sys.esp32.exception : _capture_trace, _caller_address, _resolve_address, _resolve_batch; +else version (BareMetal) + import sys.baremetal.exception : _capture_trace, _caller_address, _resolve_address, _resolve_batch; +else + import sys.posix.exception : _capture_trace, _caller_address, _resolve_address, _resolve_batch; -extern(C) extern __gshared DWORD _except_list; // FS:[0] +nothrow @nogc: -// Data structures — compiler-generated exception handler tables (Win32) -struct DEstablisherFrame -{ - DEstablisherFrame* prev; - LanguageSpecificHandler handler; - DWORD table_index; - DWORD ebp; -} +// Public API -struct DHandlerInfo +// Resolved symbol information for a single return address. Fields are +// best-effort - any of them may be empty/zero if the driver can't +// supply them (no on-device symtab, stripped binary, missing DWARF). +// `name` is the raw symbol as the driver sees it - possibly D-mangled +// (`_D...`); pass through `demangle_symbol` before display. +// String slices are owned by driver static/TLS storage - copy before +// the next `_resolve_address`/`_resolve_batch` call. +struct Resolved { - int prev_index; - uint cioffset; // offset to DCatchInfo data from start of table - void* finally_code; // pointer to finally code (!=null if try-finally) + const(char)[] name; + const(char)[] file; + const(char)[] dir; + uint line; + size_t offset; // addr - symbol_base } -struct DHandlerTable +pragma(inline, false) +size_t capture_trace(void*[] addrs) @trusted { - void* fptr; // pointer to start of function - uint espoffset; - uint retoffset; - DHandlerInfo[1] handler_info; + return _capture_trace(addrs); } -struct DCatchBlock +pragma(inline, false) +void* caller_address(uint skip = 0) @trusted { - ClassInfo type; - uint bpoffset; // EBP offset of catch var - void* code; // catch handler code pointer + return _caller_address(skip); } -struct DCatchInfo +bool resolve_address(void* addr, out Resolved r) @trusted { - uint ncatches; - DCatchBlock[1] catch_block; + return _resolve_address(addr, r); } -// InFlight exception list (per-stack, swapped on fiber switches) - -EXCEPTION_RECORD* inflight_exception_list = null; - -extern(C) void* _d_eh_swapContext(void* newContext) nothrow @nogc +bool resolve_batch(const(void*)[] addrs, Resolved[] results) @trusted { - auto old = inflight_exception_list; - inflight_exception_list = cast(EXCEPTION_RECORD*) newContext; - return old; + assert(addrs.length == results.length); + return _resolve_batch(addrs, results); } -private EXCEPTION_RECORD* skip_collateral_exceptions(EXCEPTION_RECORD* n) nothrow @nogc @trusted +version (Tiny) { - while (n.ExceptionRecord && n.ExceptionFlags & EXCEPTION_COLLATERAL) - n = n.ExceptionRecord; - return n; + const(char)[] demangle_symbol(const(char)[] mangled, char[]) @trusted + => mangled; } - -// SEH to D exception translation - -private Throwable _d_translate_se_to_d_exception( - EXCEPTION_RECORD* exceptionRecord, CONTEXT*) nothrow @nogc @trusted +else { - if (exceptionRecord.ExceptionCode == STATUS_DIGITAL_MARS_D_EXCEPTION) - return cast(Throwable) cast(void*)(exceptionRecord.ExceptionInformation[0]); + const(char)[] demangle_symbol(const(char)[] mangled, char[] buf) @trusted + { + import urt.array : beginsWith; + import urt.conv : parse_uint; + import urt.mem : memcpy; - // Non-D (hardware) exceptions: cannot create Error objects without GC. - terminate(); - assert(0); -} + if (mangled.length < 3 || !mangled.beginsWith("_D")) + return mangled; -// _d_throwc — throw a D exception via Windows SEH + auto src = mangled[2 .. $]; + size_t pos = 0; + bool first = true; -private void throw_impl(Throwable h) @trusted -{ - auto refcount = h.refcount(); - if (refcount) - h.refcount() = refcount + 1; + while (src.length > 0) + { + auto ch = src[0]; - _d_createTrace(h, null); - RaiseException(STATUS_DIGITAL_MARS_D_EXCEPTION, - EXCEPTION_NONCONTINUABLE, 1, cast(void*)&h); -} + if (ch >= '1' && ch <= '9') + { + // LName: decimal length followed by that many characters + size_t taken; + size_t len = cast(size_t) parse_uint(src, &taken); + src = src[taken .. $]; + if (len > src.length || pos + len + 1 > buf.length) + break; -extern(C) void _d_throwc(Throwable h) @trusted -{ - version (D_InlineAsm_X86) - asm - { - naked; - enter 0, 0; - mov EAX, [EBP + 8]; - call throw_impl; - leave; - ret; - } -} + if (!first) + buf[pos++] = '.'; + first = false; -// _d_framehandler — SEH frame handler called by OS for each frame. -// The handler table address is passed in EAX by compiler-generated thunks. + buf[pos .. pos + len] = src[0 .. len]; + pos += len; + src = src[len .. $]; + } + else if (ch == 'Q') + { + // Back reference: base-26 offset pointing to an earlier LName. + auto q_pos = cast(size_t)(src.ptr - mangled.ptr); + src = src[1 .. $]; -extern(C) EXCEPTION_DISPOSITION _d_framehandler( - EXCEPTION_RECORD* exceptionRecord, - DEstablisherFrame* frame, - CONTEXT* context, - void* dispatcherContext) @trusted -{ - DHandlerTable* handler_table; - asm { mov handler_table, EAX; } + size_t ref_val = 0; + while (src.length > 0 && src[0] >= 'A' && src[0] <= 'Z') + { + ref_val = ref_val * 26 + (src[0] - 'A'); + src = src[1 .. $]; + } + if (src.length > 0 && src[0] >= 'a' && src[0] <= 'z') + { + ref_val = ref_val * 26 + (src[0] - 'a'); + src = src[1 .. $]; + } + else + break; // malformed - if (exceptionRecord.ExceptionFlags & EXCEPTION_UNWIND) - { - // Unwind phase: call all finally blocks in this frame - _d_local_unwind(handler_table, frame, -1, &unwindCollisionExceptionHandler); - } - else - { - // Search phase: look for a matching catch handler + if (ref_val >= q_pos) + break; + auto target = mangled[q_pos - ref_val .. $]; + if (target.length == 0 || target[0] < '1' || target[0] > '9') + break; - EXCEPTION_RECORD* master = null; - ClassInfo master_class_info = null; + size_t taken; + size_t len = cast(size_t) parse_uint(target, &taken); + target = target[taken .. $]; + if (len > target.length || pos + len + 1 > buf.length) + break; - int prev_ndx; - for (auto ndx = frame.table_index; ndx != -1; ndx = prev_ndx) - { - auto phi = &handler_table.handler_info.ptr[ndx]; - prev_ndx = phi.prev_index; + if (!first) + buf[pos++] = '.'; + first = false; - if (phi.cioffset) + buf[pos .. pos + len] = target[0 .. len]; + pos += len; + } + else if (ch == '_' && src.length >= 3 && src[1] == '_' && (src[2] == 'T' || src[2] == 'U')) { - auto pci = cast(DCatchInfo*)(cast(ubyte*) handler_table + phi.cioffset); - auto ncatches = pci.ncatches; + // Template instance __T/__U: extract name, skip args until Z + src = src[3 .. $]; - foreach (i; 0 .. ncatches) + if (src.length > 0 && src[0] >= '1' && src[0] <= '9') { - auto pcb = &pci.catch_block.ptr[i]; - - // Walk the collateral exception chain to find the master - EXCEPTION_RECORD* er = exceptionRecord; - master = null; - master_class_info = null; - - for (;;) + size_t taken; + size_t len = cast(size_t) parse_uint(src, &taken); + src = src[taken .. $]; + if (len <= src.length && pos + len + 1 <= buf.length) { - if (er.ExceptionCode == STATUS_DIGITAL_MARS_D_EXCEPTION) - { - ClassInfo ci = (**(cast(ClassInfo**)(er.ExceptionInformation[0]))); - if (!master && !(er.ExceptionFlags & EXCEPTION_COLLATERAL)) - { - master = er; - master_class_info = ci; - break; - } - if (_d_isbaseof(ci, typeid(Error))) - { - master = er; - master_class_info = ci; - } - } - else - { - // Non-D exception — cannot translate without GC - terminate(); - } - - if (!(er.ExceptionFlags & EXCEPTION_COLLATERAL)) - break; - - if (er.ExceptionRecord) - er = er.ExceptionRecord; - else - er = inflight_exception_list; + if (!first) + buf[pos++] = '.'; + first = false; + + buf[pos .. pos + len] = src[0 .. len]; + pos += len; + src = src[len .. $]; } + } - if (_d_isbaseof(master_class_info, pcb.type)) + int depth = 1; + while (src.length > 0 && depth > 0) + { + if (src[0] == 'Z') + --depth; + else if (src.length >= 3 && src[0] == '_' && src[1] == '_' && (src[2] == 'T' || src[2] == 'U')) { - // Found matching catch handler - - auto original_exception = skip_collateral_exceptions(exceptionRecord); - if (original_exception.ExceptionRecord is null - && !(exceptionRecord is inflight_exception_list)) - original_exception.ExceptionRecord = inflight_exception_list; - inflight_exception_list = exceptionRecord; - - // Global unwind: call finally blocks in intervening frames - _d_global_unwind(frame, exceptionRecord); - - // Local unwind: call finally blocks skipped in this frame - _d_local_unwind(handler_table, frame, ndx, - &searchCollisionExceptionHandler); - - frame.table_index = prev_ndx; - - // Build D exception chain from SEH records - EXCEPTION_RECORD* z = exceptionRecord; - Throwable prev = null; - Error master_error = null; - - for (;;) - { - Throwable w = _d_translate_se_to_d_exception(z, context); - if (z == master && (z.ExceptionFlags & EXCEPTION_COLLATERAL)) - master_error = cast(Error) w; - prev = Throwable.chainTogether(w, prev); - if (!(z.ExceptionFlags & EXCEPTION_COLLATERAL)) - break; - z = z.ExceptionRecord; - } - - Throwable pti; - if (master_error) - { - master_error.bypassedException = prev; - pti = master_error; - } - else - pti = prev; - - inflight_exception_list = z.ExceptionRecord; - - // Initialize catch variable and jump to handler - int regebp = cast(int)&frame.ebp; - *cast(Object*)(regebp + pcb.bpoffset) = pti; - - { - uint catch_esp; - alias fp_t = void function(); - fp_t catch_addr = cast(fp_t) pcb.code; - catch_esp = regebp - handler_table.espoffset - fp_t.sizeof; - asm - { - mov EAX, catch_esp; - mov ECX, catch_addr; - mov [EAX], ECX; - mov EBP, regebp; - mov ESP, EAX; - ret; - } - } + ++depth; + src = src[2 .. $]; } + src = src[1 .. $]; } } + else if (ch == '0') + src = src[1 .. $]; // anonymous - skip + else + break; // type signature - done } - } - return EXCEPTION_DISPOSITION.ExceptionContinueSearch; -} - -// Exception filter for __try/__except around Dmain - -extern(C) int _d_exception_filter(EXCEPTION_POINTERS* eptrs, - int retval, Object* exception_object) @trusted -{ - *exception_object = _d_translate_se_to_d_exception( - eptrs.ExceptionRecord, eptrs.ContextRecord); - return retval; -} - -// Collision exception handlers - -extern(C) EXCEPTION_DISPOSITION searchCollisionExceptionHandler( - EXCEPTION_RECORD* exceptionRecord, - DEstablisherFrame*, - CONTEXT*, - void* dispatcherContext) @trusted -{ - if (!(exceptionRecord.ExceptionFlags & EXCEPTION_UNWIND)) - { - auto n = skip_collateral_exceptions(exceptionRecord); - n.ExceptionFlags |= EXCEPTION_COLLATERAL; - return EXCEPTION_DISPOSITION.ExceptionContinueSearch; - } - // Collision during SEARCH phase — restart from 'frame' - *(cast(void**) dispatcherContext) = dispatcherContext; // frame - return EXCEPTION_DISPOSITION.ExceptionCollidedUnwind; -} - -extern(C) EXCEPTION_DISPOSITION unwindCollisionExceptionHandler( - EXCEPTION_RECORD* exceptionRecord, - DEstablisherFrame* frame, - CONTEXT*, - void* dispatcherContext) @trusted -{ - if (!(exceptionRecord.ExceptionFlags & EXCEPTION_UNWIND)) - { - auto n = skip_collateral_exceptions(exceptionRecord); - n.ExceptionFlags |= EXCEPTION_COLLATERAL; - return EXCEPTION_DISPOSITION.ExceptionContinueSearch; - } - // Collision during UNWIND phase — restart from 'frame.prev' - *(cast(void**) dispatcherContext) = frame.prev; - return EXCEPTION_DISPOSITION.ExceptionCollidedUnwind; -} - -// Local unwind — run finally blocks in the current frame - -extern(C) void _d_local_unwind(DHandlerTable* handler_table, - DEstablisherFrame* frame, int stop_index, - LanguageSpecificHandler collision_handler) @trusted -{ - // Install collision handler on SEH chain - asm - { - push dword ptr -1; - push dword ptr 0; - push collision_handler; - push dword ptr FS:_except_list; - mov FS:_except_list, ESP; - } - - for (auto i = frame.table_index; i != -1 && i != stop_index;) - { - auto phi = &handler_table.handler_info.ptr[i]; - i = phi.prev_index; - - if (phi.finally_code) - { - auto catch_ebp = &frame.ebp; - auto blockaddr = phi.finally_code; - asm - { - push EBX; - mov EBX, blockaddr; - push EBP; - mov EBP, catch_ebp; - call EBX; - pop EBP; - pop EBX; - } - } - } - - // Remove collision handler from SEH chain - asm - { - pop FS:_except_list; - add ESP, 12; - } -} - -// Global unwind — thin wrapper around RtlUnwind - -extern(C) int _d_global_unwind(DEstablisherFrame* pFrame, - EXCEPTION_RECORD* eRecord) @trusted -{ - asm - { - naked; - push EBP; - mov EBP, ESP; - push ECX; - push EBX; - push ESI; - push EDI; - push EBP; - push 0; - push dword ptr 12[EBP]; // eRecord - call __system_unwind; - jmp __unwind_exit; - __system_unwind: - push dword ptr 8[EBP]; // pFrame - call RtlUnwind; - __unwind_exit: - pop EBP; - pop EDI; - pop ESI; - pop EBX; - pop ECX; - mov ESP, EBP; - pop EBP; - ret; - } -} - -// Local unwind for goto/return across finally blocks - -extern(C) void _d_local_unwind2() @trusted -{ - asm - { - naked; - jmp _d_localUnwindForGoto; - } -} - -extern(C) void _d_localUnwindForGoto(DHandlerTable* handler_table, - DEstablisherFrame* frame, int stop_index) @trusted -{ - _d_local_unwind(handler_table, frame, stop_index, - &searchCollisionExceptionHandler); -} - -// Monitor handler stubs (for synchronized blocks) - -extern(C) EXCEPTION_DISPOSITION _d_monitor_handler( - EXCEPTION_RECORD* exceptionRecord, - DEstablisherFrame*, - CONTEXT*, - void*) @trusted -{ - if (exceptionRecord.ExceptionFlags & EXCEPTION_UNWIND) - { - // TODO: _d_monitorexit(cast(Object)cast(void*)frame.table_index); - } - return EXCEPTION_DISPOSITION.ExceptionContinueSearch; -} - -extern(C) void _d_monitor_prolog(void*, void*, Object) @trusted -{ - // TODO: _d_monitorenter(h); -} - -extern(C) void _d_monitor_epilog(void*, void*, Object) @trusted -{ - // TODO: _d_monitorexit(h); -} - - -} -else version (Win64) -{ - -// ────────────────────────────────────────────────────────────────────── -// Win64 — RBP-chain walking with ._deh section tables -// ────────────────────────────────────────────────────────────────────── - -// Data structures — compiler-generated exception handler tables - -struct DHandlerInfo -{ - uint offset; // offset from function address to start of guarded section - uint endoffset; // offset of end of guarded section - int prev_index; // previous table index - uint cioffset; // offset to DCatchInfo data from start of table (!=0 if try-catch) - size_t finally_offset; // offset to finally code to execute (!=0 if try-finally) -} - -struct DHandlerTable -{ - uint espoffset; // offset of ESP from EBP - uint retoffset; // offset from start of function to return code - size_t nhandlers; // dimension of handler_info[] - DHandlerInfo[1] handler_info; -} - -struct DCatchBlock -{ - ClassInfo type; // catch type - size_t bpoffset; // EBP offset of catch var - size_t codeoffset; // catch handler offset -} - -struct DCatchInfo -{ - size_t ncatches; // number of catch blocks - DCatchBlock[1] catch_block; -} - -struct FuncTable -{ - void* fptr; // pointer to start of function - DHandlerTable* handlertable; - uint fsize; // size of function in bytes -} - -// InFlight exception tracking (per-stack, swapped on fiber switches) - -private struct InFlight -{ - InFlight* next; - void* addr; - Throwable t; -} - -private __gshared InFlight* __inflight = null; - -extern(C) void* _d_eh_swapContext(void* newContext) nothrow @nogc -{ - auto old = __inflight; - __inflight = cast(InFlight*) newContext; - return old; -} - -// DEH section scanning — find exception handler tables in the binary - -version (Windows) - extern(C) extern __gshared ubyte __ImageBase; - -private __gshared immutable(FuncTable)* _deh_start; -private __gshared immutable(FuncTable)* _deh_end; - -private void ensure_deh_loaded() nothrow @nogc @trusted -{ - if (_deh_start !is null) - return; - - version (Windows) - { - auto section = find_pe_section(cast(void*) &__ImageBase, "._deh\0\0\0"); - if (section.length) - { - _deh_start = cast(immutable(FuncTable)*) section.ptr; - _deh_end = cast(immutable(FuncTable)*)(section.ptr + section.length); - } - } - else version (linux) - { - // TODO: ELF section scanning for .deh - } -} - -/// PE section lookup — duplicated from urt.package to avoid import cycle. -private void[] find_pe_section(void* imageBase, string name) nothrow @nogc @trusted -{ - if (name.length > 8) return null; - - auto base = cast(ubyte*) imageBase; - if (base[0] != 0x4D || base[1] != 0x5A) - return null; - - auto lfanew = *cast(int*)(base + 0x3C); - auto pe = base + lfanew; - if (pe[0] != 'P' || pe[1] != 'E' || pe[2] != 0 || pe[3] != 0) - return null; - - auto fileHeader = pe + 4; - ushort numSections = *cast(ushort*)(fileHeader + 2); - ushort optHeaderSize = *cast(ushort*)(fileHeader + 16); - auto sections = fileHeader + 20 + optHeaderSize; - - foreach (i; 0 .. numSections) - { - auto sec = sections + i * 40; - auto secName = (cast(char*) sec)[0 .. 8]; - bool match = true; - foreach (j; 0 .. 8) - { - if (secName[j] != name[j]) - { - match = false; - break; - } - } - if (match) - { - auto virtualSize = *cast(uint*)(sec + 8); - auto virtualAddress = *cast(uint*)(sec + 12); - return (base + virtualAddress)[0 .. virtualSize]; - } - } - return null; -} - -// Handler table lookup - -immutable(FuncTable)* __eh_finddata(void* address) nothrow @nogc @trusted -{ - ensure_deh_loaded(); - if (_deh_start is null) - return null; - return __eh_finddata_range(address, _deh_start, _deh_end); -} - -immutable(FuncTable)* __eh_finddata_range(void* address, - immutable(FuncTable)* pstart, immutable(FuncTable)* pend) nothrow @nogc @trusted -{ - for (auto ft = pstart; ; ft++) - { - Lagain: - if (ft >= pend) - break; - version (Win64) - { - // MS Linker sometimes inserts zero padding between .obj sections - if (ft.fptr == null) - { - ft = cast(immutable(FuncTable)*)(cast(void**) ft + 1); - goto Lagain; - } - } + if (pos == 0) + return mangled; - immutable(void)* fptr = ft.fptr; - version (Win64) + // Append $TypeSignature if there's anything left + if (src.length > 0 && pos + 1 + src.length <= buf.length) { - // Follow JMP indirection from /DEBUG linker - if ((cast(ubyte*) fptr)[0] == 0xE9) - fptr = fptr + 5 + *cast(int*)(fptr + 1); + buf[pos++] = '$'; + buf[pos .. pos + src.length] = src[]; + pos += src.length; } - if (fptr <= address && address < cast(void*)(cast(char*) fptr + ft.fsize)) - return ft; + return buf[0 .. pos]; } - return null; } -// Stack frame walking - -size_t __eh_find_caller(size_t regbp, size_t* pretaddr) nothrow @nogc @trusted +// Print a captured trace to stderr. +// +// formats each frame as: `{dir}/{file}:{line} {name}+0x{offset} [0x{address}]` +// with graceful degradation: +// file:line missing → `??:?` +// symbol missing → drop the `name+0x...` component +// nothing resolved → ` 0x{address}` only +// Stops after emitting `_Dmain` / `main` to hide C runtime tail noise. +version (Tiny) { - size_t bp = *cast(size_t*) regbp; - - if (bp) + void print_trace(const(void*)[] addrs) @trusted { - // Stack grows downward — new BP must be above old - if (bp <= regbp) - terminate(); + import urt.io : writef_to, WriteTarget; - *pretaddr = *cast(size_t*)(regbp + size_t.sizeof); + enum addr_fmt = size_t.sizeof == 4 ? "08x" : "016x"; + foreach (addr; addrs) + writef_to!(WriteTarget.stderr, true)(" 0x{0:" ~ addr_fmt ~ "}", cast(size_t) addr); } - return bp; -} - -// _d_throwc — the core throw implementation (RBP-chain walking) - -alias fp_t = int function(); - -extern(C) void _d_throwc(Throwable h) @trusted +} +else { - size_t regebp; - - version (D_InlineAsm_X86) - asm { mov regebp, EBP; } - else version (D_InlineAsm_X86_64) - asm { mov regebp, RBP; } - - // Increment reference count if refcounted - auto refcount = h.refcount(); - if (refcount) - h.refcount() = refcount + 1; + void print_trace(const(void*)[] addrs) @trusted + { + import urt.io : write_err, writef_to, WriteTarget; + import urt.string : endsWith; - _d_createTrace(h, null); + if (addrs.length == 0) + return; - while (1) - { - size_t retaddr; + const n = addrs.length > max_frames ? max_frames : addrs.length; - regebp = __eh_find_caller(regebp, &retaddr); - if (!regebp) - break; + Resolved[max_frames] results; + const have_symbols = _resolve_batch(addrs[0 .. n], results[0 .. n]); - auto func_table = __eh_finddata(cast(void*) retaddr); - auto handler_table = func_table ? func_table.handlertable : null; - if (!handler_table) - continue; + enum addr_fmt = size_t.sizeof == 4 ? "08x" : "016x"; - auto funcoffset = cast(size_t) func_table.fptr; - version (Win64) + if (!have_symbols) { - // Follow JMP indirection from /DEBUG linker - if ((cast(ubyte*) funcoffset)[0] == 0xE9) - funcoffset = funcoffset + 5 + *cast(int*)(funcoffset + 1); + foreach (addr; addrs[0 .. n]) + writef_to!(WriteTarget.stderr, true)(" 0x{0:" ~ addr_fmt ~ "}", cast(size_t) addr); + return; } - // Find start index for retaddr in handler table - auto dim = handler_table.nhandlers; - auto index = -1; - for (uint i = 0; i < dim; i++) + // Skip internal throw machinery - start after the last matching frame. Matches LDC druntime behavior. + size_t start = 0; + foreach (i; 0 .. n) { - auto phi = &handler_table.handler_info.ptr[i]; - if (retaddr > funcoffset + phi.offset && - retaddr <= funcoffset + phi.endoffset) - index = i; + auto name = results[i].name; + if (name.endsWith("_d_throw_exception") || name.endsWith("_d_throwdwarf")) + start = i + 1; } - // Handle inflight exception chaining - if (dim) + foreach (i; start .. n) { - auto phi = &handler_table.handler_info.ptr[index + 1]; - auto prev = cast(InFlight*) &__inflight; - auto curr = prev.next; + auto addr = addrs[i]; + auto r = &results[i]; + const bool have_any = r.name.length > 0 || r.line > 0; - if (curr !is null && curr.addr == cast(void*)(funcoffset + phi.finally_offset)) + if (!have_any) { - auto e = cast(Error) h; - if (e !is null && (cast(Error) curr.t) is null) - { - e.bypassedException = curr.t; - prev.next = curr.next; - } - else - { - h = Throwable.chainTogether(curr.t, h); - prev.next = curr.next; - } + writef_to!(WriteTarget.stderr, true)(" 0x{0:" ~ addr_fmt ~ "}", cast(size_t) addr); + continue; } - } - // Walk handler table entries - int prev_ndx; - for (auto ndx = index; ndx != -1; ndx = prev_ndx) - { - auto phi = &handler_table.handler_info.ptr[ndx]; - prev_ndx = phi.prev_index; - - if (phi.cioffset) + // file:line (or ??:? when missing) + if (r.line > 0 && r.file.length > 0) { - // Catch handler - auto pci = cast(DCatchInfo*)(cast(char*) handler_table + phi.cioffset); - auto ncatches = pci.ncatches; - - for (uint i = 0; i < ncatches; i++) + if (r.dir.length > 0) { - auto ci = **cast(ClassInfo**) h; - auto pcb = &pci.catch_block.ptr[i]; - - if (_d_isbaseof(ci, pcb.type)) - { - // Initialize catch variable - *cast(void**)(regebp + pcb.bpoffset) = cast(void*) h; - - // Jump to catch block — does not return - { - size_t catch_esp; - fp_t catch_addr; - - catch_addr = cast(fp_t)(funcoffset + pcb.codeoffset); - catch_esp = regebp - handler_table.espoffset - fp_t.sizeof; - - version (D_InlineAsm_X86) - asm - { - mov EAX, catch_esp; - mov ECX, catch_addr; - mov [EAX], ECX; - mov EBP, regebp; - mov ESP, EAX; - ret; - } - else version (D_InlineAsm_X86_64) - asm - { - mov RAX, catch_esp; - mov RCX, catch_esp; - mov RCX, catch_addr; - mov [RAX], RCX; - mov RBP, regebp; - mov RSP, RAX; - ret; - } - } - } + const sep = (r.dir[$ - 1] == '/' || r.dir[$ - 1] == '\\') ? "" : "/"; + writef_to!(WriteTarget.stderr, false)(" {0}{1}{2}:{3}", r.dir, sep, r.file, r.line); } + else + writef_to!(WriteTarget.stderr, false)(" {0}:{1}", r.file, r.line); } - else if (phi.finally_offset) - { - // Finally block - auto blockaddr = cast(void*)(funcoffset + phi.finally_offset); - InFlight inflight; - - inflight.addr = blockaddr; - inflight.next = __inflight; - inflight.t = h; - __inflight = &inflight; - - version (D_InlineAsm_X86) - asm - { - push EBX; - mov EBX, blockaddr; - push EBP; - mov EBP, regebp; - call EBX; - pop EBP; - pop EBX; - } - else version (D_InlineAsm_X86_64) - asm - { - sub RSP, 8; - push RBX; - mov RBX, blockaddr; - push RBP; - mov RBP, regebp; - call RBX; - pop RBP; - pop RBX; - add RSP, 8; - } + else + write_err(" ??:?"); - if (__inflight is &inflight) - __inflight = __inflight.next; + // symbol+offset (demangled) + if (r.name.length > 0) + { + char[512] dbuf = void; + auto dname = demangle_symbol(r.name, dbuf); + writef_to!(WriteTarget.stderr, false)(" {0}+0x{1:x}", dname, r.offset); } - } - } - terminate(); -} - -} // version (Win64) -// Shared DWARF block — not part of the version chain above. -// Compiles on all non-Windows platforms (DMD Linux, LDC everywhere). -version (Windows) {} else -{ - -// ────────────────────────────────────────────────────────────────────── -// DWARF exception handling (shared by DMD and LDC) -// -// Uses the GCC/DWARF unwinder (libgcc_s / libunwind). -// pragma(mangle) selects the compiler-specific symbol names: -// DMD: _d_throwdwarf, __dmd_begin_catch, __dmd_personality_v0 -// LDC: _d_throw_exception, _d_eh_enter_catch, _d_eh_personality -// -// Ported from druntime rt/dwarfeh.d. -// Copyright: Digital Mars 2015-2016 (original), uRT authors (port). -// License: Boost Software License 1.0 -// ────────────────────────────────────────────────────────────────────── - -// ── libunwind / libgcc_s bindings ─────────────────────────────────── - -private: -alias _Unwind_Ptr = size_t; -alias _Unwind_Word = size_t; -alias _Unwind_Exception_Class = ulong; -alias _uleb128_t = size_t; -alias _sleb128_t = ptrdiff_t; + writef_to!(WriteTarget.stderr, true)(" [0x{0:" ~ addr_fmt ~ "}]", cast(size_t) addr); -alias _Unwind_Reason_Code = int; -enum -{ - _URC_NO_REASON = 0, - _URC_FOREIGN_EXCEPTION_CAUGHT = 1, - _URC_FATAL_PHASE2_ERROR = 2, - _URC_FATAL_PHASE1_ERROR = 3, - _URC_NORMAL_STOP = 4, - _URC_END_OF_STACK = 5, - _URC_HANDLER_FOUND = 6, - _URC_INSTALL_CONTEXT = 7, - _URC_CONTINUE_UNWIND = 8, -} - -alias _Unwind_Action = int; -enum : _Unwind_Action -{ - _UA_SEARCH_PHASE = 1, - _UA_CLEANUP_PHASE = 2, - _UA_HANDLER_FRAME = 4, - _UA_FORCE_UNWIND = 8, -} - -alias _Unwind_Exception_Cleanup_Fn = extern(C) void function( - _Unwind_Reason_Code, _Unwind_Exception*); - -version (X86_64) -{ - align(16) struct _Unwind_Exception - { - _Unwind_Exception_Class exception_class; - _Unwind_Exception_Cleanup_Fn exception_cleanup; - _Unwind_Word private_1; - _Unwind_Word private_2; - } -} -else -{ - align(8) struct _Unwind_Exception - { - _Unwind_Exception_Class exception_class; - _Unwind_Exception_Cleanup_Fn exception_cleanup; - _Unwind_Word private_1; - _Unwind_Word private_2; + // Stop at program entry - hides C runtime tail noise. + if (r.name == "_Dmain" || r.name == "main") + break; + } } } -struct _Unwind_Context; - -extern(C) nothrow @nogc +public void terminate() @trusted { - _Unwind_Reason_Code _Unwind_RaiseException(_Unwind_Exception*); - void _Unwind_DeleteException(_Unwind_Exception*); - _Unwind_Word _Unwind_GetGR(_Unwind_Context*, int); - void _Unwind_SetGR(_Unwind_Context*, int, _Unwind_Word); - _Unwind_Ptr _Unwind_GetIP(_Unwind_Context*); - _Unwind_Ptr _Unwind_GetIPInfo(_Unwind_Context*, int*); - void _Unwind_SetIP(_Unwind_Context*, _Unwind_Ptr); - void* _Unwind_GetLanguageSpecificData(_Unwind_Context*); - _Unwind_Ptr _Unwind_GetRegionStart(_Unwind_Context*); -} - - -// ── ARM EABI unwinder types ──────────────────────────────────────── + import urt.io : writeln_err; + writeln_err("Unhandled exception -- no catch handler found, terminating."); -version (ARM) -{ - alias _Unwind_State = int; - enum : _Unwind_State + if (_tls_trace.length > 0) { - _US_VIRTUAL_UNWIND_FRAME = 0, - _US_UNWIND_FRAME_STARTING = 1, - _US_UNWIND_FRAME_RESUME = 2, - _US_ACTION_MASK = 3, - _US_FORCE_UNWIND = 8, + writeln_err(" stack trace:"); + print_trace(_tls_trace.addrs[0 .. _tls_trace.length]); } - extern(C) void _Unwind_Complete(_Unwind_Exception*) nothrow @nogc; + import urt.internal.stdc.stdlib : abort; + abort(); } -// ── EH register numbers (architecture-specific) ──────────────────── -version (X86_64) -{ - enum eh_exception_regno = 0; - enum eh_selector_regno = 1; -} -else version (X86) -{ - enum eh_exception_regno = 0; - enum eh_selector_regno = 2; -} -else version (AArch64) -{ - enum eh_exception_regno = 0; - enum eh_selector_regno = 1; -} -else version (ARM) -{ - enum eh_exception_regno = 0; - enum eh_selector_regno = 1; -} -else version (RISCV64) -{ - enum eh_exception_regno = 10; - enum eh_selector_regno = 11; -} -else version (RISCV32) -{ - enum eh_exception_regno = 10; - enum eh_selector_regno = 11; -} -else version (Xtensa) -{ - enum eh_exception_regno = 2; // a2 - enum eh_selector_regno = 3; // a3 -} -else - static assert(0, "Unknown EH register numbers for this architecture"); +// Shared state -// ── DWARF encoding constants ─────────────────────────────────────── +enum max_frames = 32; -enum +struct StackTraceData { - DW_EH_PE_FORMAT_MASK = 0x0F, - DW_EH_PE_APPL_MASK = 0x70, - DW_EH_PE_indirect = 0x80, - - DW_EH_PE_omit = 0xFF, - DW_EH_PE_ptr = 0x00, - DW_EH_PE_uleb128 = 0x01, - DW_EH_PE_udata2 = 0x02, - DW_EH_PE_udata4 = 0x03, - DW_EH_PE_udata8 = 0x04, - DW_EH_PE_sleb128 = 0x09, - DW_EH_PE_sdata2 = 0x0A, - DW_EH_PE_sdata4 = 0x0B, - DW_EH_PE_sdata8 = 0x0C, - - DW_EH_PE_absptr = 0x00, - DW_EH_PE_pcrel = 0x10, - DW_EH_PE_textrel = 0x20, - DW_EH_PE_datarel = 0x30, - DW_EH_PE_funcrel = 0x40, - DW_EH_PE_aligned = 0x50, + void*[max_frames] addrs; + ubyte length; } -// ── DMD exception class identifier ───────────────────────────────── - -enum _Unwind_Exception_Class dmd_exception_class = - (cast(_Unwind_Exception_Class)'D' << 56) | - (cast(_Unwind_Exception_Class)'M' << 48) | - (cast(_Unwind_Exception_Class)'D' << 40) | - (cast(_Unwind_Exception_Class)'D' << 24); +private StackTraceData _tls_trace; // static = TLS in D -// ── Compiler-specific symbol names ───────────────────────────────── -version (LDC) -{ - private enum throw_mangle = "_d_throw_exception"; - private enum catch_mangle = "_d_eh_enter_catch"; - private enum personality_mangle = "_d_eh_personality"; -} -else -{ - private enum throw_mangle = "_d_throwdwarf"; - private enum catch_mangle = "__dmd_begin_catch"; - private enum personality_mangle = "__dmd_personality_v0"; -} +// Druntime hooks (extern(C), linker-visible) -// ── ExceptionHeader ──────────────────────────────────────────────── +alias ClassInfo = TypeInfo_Class; -struct ExceptionHeader +extern(C) int _d_isbaseof(scope ClassInfo oc, scope const ClassInfo c) pure @trusted { - Throwable object; - _Unwind_Exception exception_object; - - int handler; - const(ubyte)* language_specific_data; - _Unwind_Ptr landing_pad; - - ExceptionHeader* next; - - static ExceptionHeader* stack; // thread-local chain - static ExceptionHeader ehstorage; // pre-allocated (one per thread) + if (oc is c) + return true; - static ExceptionHeader* create(Throwable o) nothrow @nogc + do { - import urt.mem.alloc : alloc; - auto eh = &ehstorage; - if (eh.object) + if (oc.base is c) + return true; + + foreach (iface; oc.interfaces) { - eh = cast(ExceptionHeader*)alloc(ExceptionHeader.sizeof).ptr; - if (!eh) - dwarf_terminate(__LINE__); + if (iface.classinfo is c || _d_isbaseof(iface.classinfo, c)) + return true; } - eh.object = o; - eh.exception_object.exception_class = dmd_exception_class; - return eh; - } - - static void release(ExceptionHeader* eh) nothrow @nogc - { - import urt.mem.alloc : free; - *eh = ExceptionHeader.init; - if (eh != &ehstorage) - free(eh[0..1]); - } - - void push() nothrow @nogc - { - next = stack; - stack = &this; - } - - static ExceptionHeader* pop() nothrow @nogc - { - auto eh = stack; - stack = eh.next; - return eh; - } - - static ExceptionHeader* to_exception_header(_Unwind_Exception* eo) nothrow @nogc - { - return cast(ExceptionHeader*)(cast(void*) eo - ExceptionHeader.exception_object.offsetof); - } -} - -// ── Helpers ──────────────────────────────────────────────────────── - -_Unwind_Ptr read_unaligned(T, bool consume)(ref const(ubyte)* p) nothrow @nogc @trusted -{ - import urt.processor : SupportUnalignedLoadStore; - static if (SupportUnalignedLoadStore) - T value = *cast(T*) p; - else - { - import urt.mem : memcpy; - T value = void; - memcpy(&value, p, T.sizeof); - } - - static if (consume) - p += T.sizeof; - return cast(_Unwind_Ptr) value; -} - -_uleb128_t u_leb128(const(ubyte)** p) nothrow @nogc -{ - auto q = *p; - _uleb128_t result = 0; - uint shift = 0; - while (1) - { - ubyte b = *q++; - result |= cast(_uleb128_t)(b & 0x7F) << shift; - if ((b & 0x80) == 0) - break; - shift += 7; - } - *p = q; - return result; -} -_sleb128_t s_leb128(const(ubyte)** p) nothrow @nogc -{ - auto q = *p; - ubyte b; - _sleb128_t result = 0; - uint shift = 0; - while (1) - { - b = *q++; - result |= cast(_sleb128_t)(b & 0x7F) << shift; - shift += 7; - if ((b & 0x80) == 0) - break; + oc = oc.base; } - if (shift < result.sizeof * 8 && (b & 0x40)) - result |= -(cast(_sleb128_t)1 << shift); - *p = q; - return result; -} + while (oc); -void dwarf_terminate(uint line) nothrow @nogc -{ - import urt.io : writef_to, WriteTarget; - import urt.internal.stdc.stdlib : abort; - writef_to!(WriteTarget.stderr, true)("dwarfeh({0}) fatal error", line); - abort(); + return false; } -// ── LSDA scanning ────────────────────────────────────────────────── - -enum LsdaResult -{ - not_found, - foreign, - corrupt, - no_action, - cleanup, - handler, -} -ClassInfo get_class_info(_Unwind_Exception* exception_object, const(ubyte)* current_lsd) nothrow @nogc +extern(C) void _d_createTrace(Throwable, void*) @trusted { - ExceptionHeader* eh = ExceptionHeader.to_exception_header(exception_object); - Throwable ehobject = eh.object; - for (ExceptionHeader* ehn = eh.next; ehn; ehn = ehn.next) - { - if (current_lsd != ehn.language_specific_data) - break; - Error e = cast(Error) ehobject; - if (e is null || (cast(Error) ehn.object) !is null) - ehobject = ehn.object; - } - return typeid(ehobject); + debug + _tls_trace.length = cast(ubyte) _capture_trace(_tls_trace.addrs[]); } -int action_table_lookup(_Unwind_Exception* exception_object, uint action_record_ptr, const(ubyte)* p_action_table, const(ubyte)* tt, - ubyte ttype, _Unwind_Exception_Class exception_class, const(ubyte)* lsda) nothrow @nogc +extern(C) void _d_printLastTrace(Throwable t) @trusted { - ClassInfo thrown_type; - if (exception_class == dmd_exception_class) - thrown_type = get_class_info(exception_object, lsda); - - for (auto ap = p_action_table + action_record_ptr - 1; 1; ) + debug { - auto type_filter = s_leb128(&ap); - auto apn = ap; - auto next_record_ptr = s_leb128(&ap); - - if (type_filter <= 0) - return -1; + import urt.io : writeln_err, writef_to, WriteTarget; - _Unwind_Ptr entry; - const(ubyte)* tt2; - switch (ttype & DW_EH_PE_FORMAT_MASK) - { - case DW_EH_PE_sdata2: entry = read_unaligned!(short, false)(tt2 = tt - type_filter * 2); break; - case DW_EH_PE_udata2: entry = read_unaligned!(ushort, false)(tt2 = tt - type_filter * 2); break; - case DW_EH_PE_sdata4: entry = read_unaligned!(int, false)(tt2 = tt - type_filter * 4); break; - case DW_EH_PE_udata4: entry = read_unaligned!(uint, false)(tt2 = tt - type_filter * 4); break; - case DW_EH_PE_sdata8: entry = read_unaligned!(long, false)(tt2 = tt - type_filter * 8); break; - case DW_EH_PE_udata8: entry = read_unaligned!(ulong, false)(tt2 = tt - type_filter * 8); break; - case DW_EH_PE_ptr: if (size_t.sizeof == 8) - goto case DW_EH_PE_udata8; - else - goto case DW_EH_PE_udata4; - default: - return -1; - } - if (!entry) - return -1; + if (_tls_trace.length == 0) + return; - switch (ttype & DW_EH_PE_APPL_MASK) + if (t !is null) { - case DW_EH_PE_absptr: - break; - case DW_EH_PE_pcrel: - entry += cast(_Unwind_Ptr) tt2; - break; - default: - return -1; + auto msg = t.msg; + writef_to!(WriteTarget.stderr, true)("Exception: {0}", msg); } - if (ttype & DW_EH_PE_indirect) - entry = *cast(_Unwind_Ptr*) entry; - - ClassInfo ci = cast(ClassInfo) cast(void*) entry; - - // D exception — check class hierarchy - if (exception_class == dmd_exception_class && _d_isbaseof(thrown_type, ci)) - return cast(int) type_filter; - - if (!next_record_ptr) - return 0; - ap = apn + next_record_ptr; + writeln_err(" stack trace:"); + print_trace(_tls_trace.addrs[0 .. _tls_trace.length]); } - assert(0); // unreachable — all paths return inside the loop } -LsdaResult scan_lsda(const(ubyte)* lsda, _Unwind_Ptr ip, _Unwind_Exception_Class exception_class, bool cleanups_only, bool prefer_handler, - _Unwind_Exception* exception_object, out _Unwind_Ptr landing_pad, out int handler) nothrow @nogc -{ - auto p = lsda; - if (!p) - return LsdaResult.no_action; - _Unwind_Ptr dw_pe_value(ubyte pe) +version (unittest) +{ + private bool eh_contains(const(char)[] haystack, const(char)[] needle) @trusted nothrow @nogc { - switch (pe) - { - case DW_EH_PE_sdata2: return read_unaligned!(short, true)(p); - case DW_EH_PE_udata2: return read_unaligned!(ushort, true)(p); - case DW_EH_PE_sdata4: return read_unaligned!(int, true)(p); - case DW_EH_PE_udata4: return read_unaligned!(uint, true)(p); - case DW_EH_PE_sdata8: return read_unaligned!(long, true)(p); - case DW_EH_PE_udata8: return read_unaligned!(ulong, true)(p); - case DW_EH_PE_sleb128: return cast(_Unwind_Ptr) s_leb128(&p); - case DW_EH_PE_uleb128: return cast(_Unwind_Ptr) u_leb128(&p); - case DW_EH_PE_ptr: if (size_t.sizeof == 8) - goto case DW_EH_PE_udata8; - else - goto case DW_EH_PE_udata4; - default: - dwarf_terminate(__LINE__); - return 0; - } + if (needle.length == 0) + return true; + if (needle.length > haystack.length) + return false; + foreach (i; 0 .. haystack.length - needle.length + 1) + if (haystack[i .. i + needle.length] == needle) + return true; + return false; } - ubyte lp_start = *p++; + // Skip-count verification layers. Each is `pragma(inline, false)` so + // the frames actually exist at runtime; each assigns to a local + // before returning to defeat tail-call optimisation. Distinct, + // grep-friendly names make the resolved symbols easy to match. - _Unwind_Ptr lp_base = 0; - if (lp_start != DW_EH_PE_omit) - lp_base = dw_pe_value(lp_start); + private pragma(inline, false) + void* eh_ca_layer_0(uint skip) @trusted nothrow @nogc + => caller_address(skip); - ubyte ttype = *p++; - _Unwind_Ptr tt_base = 0; - _Unwind_Ptr tt_offset = 0; - if (ttype != DW_EH_PE_omit) + private pragma(inline, false) + void* eh_ca_layer_1(uint skip) @trusted nothrow @nogc { - tt_base = u_leb128(&p); - tt_offset = (p - lsda) + tt_base; + auto pc = eh_ca_layer_0(skip); + return pc; } - ubyte call_site_format = *p++; - _Unwind_Ptr call_site_table_size = dw_pe_value(DW_EH_PE_uleb128); - - _Unwind_Ptr ip_offset = ip - lp_base; - bool no_action = false; - auto tt = lsda + tt_offset; - const(ubyte)* p_action_table = p + call_site_table_size; - - while (1) + private pragma(inline, false) + void* eh_ca_layer_2(uint skip) @trusted nothrow @nogc { - if (p >= p_action_table) - { - if (p == p_action_table) - break; - return LsdaResult.corrupt; - } - - _Unwind_Ptr call_site_start = dw_pe_value(call_site_format); - _Unwind_Ptr call_site_range = dw_pe_value(call_site_format); - _Unwind_Ptr call_site_lp = dw_pe_value(call_site_format); - _uleb128_t action_record_ptr_val = u_leb128(&p); - - if (ip_offset < call_site_start) - break; - - if (ip_offset < call_site_start + call_site_range) - { - if (action_record_ptr_val) - { - if (cleanups_only) - continue; - - auto h = action_table_lookup(exception_object, cast(uint) action_record_ptr_val, - p_action_table, tt, ttype, exception_class, lsda); - if (h < 0) - return LsdaResult.corrupt; - if (h == 0) - continue; - - no_action = false; - landing_pad = call_site_lp; - handler = h; - } - else if (call_site_lp) - { - if (prefer_handler && handler) - continue; - no_action = false; - landing_pad = call_site_lp; - handler = 0; - } - else - no_action = true; - } + auto pc = eh_ca_layer_1(skip); + return pc; } - if (no_action) - return LsdaResult.no_action; - - if (landing_pad) - return handler ? LsdaResult.handler : LsdaResult.cleanup; + // Demangler target - `.mangleof` gives us the real D-mangled form at + // compile time, so the test is stable across compilers. + private void eh_demangle_target() @trusted nothrow @nogc {} - return LsdaResult.not_found; + // Helper: this unittest function is what we expect to find as the + // caller in the capture_trace and skip-count tests below. Wrapping the + // captures in a private function lets us assert by name match. + private pragma(inline, false) + size_t eh_capture_here(void*[] buf) @trusted nothrow @nogc + => capture_trace(buf); } -// ── Public API ────────────────────────────────────────────────────── - -pragma(mangle, catch_mangle) -extern(C) Throwable dwarfeh_begin_catch(_Unwind_Exception* exception_object) nothrow @nogc -{ - version (ARM) version (LDC) - _Unwind_Complete(exception_object); - - ExceptionHeader* eh = ExceptionHeader.to_exception_header(exception_object); - auto o = eh.object; - eh.object = null; - - if (eh != ExceptionHeader.pop()) - dwarf_terminate(__LINE__); - - _Unwind_DeleteException(&eh.exception_object); - return o; -} - -extern(C) void* _d_eh_swapContextDwarf(void* newContext) nothrow @nogc -{ - auto old = ExceptionHeader.stack; - ExceptionHeader.stack = cast(ExceptionHeader*) newContext; - return old; -} - -pragma(mangle, throw_mangle) -extern(C) void dwarfeh_throw(Throwable o) +unittest { - import urt.io : writeln_err, writef_to, WriteTarget; - import urt.internal.stdc.stdlib : abort; - - ExceptionHeader* eh = ExceptionHeader.create(o); - eh.push(); + // capture_trace produces a non-empty trace whose first frame + // is in the function that called it (eh_capture_here). - auto refcount = o.refcount(); - if (refcount) - o.refcount() = refcount + 1; + void*[max_frames] buf; + auto n = eh_capture_here(buf[]); + assert(n > 0); + foreach (addr; buf[0 .. n]) + assert(addr !is null); - extern(C) static void exception_cleanup(_Unwind_Reason_Code reason, - _Unwind_Exception* eo) nothrow @nogc + // First captured frame should resolve to the immediate caller - + // eh_capture_here. (Skipped on platforms with no symbol table.) + Resolved r; + if (resolve_address(buf[0], r)) { - switch (reason) - { - case _URC_FOREIGN_EXCEPTION_CAUGHT: - case _URC_NO_REASON: - ExceptionHeader.release(ExceptionHeader.to_exception_header(eo)); - break; - default: - dwarf_terminate(__LINE__); - } + char[512] dbuf; + auto name = demangle_symbol(r.name, dbuf); + assert(eh_contains(name, "eh_capture_here"), name); } - eh.exception_object.exception_cleanup = &exception_cleanup; - _d_createTrace(o, null); - - auto r = _Unwind_RaiseException(&eh.exception_object); + // caller_address skip walks one frame per increment, starting + // from the caller of the function that called caller_address. - // Should not return — if it did, the exception was not caught. - dwarfeh_begin_catch(&eh.exception_object); - writeln_err("uncaught exception reached top of stack"); - auto msg = o.msg; - if (msg.length) - writef_to!(WriteTarget.stderr, true)(" {0}", msg); - abort(); -} - -// Common personality implementation. -_Unwind_Reason_Code dwarfeh_personality_common(_Unwind_Action actions, _Unwind_Exception_Class exception_class, _Unwind_Exception* exception_object, _Unwind_Context* context) nothrow @nogc -{ - const(ubyte)* language_specific_data; - int handler; - _Unwind_Ptr landing_pad; - - language_specific_data = cast(const(ubyte)*) _Unwind_GetLanguageSpecificData(context); - auto Start = _Unwind_GetRegionStart(context); - - // Get IP; use _Unwind_GetIPInfo to handle signal frames correctly. - int ip_before_insn; - auto ip = _Unwind_GetIPInfo(context, &ip_before_insn); - if (!ip_before_insn) - --ip; - - auto result = scan_lsda(language_specific_data, ip - Start, exception_class, - (actions & _UA_FORCE_UNWIND) != 0, - (actions & _UA_SEARCH_PHASE) != 0, - exception_object, - landing_pad, - handler); - landing_pad += Start; - - final switch (result) - { - case LsdaResult.not_found: - dwarf_terminate(__LINE__); - assert(0); - - case LsdaResult.foreign: - dwarf_terminate(__LINE__); - assert(0); + // From eh_ca_layer_0: + // skip=0 → PC inside eh_ca_layer_1 (layer_0's caller) + // skip=1 → PC inside eh_ca_layer_2 (layer_1's caller) + // skip=2 → PC inside this unittest (layer_2's caller) + auto pc0 = eh_ca_layer_2(0); + auto pc1 = eh_ca_layer_2(1); + auto pc2 = eh_ca_layer_2(2); - case LsdaResult.corrupt: - dwarf_terminate(__LINE__); - assert(0); + assert(pc0 !is null); + assert(pc1 !is null); + assert(pc2 !is null); - case LsdaResult.no_action: - return _URC_CONTINUE_UNWIND; + // Distinct PCs - each skip level yields a different call site. + assert(pc0 != pc1); + assert(pc1 != pc2); + assert(pc0 != pc2); - case LsdaResult.cleanup: - if (actions & _UA_SEARCH_PHASE) - return _URC_CONTINUE_UNWIND; - break; + // Strong check via the symbol resolver. Bare-metal / ESP32 have no + // on-device symtab and silently skip - the distinct-PC check above + // is the strongest assertion they can verify. + char[512] name; - case LsdaResult.handler: - if (actions & _UA_SEARCH_PHASE) - { - if (exception_class == dmd_exception_class) - { - auto eh = ExceptionHeader.to_exception_header(exception_object); - eh.handler = handler; - eh.language_specific_data = language_specific_data; - eh.landing_pad = landing_pad; - } - return _URC_HANDLER_FOUND; - } - break; + if (resolve_address(pc0, r)) + { + auto mangle = demangle_symbol(r.name, name); + assert(eh_contains(mangle, "eh_ca_layer_1"), mangle); } - - // Multiple exceptions in flight — chain them - if (exception_class == dmd_exception_class) + if (resolve_address(pc1, r)) { - auto eh = ExceptionHeader.to_exception_header(exception_object); - auto current_lsd = language_specific_data; - bool bypassed = false; - - while (eh.next) - { - ExceptionHeader* ehn = eh.next; - - Error e = cast(Error) eh.object; - if (e !is null && !cast(Error) ehn.object) - { - current_lsd = ehn.language_specific_data; - eh = ehn; - bypassed = true; - continue; - } - - if (current_lsd != ehn.language_specific_data) - break; - - eh.object = Throwable.chainTogether(ehn.object, eh.object); - - if (ehn.handler != handler && !bypassed) - { - handler = ehn.handler; - eh.handler = handler; - eh.language_specific_data = language_specific_data; - eh.landing_pad = landing_pad; - } - - eh.next = ehn.next; - _Unwind_DeleteException(&ehn.exception_object); - } - - if (bypassed) - { - eh = ExceptionHeader.to_exception_header(exception_object); - Error e = cast(Error) eh.object; - auto ehn = eh.next; - e.bypassedException = ehn.object; - eh.next = ehn.next; - _Unwind_DeleteException(&ehn.exception_object); - } + auto mangle = demangle_symbol(r.name, name); + assert(eh_contains(mangle, "eh_ca_layer_2"), mangle); } - _Unwind_SetGR(context, eh_exception_regno, cast(_Unwind_Word) exception_object); - _Unwind_SetGR(context, eh_selector_regno, handler); - _Unwind_SetIP(context, landing_pad); - - return _URC_INSTALL_CONTEXT; -} + // demangler -// Personality function entry points. -// ARM EABI uses a different calling convention than the standard Itanium ABI. -// FreeStanding ARM (bare-metal) uses DWARF EH, not ARM EHABI, so use the standard personality. -version (ARM) -{ - version (Beken) - enum UseArmEhabi = true; - else version (FreeStanding) - enum UseArmEhabi = false; - else version (LDC) - enum UseArmEhabi = true; - else - enum UseArmEhabi = false; -} -else - enum UseArmEhabi = false; + // Non-D / degenerate inputs pass through unchanged. + assert(demangle_symbol("main", name) == "main"); + assert(demangle_symbol("", name) == ""); + assert(demangle_symbol("_D", name) == "_D"); + assert(demangle_symbol("?MyClass@@QAEXXZ", name) == "?MyClass@@QAEXXZ"); -static if (UseArmEhabi) -{ - extern(C) _Unwind_Reason_Code _d_eh_personality(_Unwind_State state, _Unwind_Exception* exception_object, _Unwind_Context* context) nothrow @nogc + // Real D mangling via .mangleof - qualified path must contain the + // function name and at least one module-separator dot. + auto dem = demangle_symbol(eh_demangle_target.mangleof, name); + assert(eh_contains(dem, "eh_demangle_target"), dem); + bool has_dot = false; + foreach (c; dem) if (c == '.') { - _Unwind_Action actions; - switch (state & _US_ACTION_MASK) - { - case _US_VIRTUAL_UNWIND_FRAME: - actions = _UA_SEARCH_PHASE; - break; - case _US_UNWIND_FRAME_STARTING: - actions = _UA_CLEANUP_PHASE; - break; - case _US_UNWIND_FRAME_RESUME: - return _URC_CONTINUE_UNWIND; - default: - dwarf_terminate(__LINE__); - return _URC_FATAL_PHASE1_ERROR; - } - if (state & _US_FORCE_UNWIND) - actions |= _UA_FORCE_UNWIND; - - return dwarfeh_personality_common(actions, exception_object.exception_class, - exception_object, context); + has_dot = true; + break; } -} -else -{ - pragma(mangle, personality_mangle) - extern(C) _Unwind_Reason_Code dwarfeh_personality(int ver, _Unwind_Action actions, _Unwind_Exception_Class exception_class, _Unwind_Exception* exception_object, _Unwind_Context* context) nothrow @nogc - { - if (ver != 1) - return _URC_FATAL_PHASE1_ERROR; + assert(has_dot, dem); - return dwarfeh_personality_common(actions, exception_class, - exception_object, context); - } -} + // Hand-crafted ABI-compliant manglings - parse must recover the + // qualified name prefix regardless of the trailing type signature. + assert(eh_contains(demangle_symbol("_D3foo3barFZv", name), "foo.bar")); + assert(eh_contains(demangle_symbol("_D3foo3bar3bazFZv", name), "foo.bar.baz")); -// LDC-only: trivial cleanup hooks (DWARF handles cleanup via personality). -version (LDC) -{ - extern(C) bool _d_enter_cleanup(void* ptr) nothrow @nogc @trusted => true; - extern(C) void _d_leave_cleanup(void* ptr) nothrow @nogc @trusted {} + // Malformed input must not crash - output is undefined but safe. + demangle_symbol("_D999zzz", name); // LName length overflows src + demangle_symbol("_D3fooQ", name); // Q back-ref with no offset + demangle_symbol("_D__T1aZ", name); // template with minimal content } - -} // version (!Windows) — shared DWARF EH From 8cd6a040323867456fec104a79e947a3c2028ed5 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 23 Apr 2026 00:58:33 +1000 Subject: [PATCH 129/138] Add a mem-tracking system to find leaks... --- src/urt/mem/alloc.d | 34 +++- src/urt/mem/tracking.d | 413 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 445 insertions(+), 2 deletions(-) create mode 100644 src/urt/mem/tracking.d diff --git a/src/urt/mem/alloc.d b/src/urt/mem/alloc.d index 1a7d4d6..208147b 100644 --- a/src/urt/mem/alloc.d +++ b/src/urt/mem/alloc.d @@ -29,7 +29,17 @@ void[] alloc(size_t size, size_t alignment, MemFlags flags = MemFlags.none) pure assert(is_power_of_2(alignment), "Alignment must be a power of two!"); - return _alloc(size, alignment, flags); + void[] mem = _alloc(size, alignment, flags); + version (AllocTracking) + { + import urt.mem.tracking : track_alloc; + if (mem.ptr !is null) + { + alias TrackFn = void function(void*, size_t) pure nothrow @nogc; + (cast(TrackFn) &track_alloc)(mem.ptr, mem.length); + } + } + return mem; } void[] realloc(void[] mem, size_t new_size, size_t alignment = 8, MemFlags flags = MemFlags.none) pure @@ -45,9 +55,23 @@ void[] realloc(void[] mem, size_t new_size, size_t alignment = 8, MemFlags flags return alloc(new_size, alignment, flags); static if (has_realloc) - return _realloc(mem, new_size, alignment, flags); + { + void* old_ptr = mem.ptr; + void[] new_mem = _realloc(mem, new_size, alignment, flags); + version (AllocTracking) + { + import urt.mem.tracking : track_realloc; + if (new_mem.ptr !is null) + { + alias TrackFn = void function(void*, void*, size_t) pure nothrow @nogc; + (cast(TrackFn) &track_realloc)(old_ptr, new_mem.ptr, new_mem.length); + } + } + return new_mem; + } else { + // Fallback path uses nested alloc/free, which are already hooked. void[] new_mem = alloc(new_size, alignment, flags); if (new_mem.ptr !is null) { @@ -63,6 +87,12 @@ void free(void[] mem) pure { if (mem.ptr is null) return; + version (AllocTracking) + { + import urt.mem.tracking : untrack_alloc; + alias UntrackFn = void function(void*) pure nothrow @nogc; + (cast(UntrackFn) &untrack_alloc)(mem.ptr); + } _free(mem.ptr); } diff --git a/src/urt/mem/tracking.d b/src/urt/mem/tracking.d new file mode 100644 index 0000000..cbb679d --- /dev/null +++ b/src/urt/mem/tracking.d @@ -0,0 +1,413 @@ +/** + * Allocation tracking for leak detection. + * + * Enabled with `version = AllocTracking;`. When enabled, the central + * allocator (`urt.mem.alloc`) calls into this module on every + * alloc/realloc/free to record the caller's stack trace in a fixed-size + * open-addressed hash table keyed on pointer. + * + * The table uses static `__gshared` storage (no allocation of its own, + * which would recurse) and is NOT thread-safe. + * + * Typical workflow: + * 1. Boot the system, let all startup/immortal allocations settle. + * 2. Call `alloc_mark_baseline()` (via the console command + * `/system/alloc/mark`). + * 3. After running for a while, call `alloc_print_leaks(min_age, sink)` + * to see allocations made after the baseline that have been alive + * for at least `min_age`, grouped by call site. + */ +module urt.mem.tracking; + +version (AllocTracking): + +import urt.time : MonoTime, getTime, Duration; +import urt.internal.exception : capture_trace, resolve_address, resolve_batch, Resolved; + +nothrow @nogc: + + +// Tuning + +// Number of stack frames captured per allocation. +enum trace_depth = 6; + +// Maximum live allocations tracked. +// ~80 bytes per entry on 64-bit: 16384 * 80 = 1.25 MiB. +enum track_capacity = 16384; +static assert((track_capacity & (track_capacity - 1)) == 0, "track_capacity must be a power of two"); + +// Load factor ceiling (num/den). Linear probing degrades past ~70%. +enum track_max_load_num = 7; +enum track_max_load_den = 10; + +// Max unique call sites shown in a grouped leak dump. +enum max_groups = 256; + + +// Entry / storage + +struct Entry +{ + void* ptr; // null = empty, (void*)1 = tombstone + uint size; + uint serial; + MonoTime time; + void*[trace_depth] pcs; +} + +__gshared Entry[track_capacity] _table; +__gshared uint _live_count; +__gshared uint _serial_counter; +__gshared uint _baseline_serial; +__gshared ulong _tracked_bytes; +__gshared bool _table_full_warned; + + +// Tracking API -- called by urt.mem.alloc hooks + +void track_alloc(void* ptr, size_t size) +{ + if (ptr is null) + return; + + // Refuse inserts past the load factor -- linear probing gets pathological. + if (_live_count * track_max_load_den >= track_capacity * track_max_load_num) + { + warn_table_full(); + return; + } + + size_t slot = find_insert(ptr); + if (slot == size_t.max) + { + warn_table_full(); + return; + } + + bool was_live = _table[slot].ptr !is null && _table[slot].ptr !is tombstone; + if (was_live) + _tracked_bytes -= _table[slot].size; + else + _live_count++; + + _table[slot].ptr = ptr; + _table[slot].size = cast(uint) size; + _table[slot].serial = ++_serial_counter; + _table[slot].time = getTime(); + _table[slot].pcs[] = null; + capture_trace(_table[slot].pcs[]); + + _tracked_bytes += size; +} + +void untrack_alloc(void* ptr) +{ + if (ptr is null) + return; + size_t slot = find(ptr); + if (slot == size_t.max) + return; + _tracked_bytes -= _table[slot].size; + _table[slot].ptr = tombstone; + _table[slot].size = 0; + _live_count--; +} + +void track_realloc(void* old_ptr, void* new_ptr, size_t new_size) +{ + if (old_ptr is new_ptr) + { + if (new_ptr is null) + return; + size_t slot = find(new_ptr); + if (slot != size_t.max) + { + _tracked_bytes = _tracked_bytes - _table[slot].size + new_size; + _table[slot].size = cast(uint) new_size; + // Keep original serial/time/trace -- the allocation's identity hasn't changed. + } + else + track_alloc(new_ptr, new_size); + return; + } + untrack_alloc(old_ptr); + track_alloc(new_ptr, new_size); +} + + +// Baseline / stats + +void alloc_mark_baseline() +{ + _baseline_serial = _serial_counter; +} + +uint alloc_baseline() => _baseline_serial; + +void alloc_stats(out uint live_count, out ulong live_bytes, out uint capacity, out uint total_serial) +{ + live_count = _live_count; + live_bytes = _tracked_bytes; + capacity = track_capacity; + total_serial = _serial_counter; +} + + +// Report helpers + +// Return the first frame in `pcs` whose resolved symbol is NOT a known +// allocator wrapper. Falls back to the last non-null frame if every +// frame resolves to a wrapper. +void* top_user_pc(const(void*)[] pcs) +{ + void* fallback = null; + foreach (addr; pcs) + { + if (addr is null) + continue; + fallback = cast(void*) addr; + + Resolved r; + if (!resolve_address(cast(void*) addr, r)) + return cast(void*) addr; // unresolved -> assume user code + if (!is_wrapper_name(r.name)) + return cast(void*) addr; + } + return fallback; +} + +alias Sink = void delegate(const(char)[]) nothrow @nogc; + +// Sink-based stats dump. One sink call per line, no trailing newline. +void alloc_print_stats(scope Sink sink) +{ + import urt.mem.temp : tformat; + + sink(tformat("Live allocations: {0}", _live_count)); + sink(tformat("Live bytes: {0}", _tracked_bytes)); + sink(tformat("Table capacity: {0}", cast(uint) track_capacity)); + sink(tformat("Total allocs: {0} (serial)", _serial_counter)); + sink(tformat("Baseline: {0}", _baseline_serial)); + if (_table_full_warned) + sink("WARNING: tracking table has overflowed at least once -- some allocations untracked"); +} + +// Grouped leak dump. Shows allocations with `serial > baseline` that +// have been alive at least `min_age`, grouped by top-user PC. +void alloc_print_leaks(Duration min_age, scope Sink sink) +{ + print_candidates(_baseline_serial, min_age, "Leak candidates", sink); +} + +// Grouped dump of every currently-live allocation, ignoring baseline +// and age. Intended for shutdown/exit leak reports -- at shutdown +// anything still alive is effectively a leak. +void alloc_print_live(scope Sink sink) +{ + print_candidates(0, Duration.init, "Live allocations", sink); +} + +private void print_candidates(uint min_serial, Duration min_age, string kind, scope Sink sink) +{ + import urt.mem.temp : tformat; + + struct Site + { + void* pc; + uint count; + ulong bytes; + MonoTime oldest; + } + + Site[max_groups] groups = void; + size_t ngroups = 0; + Site overflow; + overflow.pc = null; + overflow.count = 0; + overflow.bytes = 0; + + MonoTime now = getTime(); + + uint total_count = 0; + ulong total_bytes = 0; + + foreach (ref e; _table) + { + if (e.ptr is null || e.ptr is tombstone) + continue; + if (e.serial <= min_serial) + continue; + if (now - e.time < min_age) + continue; + + void* top = top_user_pc(e.pcs[]); + + size_t g = size_t.max; + foreach (i; 0 .. ngroups) + if (groups[i].pc is top) { g = i; break; } + + Site* s; + if (g == size_t.max) + { + if (ngroups < max_groups) + { + groups[ngroups].pc = top; + groups[ngroups].count = 0; + groups[ngroups].bytes = 0; + groups[ngroups].oldest = e.time; + s = &groups[ngroups]; + ngroups++; + } + else + s = &overflow; + } + else + s = &groups[g]; + + s.count++; + s.bytes += e.size; + if (e.time < s.oldest || s.oldest == MonoTime()) + s.oldest = e.time; + + total_count++; + total_bytes += e.size; + } + + if (total_count == 0) + { + sink(tformat("{0}: none (min serial {1}, min age {2})", kind, min_serial, min_age)); + return; + } + + // Sort by bytes descending -- insertion sort, ngroups is small. + foreach (i; 1 .. ngroups) + { + Site tmp = groups[i]; + size_t j = i; + while (j > 0 && groups[j - 1].bytes < tmp.bytes) + { + groups[j] = groups[j - 1]; + --j; + } + groups[j] = tmp; + } + + sink(tformat("{0}: {1} allocations, {2} bytes, {3} unique sites", + kind, total_count, total_bytes, + cast(uint) ngroups + (overflow.count > 0 ? 1 : 0))); + sink(""); + + // One batched resolve for all unique sites - on POSIX this folds + // what was N full DWARF .debug_line scans into a single scan. + void*[max_groups] addrs = void; + Resolved[max_groups] resolved; + foreach (i; 0 .. ngroups) + addrs[i] = groups[i].pc; + resolve_batch(addrs[0 .. ngroups], resolved[0 .. ngroups]); + + foreach (i; 0 .. ngroups) + { + Site s = groups[i]; + Duration age = now - s.oldest; + const r = &resolved[i]; + + sink(tformat(" {0} allocs, {1} bytes, oldest {2}", s.count, s.bytes, age)); + if (r.file.length > 0 && r.line > 0) + sink(tformat(" {0}({1}): {2}", r.file, r.line, r.name)); + else if (r.name.length > 0) + sink(tformat(" {0}", r.name)); + else + sink(tformat(" 0x{0:016x}", cast(size_t) s.pc)); + } + + if (overflow.count > 0) + { + Duration age = now - overflow.oldest; + sink(tformat(" [overflow] {0} allocs, {1} bytes, oldest {2} (from > {3} unique sites)", + overflow.count, overflow.bytes, age, cast(uint) max_groups)); + } +} + + +private: + +enum void* tombstone = cast(void*) 1; + +// Prefixes of symbol names considered "allocator plumbing" and skipped +// when computing the top user frame. Extend as needed. +immutable string[] WRAPPER_PREFIXES = [ + "urt.mem.", + "urt.internal.exception.", // capture_trace itself lives here + "_d_new", + "_d_alloc", + "_d_array", + "_D3urt3mem", +]; + +bool is_wrapper_name(const(char)[] name) +{ + foreach (p; WRAPPER_PREFIXES) + { + if (name.length >= p.length && name[0 .. p.length] == p) + return true; + } + return false; +} + +void warn_table_full() +{ + if (_table_full_warned) + return; + _table_full_warned = true; + + import urt.io : writeln_err; + writeln_err("urt.mem.tracking: allocation table at capacity -- subsequent allocs untracked! Increase track_capacity in urt.mem.tracking."); +} + + +// Hash table primitives + +size_t hash_ptr(void* p) pure +{ + size_t x = cast(size_t) p >> 3; + return cast(size_t)(cast(ulong) x * 0x9E3779B97F4A7C15UL); +} + +size_t find(void* ptr) +{ + enum mask = cast(size_t)(track_capacity - 1); + size_t i = hash_ptr(ptr) & mask; + foreach (_; 0 .. track_capacity) + { + void* p = _table[i].ptr; + if (p is null) + return size_t.max; + if (p is ptr) + return i; + i = (i + 1) & mask; + } + return size_t.max; +} + +size_t find_insert(void* ptr) +{ + enum mask = cast(size_t)(track_capacity - 1); + size_t i = hash_ptr(ptr) & mask; + size_t first_tomb = size_t.max; + foreach (_; 0 .. track_capacity) + { + void* p = _table[i].ptr; + if (p is null) + return first_tomb != size_t.max ? first_tomb : i; + if (p is tombstone) + { + if (first_tomb == size_t.max) + first_tomb = i; + } + else if (p is ptr) + return i; + i = (i + 1) & mask; + } + return first_tomb; +} From 4524cd6de3672d3b83407170aff01da963b470a2 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Wed, 22 Apr 2026 22:01:06 +1000 Subject: [PATCH 130/138] ESP32 BLE --- src/sys/esp32/ble.d | 14 +++++------ src/sys/esp32/ow_shim.c | 55 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/sys/esp32/ble.d b/src/sys/esp32/ble.d index d9edfca..5cc3d4d 100644 --- a/src/sys/esp32/ble.d +++ b/src/sys/esp32/ble.d @@ -52,7 +52,7 @@ void ble_hw_close(uint port) return; ble_hw_scan_stop(port); - ble_hw_adv_stop(port); + ble_hw_adv_stop(port, BLEAdv.init); // disconnect all active connections foreach (ref s; _sessions) @@ -96,18 +96,18 @@ void ble_hw_scan_stop(uint port) // --- Advertising --- -bool ble_hw_adv_start(uint port, ref const BLEAdvConfig cfg) +BLEAdv ble_hw_adv_start(uint port, ref const BLEAdvConfig cfg) { if (cfg.adv_data.length > 0 && cfg.adv_data.length <= 31) { if (ble_gap_adv_set_data(cfg.adv_data.ptr, cast(int)cfg.adv_data.length) != 0) - return false; + return BLEAdv.init; } if (cfg.scan_rsp.length > 0 && cfg.scan_rsp.length <= 31) { if (ble_gap_adv_rsp_set_data(cfg.scan_rsp.ptr, cast(int)cfg.scan_rsp.length) != 0) - return false; + return BLEAdv.init; } ble_gap_adv_params params; @@ -117,11 +117,11 @@ bool ble_hw_adv_start(uint port, ref const BLEAdvConfig cfg) params.itvl_max = params.itvl_min; if (ble_gap_adv_start(0, null, 0, ¶ms, &gap_event_trampoline, null) != 0) - return false; - return true; + return BLEAdv.init; + return BLEAdv(0); } -void ble_hw_adv_stop(uint port) +void ble_hw_adv_stop(uint port, BLEAdv) { ble_gap_adv_stop(); } diff --git a/src/sys/esp32/ow_shim.c b/src/sys/esp32/ow_shim.c index 9220cab..6994270 100644 --- a/src/sys/esp32/ow_shim.c +++ b/src/sys/esp32/ow_shim.c @@ -417,6 +417,61 @@ void ow_wifi_set_ap_callback(ow_wifi_event_cb_t cb) { (void)cb; } #endif // CONFIG_ESP_WIFI_ENABLED +// -- BLE (NimBLE) wrappers -- + +#if CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ENABLED +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/ble_gap.h" + +typedef int (*ow_gap_event_cb_t)(struct ble_gap_event *, void *); +static ow_gap_event_cb_t ow_gap_callback; + +static int ow_gap_event_dispatch(struct ble_gap_event *event, void *arg) +{ + if (ow_gap_callback) + return ow_gap_callback(event, arg); + return 0; +} + +static void ow_nimble_host_task(void *param) +{ + (void)param; + nimble_port_run(); + nimble_port_freertos_deinit(); +} + +int ow_ble_init(void) +{ + int rc = nimble_port_init(); + if (rc != 0) + return rc; + nimble_port_freertos_init(ow_nimble_host_task); + return 0; +} + +void ow_ble_deinit(void) +{ + nimble_port_stop(); + nimble_port_deinit(); +} + +void ow_ble_set_gap_callback(ow_gap_event_cb_t cb) +{ + ow_gap_callback = cb; +} + +#else // !BT_NIMBLE + +typedef int (*ow_gap_event_cb_t)(void *, void *); + +int ow_ble_init(void) { return -1; } +void ow_ble_deinit(void) {} +void ow_ble_set_gap_callback(ow_gap_event_cb_t cb) { (void)cb; } + +#endif // CONFIG_BT_NIMBLE_ENABLED + // -- CAN (TWAI) driver -- // // Only ow_can_open lives here -- it builds the timing/general/filter config From 9c238bdf6c5722013cb6be1697516f4597ea2e61 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 26 Apr 2026 14:13:50 +1000 Subject: [PATCH 131/138] Rejig platforms for URT CI. --- .github/workflows/ci.yml | 167 ++++++--- Makefile | 254 ++++++++------ platforms.mk | 671 ++++++++++++++++++++++++++++++++++++ platforms/bk7231/README.txt | 90 +++++ platforms/bk7231/bk7231n.ld | 139 ++++++++ platforms/bk7231/bk7231t.ld | 136 ++++++++ platforms/bl618/README.txt | 74 ++++ platforms/bl618/bl618.ld | 136 ++++++++ platforms/bl808/README.txt | 94 +++++ platforms/bl808/bl808_d0.ld | 160 +++++++++ platforms/bl808/bl808_m0.ld | 126 +++++++ platforms/rp2350/README.txt | 90 +++++ platforms/rp2350/rp2350.ld | 154 +++++++++ platforms/stm32/README.txt | 105 ++++++ platforms/stm32/stm32_f4.ld | 139 ++++++++ platforms/stm32/stm32_f7.ld | 141 ++++++++ src/sys/baremetal/timer.d | 4 +- src/sys/bk7231/start.S | 11 + src/sys/stm32/start.S | 12 + src/urt/digest/sha.d | 26 +- 20 files changed, 2567 insertions(+), 162 deletions(-) create mode 100644 platforms.mk create mode 100644 platforms/bk7231/README.txt create mode 100644 platforms/bk7231/bk7231n.ld create mode 100644 platforms/bk7231/bk7231t.ld create mode 100644 platforms/bl618/README.txt create mode 100644 platforms/bl618/bl618.ld create mode 100644 platforms/bl808/README.txt create mode 100644 platforms/bl808/bl808_d0.ld create mode 100644 platforms/bl808/bl808_m0.ld create mode 100644 platforms/rp2350/README.txt create mode 100644 platforms/rp2350/rp2350.ld create mode 100644 platforms/stm32/README.txt create mode 100644 platforms/stm32/stm32_f4.ld create mode 100644 platforms/stm32/stm32_f7.ld diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ab0698..0f7d663 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,71 +1,150 @@ -name: CI Matrix +name: CI on: push: - branches: [ "master", "release" ] + branches: [master, release] pull_request: - branches: [ "master", "release" ] + branches: [master, release] permissions: contents: read jobs: - build-and-test: - runs-on: ${{ startsWith(matrix.platform, 'arm') && 'ubuntu-24.04-arm' || format('{0}-latest', matrix.os) }} # GitHub-hosted runners + # ─── Host: build + run unittest exes on every supported host arch/OS ─── + host: + name: host ${{ matrix.os }} ${{ matrix.arch }} ${{ matrix.compiler }} + runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || format('{0}-latest', matrix.os) }} strategy: + fail-fast: false matrix: - os: [windows, ubuntu] + os: [ubuntu, windows] compiler: [dmd, ldc] - platform: [x86, x86_64, arm, arm64, riscv64] + arch: [x86, x86_64, arm64] exclude: - # No point cross-compiling on Windows; linux is so much faster! - - os: windows - platform: arm - - os: windows - platform: arm64 - - os: windows - platform: riscv64 - # TODO: DMD ARM64 support is WIP; remove when it releases... - - compiler: dmd - platform: arm64 - - compiler: dmd - platform: arm - - platform: arm # TODO: not yet working... - - platform: riscv64 # TODO: not yet working... - fail-fast: false + - { os: windows, arch: arm64 } # windows-arm64 runner not in free tier + - { compiler: dmd, arch: arm64 } # DMD arm64 still WIP + env: + MAKE_OS: ${{ matrix.os == 'ubuntu' && 'linux' || matrix.os }} steps: - uses: actions/checkout@v4 - - name: Setup D Compiler + - name: Setup D compiler uses: dlang-community/setup-dlang@v2 with: compiler: ${{ matrix.compiler == 'dmd' && 'dmd-master' || matrix.compiler }} - # Linux needs mbedtls for TLS/crypto support - - name: Install mbedtls (Linux) - if: ${{ matrix.os == 'ubuntu' }} + - name: Install mbedtls (Linux host) + if: matrix.os == 'ubuntu' run: sudo apt-get update && sudo apt-get install -y libmbedtls-dev - # 32-bit linux needs libs to link and run tests... - - name: Install 32-bit Toolchains (Linux) - if: ${{ matrix.os == 'ubuntu' && matrix.platform == 'x86' }} - run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install -y gcc-multilib libmbedtls-dev:i386 - - name: Install 32-bit ARM Toolchains (Linux) - if: ${{ matrix.os == 'ubuntu' && matrix.platform == 'arm' }} - run: sudo dpkg --add-architecture armhf && sudo apt-get update && sudo apt-get install -y libstdc++6:armhf - - - name: Build release - if: ${{ github.ref == 'refs/heads/release' }} - run: make PLATFORM=${{ matrix.platform }} CONFIG=release OS=${{ matrix.os }} D_COMPILER=${{ matrix.compiler }} + + - name: Install 32-bit deps (Linux x86) + if: matrix.os == 'ubuntu' && matrix.arch == 'x86' + run: | + sudo dpkg --add-architecture i386 + sudo apt-get update + sudo apt-get install -y gcc-multilib libmbedtls-dev:i386 + - name: Build unittest - run: make PLATFORM=${{ matrix.platform }} CONFIG=unittest OS=${{ matrix.os }} D_COMPILER=${{ matrix.compiler }} + run: make ARCH=${{ matrix.arch }} OS=${{ env.MAKE_OS }} CONFIG=unittest COMPILER=${{ matrix.compiler }} + + - name: Run unittest + run: ./bin/${{ matrix.arch }}_${{ env.MAKE_OS }}_unittest/urt_test${{ matrix.os == 'windows' && '.exe' || '' }} - - name: Test - if: ${{ success() && (matrix.platform == 'x86_64' || matrix.platform == 'x86' || matrix.platform == 'arm64') }} - run: ./bin/${{ matrix.platform }}_unittest/urt_test + - name: Build release library + if: github.ref == 'refs/heads/release' + run: make ARCH=${{ matrix.arch }} OS=${{ env.MAKE_OS }} CONFIG=release COMPILER=${{ matrix.compiler }} - name: Upload release library - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/release' && matrix.compiler == 'ldc' }} + if: github.event_name == 'push' && github.ref == 'refs/heads/release' && matrix.compiler == 'ldc' + uses: actions/upload-artifact@v4 + with: + name: urt-lib_${{ matrix.os }}_${{ matrix.arch }} + path: ./bin/${{ matrix.arch }}_${{ env.MAKE_OS }}_release/ + + # ─── Cross-bare: build flashable urt_test.bin for every embedded target ─── + # CI runner can't execute these — they're flash images. Artifacts are + # uploaded so anyone can grab one and flash it to real hardware for testing. + cross-bare: + name: cross ${{ matrix.target.name }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: + - { name: bl618, toolchain: riscv, args: PLATFORM=bl618 } + - { name: bl808-d0, toolchain: riscv, args: 'PLATFORM=bl808 PROCESSOR=c906' } + - { name: bl808-m0, toolchain: riscv, args: 'PLATFORM=bl808 PROCESSOR=e907' } + - { name: rp2350, toolchain: arm, args: PLATFORM=rp2350 } + - { name: bk7231n, toolchain: arm, args: PLATFORM=bk7231n } + - { name: bk7231t, toolchain: arm, args: PLATFORM=bk7231t } + - { name: stm4xx, toolchain: arm, args: PLATFORM=stm4xx } + - { name: stm7xx, toolchain: arm, args: PLATFORM=stm7xx } + steps: + - uses: actions/checkout@v4 + + - uses: dlang-community/setup-dlang@v2 + with: + compiler: ldc + + - name: Install ARM cross-toolchain + if: matrix.target.toolchain == 'arm' + run: | + sudo apt-get update + sudo apt-get install -y gcc-arm-none-eabi picolibc-arm-none-eabi + + - name: Install RISC-V cross-toolchain + if: matrix.target.toolchain == 'riscv' + run: | + sudo apt-get update + sudo apt-get install -y gcc-riscv64-unknown-elf picolibc-riscv64-unknown-elf + + - name: Build flashable unittest image + run: make ${{ matrix.target.args }} CONFIG=unittest + + - name: Upload flash image + uses: actions/upload-artifact@v4 + with: + name: urt_test_${{ matrix.target.name }} + path: | + bin/*/urt_test + bin/*/urt_test.bin + + # ─── Cross-ESP: pre-link artifacts only (no ESP-IDF on build slave) ─── + # esp32, esp32-s3 (Xtensa): emit LLVM bitcode (.bc) for downstream Espressif + # llc / ESP-IDF link. Requires LDC with Xtensa target enabled. + # esp32-p4 (RISC-V): plain compile to object (.o); link needs ESP-IDF. + cross-esp: + name: cross ${{ matrix.target.name }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: + - { name: esp32, arch: xtensa, ext: bc } # LX6 + - { name: esp32-s3, arch: xtensa, ext: bc } # LX7 + - { name: esp32-p4, arch: riscv, ext: o } # RV32IMAFDCV + steps: + - uses: actions/checkout@v4 + + - uses: dlang-community/setup-dlang@v2 + with: + compiler: ldc + + - name: Install RISC-V toolchain + if: matrix.target.arch == 'riscv' + run: | + sudo apt-get update + sudo apt-get install -y gcc-riscv64-unknown-elf picolibc-riscv64-unknown-elf + + # TODO: Xtensa needs LDC built with Xtensa target. Recent upstream LDC + # has experimental support; if dlang-community/setup-dlang's LDC build + # lacks it, install Espressif's LDC fork here. + + - name: Build + run: make PLATFORM=${{ matrix.target.name }} CONFIG=unittest + + - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: urt-lib_${{ matrix.os }}_${{ matrix.platform }} - path: ./bin/${{ matrix.platform }}_release/${{ matrix.os == 'windows' && 'urt.lib' || 'liburt.a' }} + name: urt_test_${{ matrix.target.name }} + path: obj/*/urt_test.${{ matrix.target.ext }} diff --git a/Makefile b/Makefile index ad0abbf..39cb25e 100644 --- a/Makefile +++ b/Makefile @@ -1,123 +1,165 @@ -OS ?= ubuntu -PLATFORM ?= x86_64 -CONFIG ?= debug -D_COMPILER ?= dmd -DC ?= dmd - -SRCDIR := src -TARGET_SUBDIR := $(PLATFORM)_$(CONFIG) -OBJDIR := obj/$(TARGET_SUBDIR) -TARGETDIR := bin/$(TARGET_SUBDIR) -TARGETNAME := urt - -# unittest config adjustments +URT_SRCDIR := src + +include platforms.mk + +# ======================================================================= +# Build mode +# +# Host (windows/linux/freebsd): +# default -> static lib (liburt.a / urt.lib) +# CONFIG=unittest -> standalone test exe +# +# Cross (freertos/baremetal): +# CONFIG=unittest -> flashable test image (.bin via objcopy), using URT's +# in-tree linker script (platforms//.ld) +# default -> compile-only (.o for non-ESP, .bc for ESP) +# ESP -> always .bc, no link possible without ESP-IDF +# ======================================================================= + +OBJDIR := obj/$(BUILDNAME)_$(CONFIG) +TARGETDIR := bin/$(BUILDNAME)_$(CONFIG) + +# Linker script selection for cross-target unittest builds (ESP excluded -- +# no ESP-IDF on build slave). platforms.mk falls back to compile-only when +# BAREMETAL_LD isn't set. ifeq ($(CONFIG),unittest) - TARGETNAME := $(TARGETNAME)_test - BUILD_TYPE := exe -else - BUILD_TYPE := lib -endif - -DEPFILE := $(OBJDIR)/$(TARGETNAME).d - -DFLAGS := $(DFLAGS) -preview=bitfields -preview=rvaluerefparam -preview=nosharedaccess -preview=in - -SOURCES := $(shell find "$(SRCDIR)" -type f -name '*.d' -not -path '$(SRCDIR)/sys/*') -SOURCES := $(SOURCES) $(shell find "$(SRCDIR)/sys/baremetal" -type f -name '*.d') -ifeq ($(OS),windows) - SOURCES := $(SOURCES) $(shell find "$(SRCDIR)/sys/windows" -type f -name '*.d') -endif -ifneq ($(filter linux ubuntu freebsd,$(OS)),) - SOURCES := $(SOURCES) $(shell find "$(SRCDIR)/sys/posix" -type f -name '*.d') -endif -SOURCES := $(SOURCES) $(SRCDIR)/urt/internal/mbedtls.c - -ifeq ($(PLATFORM),riscv64) - SOURCES := $(SOURCES) $(shell find "$(SRCDIR)/sys" -type f -name '*.d' 2>/dev/null) -endif - -# Set target file based on build type and OS -ifeq ($(BUILD_TYPE),exe) - BUILD_CMD_FLAGS := - ifeq ($(OS),windows) - TARGET = $(TARGETDIR)/$(TARGETNAME).exe - else - TARGET = $(TARGETDIR)/$(TARGETNAME) - endif -else # lib - BUILD_CMD_FLAGS := -lib - ifeq ($(OS),windows) - TARGET = $(TARGETDIR)/$(TARGETNAME).lib - else - TARGET = $(TARGETDIR)/lib$(TARGETNAME).a + ifeq ($(PLATFORM),bl808) + ifeq ($(PROCESSOR),c906) + BAREMETAL_LD := platforms/bl808/bl808_d0.ld + else ifeq ($(PROCESSOR),e907) + BAREMETAL_LD := platforms/bl808/bl808_m0.ld endif + else ifeq ($(PLATFORM),bl618) + BAREMETAL_LD := platforms/bl618/bl618.ld + else ifneq ($(filter bk7231n bk7231t,$(PLATFORM)),) + BAREMETAL_LD := platforms/bk7231/$(PLATFORM).ld + else ifeq ($(PLATFORM),rp2350) + BAREMETAL_LD := platforms/rp2350/rp2350.ld + else ifdef STM32_VARIANT + BAREMETAL_LD := platforms/stm32/stm32_$(STM32_VARIANT).ld + endif + ifdef BAREMETAL_LD + DFLAGS := $(DFLAGS) -L-T$(BAREMETAL_LD) -main + endif endif -ifeq ($(D_COMPILER),ldc) - DFLAGS := $(DFLAGS) -I $(SRCDIR) - - ifeq ($(PLATFORM),x86_64) -# DFLAGS := $(DFLAGS) -mtriple=x86_64-linux-gnu - else ifeq ($(PLATFORM),x86) - ifeq ($(OS),windows) - DFLAGS := $(DFLAGS) -mtriple=i686-windows-msvc - else - DFLAGS := $(DFLAGS) -mtriple=i686-linux-gnu - endif - else ifeq ($(PLATFORM),arm64) - DFLAGS := $(DFLAGS) -mtriple=aarch64-linux-gnu - else ifeq ($(PLATFORM),arm) - DFLAGS := $(DFLAGS) -mtriple=arm-linux-eabihf -mcpu=cortex-a7 - else ifeq ($(PLATFORM),riscv64) - # we are building the Sipeed M1s device... which is BL808 as I understand - DFLAGS := $(DFLAGS) -mtriple=riscv64-unknown-elf -mcpu=c906 -mattr=+m,+a,+f,+c,+v - else - $(error "Unsupported platform: $(PLATFORM)") - endif - - ifeq ($(CONFIG),release) - DFLAGS := $(DFLAGS) -release -O3 -enable-inlining - else - DFLAGS := $(DFLAGS) -g -d-debug - endif -else ifeq ($(D_COMPILER),dmd) - DFLAGS := $(DFLAGS) -I=$(SRCDIR) - - ifeq ($(PLATFORM),x86_64) -# DFLAGS := $(DFLAGS) -m64 - else ifeq ($(PLATFORM),x86) - DFLAGS := $(DFLAGS) -m32 - else - $(error "Unsupported platform: $(PLATFORM)") - endif - - ifeq ($(CONFIG),release) - DFLAGS := $(DFLAGS) -release -O -inline - else - DFLAGS := $(DFLAGS) -g -debug - endif +# Resolve build mode + target file +ifneq ($(filter freertos baremetal,$(OS)),) + ifdef BAREMETAL_LD + BUILD_MODE := embedded-exe + TARGETNAME := urt_test + TARGET := $(TARGETDIR)/$(TARGETNAME) + BUILD_CMD_FLAGS := + else ifeq ($(ARCH),xtensa) + # Xtensa: bitcode for downstream Espressif llc / ESP-IDF + BUILD_MODE := bitcode + TARGETNAME := urt$(if $(filter unittest,$(CONFIG)),_test) + TARGET := $(OBJDIR)/$(TARGETNAME).bc + BUILD_CMD_FLAGS := + else + # No linker script + non-Xtensa cross: compile-only object + BUILD_MODE := compile-only + TARGETNAME := urt$(if $(filter unittest,$(CONFIG)),_test) + TARGET := $(OBJDIR)/$(TARGETNAME).o + BUILD_CMD_FLAGS := + endif +else ifeq ($(CONFIG),unittest) + BUILD_MODE := exe + TARGETNAME := urt_test + BUILD_CMD_FLAGS := -main + ifeq ($(OS),windows) + TARGET := $(TARGETDIR)/$(TARGETNAME).exe + else + TARGET := $(TARGETDIR)/$(TARGETNAME) + endif else - $(error "Unknown D compiler: $(D_COMPILER)") + BUILD_MODE := lib + TARGETNAME := urt + BUILD_CMD_FLAGS := -lib + ifeq ($(OS),windows) + TARGET := $(TARGETDIR)/$(TARGETNAME).lib + else + TARGET := $(TARGETDIR)/lib$(TARGETNAME).a + endif endif -ifeq ($(CONFIG),unittest) - DFLAGS := $(DFLAGS) -unittest -main +DEPFILE := $(OBJDIR)/$(TARGETNAME).d + +# objcopy for embedded-exe -> .bin (derive from cross-gcc path) +ifeq ($(BUILD_MODE),embedded-exe) + ifneq ($(filter arm thumb,$(ARCH)),) + BAREMETAL_OBJCOPY := arm-none-eabi-objcopy + OBJCOPY_FLAGS := -R .bss -R .tbss -R '.tbss.*' -R .ARM.attributes -R '.debug*' + else + BAREMETAL_OBJCOPY := riscv64-unknown-elf-objcopy + OBJCOPY_FLAGS := + endif + + BAREMETAL_OBJS := $(patsubst %.S,$(OBJDIR)/%.o,$(patsubst %.c,$(OBJDIR)/%.o,$(BAREMETAL_SRCS))) + BAREMETAL_CFLAGS := $(BAREMETAL_CFLAGS) -ffreestanding -O2 + +$(OBJDIR)/%.o: $(BAREMETAL_DIR)/%.S + @mkdir -p $(OBJDIR) + $(BAREMETAL_GCC) $(BAREMETAL_CFLAGS) -c -o $@ $< + +$(OBJDIR)/%.o: $(BAREMETAL_DIR)/%.c + @mkdir -p $(OBJDIR) + $(BAREMETAL_GCC) $(BAREMETAL_CFLAGS) -c -o $@ $< endif --include $(DEPFILE) +# ======================================================================= +# Build rule +# ======================================================================= -$(TARGET): +$(TARGET): $(BAREMETAL_OBJS) mkdir -p $(OBJDIR) $(TARGETDIR) -ifeq ($(D_COMPILER),ldc) - "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(TARGET) -od$(OBJDIR) -deps=$(DEPFILE) $(SOURCES) -else ifeq ($(D_COMPILER),dmd) -ifeq ($(BUILD_TYPE),lib) - "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(OBJDIR)/$(notdir $(TARGET)) -od$(OBJDIR) -makedeps $(SOURCES) > $(DEPFILE) +ifeq ($(COMPILER),ldc) + "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(TARGET) -od$(OBJDIR) -deps=$(DEPFILE) $(BAREMETAL_OBJS) $(URT_SOURCES) +else ifeq ($(COMPILER),dmd) +ifeq ($(BUILD_MODE),lib) + "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(OBJDIR)/$(notdir $(TARGET)) -od$(OBJDIR) -makedeps $(URT_SOURCES) > $(DEPFILE) mv "$(OBJDIR)/$(notdir $(TARGET))" "$(TARGETDIR)" -else # exe - "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(TARGET) -od$(OBJDIR) -makedeps $(SOURCES) > $(DEPFILE) +else + "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(TARGET) -od$(OBJDIR) -makedeps $(URT_SOURCES) > $(DEPFILE) endif endif +ifeq ($(BUILD_MODE),embedded-exe) + $(BAREMETAL_OBJCOPY) -O binary $(OBJCOPY_FLAGS) $(TARGET) $(TARGETDIR)/$(TARGETNAME).bin +endif + +# ======================================================================= +# CI: build the full cross-target matrix +# +# Embedded targets produce flashable urt_test images (.bin); ESP variants +# produce LLVM bitcode (no ESP-IDF on build slave). Catches submodule-bump +# breakage before downstream projects update. +# ======================================================================= + +CI_PLATFORMS := \ + esp32 esp32-s2 esp32-s3 esp32-c2 esp32-c3 esp32-c5 esp32-c6 esp32-h2 esp32-p4 \ + bl618 bk7231n bk7231t rp2350 stm4xx stm7xx bl808-d0 bl808-m0 + +.PHONY: ci-build +ci-build: + @set -e; for p in $(CI_PLATFORMS); do \ + case $$p in \ + bl808-d0) args="PLATFORM=bl808 PROCESSOR=c906" ;; \ + bl808-m0) args="PLATFORM=bl808 PROCESSOR=e907" ;; \ + *) args="PLATFORM=$$p" ;; \ + esac; \ + echo "=== ci-build: $$p ($$args) ==="; \ + $(MAKE) --no-print-directory $$args CONFIG=unittest || exit 1; \ + done + @echo "" + @echo "=== ci-build complete ===" + @find bin obj -type f \( -name 'urt_test*.bin' -o -name 'urt_test*.bc' -o -name 'urt_test*.o' \) 2>/dev/null | sort + +# ======================================================================= +# Clean +# ======================================================================= clean: rm -rf $(OBJDIR) $(TARGETDIR) + +clean-all: + rm -rf obj bin diff --git a/platforms.mk b/platforms.mk new file mode 100644 index 0000000..2e6ccb3 --- /dev/null +++ b/platforms.mk @@ -0,0 +1,671 @@ +# ======================================================================= +# URT platform/processor/toolchain configuration +# +# Shared between URT's own Makefile and downstream consumers (OpenWatt etc.). +# Anything that depends on which SoC/core/toolchain we're targeting lives here. +# +# Inputs (set by caller before include): +# PLATFORM - SoC/board (esp32-s3, bl808, bl618, bk7231n, rp2350, stm4xx, ...) +# or OS name (windows, linux, ubuntu, freebsd) for host builds. +# Auto-detected from `uname` if undefined. +# PROCESSOR - CPU core (e907, c906, lx7, cortex-m33, ...). Usually derived +# from PLATFORM; override only for multi-core SoCs (e.g. +# PROCESSOR=e907 for the BL808 M0 core). +# CONFIG - debug | release | unittest +# COMPILER - dmd | ldc | gdc. Auto-promoted to ldc for cross-compile. +# URT_SRCDIR - path to URT's src/ tree. Default `src` (URT in-tree); outer +# projects set this to e.g. `third_party/urt/src`. +# +# Outputs (resolved variables for caller to consume): +# ARCH, OS, MARCH, MATTR, MABI, PROCESSOR, BUILDNAME, COMPILER, DC +# DFLAGS - augmented with triple, mattr, platform version flags +# URT_SOURCES - urt/**.d + sys//**.d (+ mbedtls.c on host) +# BAREMETAL_DIR - dir containing start.S etc. (empty on host targets) +# BAREMETAL_SRCS - basenames of asm/c sources for cross-gcc +# BAREMETAL_GCC - cross-gcc path +# BAREMETAL_CFLAGS - cross-gcc cflags (mcpu/march/mabi/mfpu) +# BAREMETAL_LIBC/M/GCC - resolved newlib/picolibc/libgcc archive paths +# ESPRESSIF_PATH, ESPRESSIF_XTENSA_BIN, ESPRESSIF_RISCV32_BIN +# XTENSA_TWO_STAGE, ESPRESSIF_LLC, XTENSA_MATTR +# +# Caller still owns (NOT set here -- these are app-specific): +# - BAREMETAL_LD: linker script (app memory map lives in the consumer's +# platforms//ld/*.ld tree). +# - `-J` string-imports for app config dirs (consumer's platforms//). +# - Vendor SDK roots and blob paths (BK_SDK_ROOT, ESP_PROJECT_DIR, ...). +# - Final link/objcopy/packaging (containers, .bin, flashing, OTA). +# - The build rule that actually produces $(TARGET). +# ======================================================================= + +URT_SRCDIR ?= src +CONFIG ?= debug +COMPILER ?= dmd + +# Windows always sets env OS=Windows_NT -- normalize so OS-conditional blocks +# (sys/windows source selection, host triple) recognize it. Plain `:=` (not +# `override`) so platform blocks can still set OS=baremetal/freertos for cross. +ifeq ($(OS),Windows_NT) + OS := windows +endif + +# ======================================================================= +# PLATFORM -- SoC/board identity +# +# Sets: BUILDNAME, PROCESSOR (default), OS, vendor version flags, +# platform source paths, vendor-specific GCC wrappers (Xtensa). +# ======================================================================= + +# Normalize CI-friendly platform aliases before platform detection +ifeq ($(PLATFORM),bl808_m0) + override PLATFORM := bl808 + PROCESSOR := e907 +endif + +ifeq ($(PLATFORM),esp8266) + # ESP8266 has no FPU! + BUILDNAME := esp8266 + PROCESSOR := l106 + OS = freertos + XTENSA_GCC := xtensa-lx106-elf-gcc +else ifeq ($(PLATFORM),esp32) + BUILDNAME := esp32 + PROCESSOR := lx6 + OS = freertos + XTENSA_GCC := xtensa-esp32-elf-gcc +else ifeq ($(PLATFORM),esp32-s2) + # Single-core LX7, 240MHz -- NO FPU, NO loops + BUILDNAME := esp32-s2 + PROCESSOR := lx7 + OS = freertos + XTENSA_GCC := xtensa-esp32s2-elf-gcc +else ifeq ($(PLATFORM),esp32-s3) + # Dual-core LX7, 240MHz -- FPU, loops, hardware unaligned access + BUILDNAME := esp32-s3 + PROCESSOR := lx7 + OS = freertos + XTENSA_GCC := xtensa-esp32s3-elf-gcc + MATTR = +fp,+loop + DFLAGS := $(DFLAGS) -d-version=SupportUnaligned +else ifeq ($(PLATFORM),esp32-h2) + BUILDNAME := esp32-h2 + PROCESSOR := e906 + OS = freertos +else ifeq ($(PLATFORM),esp32-c2) + BUILDNAME := esp32-c2 + PROCESSOR := e906 + OS = freertos +else ifeq ($(PLATFORM),esp32-c3) + BUILDNAME := esp32-c3 + PROCESSOR := e906 + OS = freertos +else ifeq ($(PLATFORM),esp32-c5) + # RV32IMAC, 240MHz -- has atomics + BUILDNAME := esp32-c5 + PROCESSOR := e907 + OS = freertos +else ifeq ($(PLATFORM),esp32-c6) + # RV32IMAC, 160MHz -- has atomics + BUILDNAME := esp32-c6 + PROCESSOR := e907 + OS = freertos +else ifeq ($(PLATFORM),esp32-p4) + # HP core: RV32IMAFDCV, 400MHz + BUILDNAME := esp32-p4 + PROCESSOR := esp32p4 + OS = freertos +else ifeq ($(PLATFORM),bl808) + # BL808 multi-core SoC -- default to D0 (C906 RV64GC) + # Override with PROCESSOR=e907 for M0 core (E907 RV32IMAFC) + PROCESSOR ?= c906 + OS = baremetal + ifeq ($(PROCESSOR),c906) + BUILDNAME := bl808-d0 + else ifeq ($(PROCESSOR),e907) + BUILDNAME := bl808-m0 + else + $(error "BL808: unsupported PROCESSOR=$(PROCESSOR) (expected c906 or e907)") + endif +else ifeq ($(PLATFORM),bl618) + # Sipeed M0P -- Bouffalo BL618, single-core T-Head E907 RV32IMAFC, 320MHz + BUILDNAME := bl618 + PROCESSOR := e907 + OS = baremetal +else ifneq ($(filter bk7231n bk7231t,$(PLATFORM)),) + # Beken BK7231 family -- ARM968E-S (ARMv5TE), 120MHz, 256KB SRAM, 2MB SPI flash + # BK7231N adds BLE 5.0; BK7231T is Wi-Fi only. Same MCU peripherals. + BUILDNAME := $(PLATFORM) + PROCESSOR := arm968e-s + OS = baremetal +else ifeq ($(PLATFORM),rp2350) + # Raspberry Pi RP2350 -- dual Cortex-M33, 150MHz, 520KB SRAM, XIP QSPI flash + BUILDNAME := rp2350 + PROCESSOR := cortex-m33 + OS = baremetal +else ifeq ($(PLATFORM),stm7xx) + BUILDNAME := stm7xx + PROCESSOR := cortex-m7 + OS = baremetal + STM32_VARIANT = f7 + MFPU = fpv5-d16 +else ifeq ($(PLATFORM),stm4xx) + BUILDNAME := stm4xx + PROCESSOR := cortex-m4 + OS = baremetal + STM32_VARIANT = f4 + MFPU = fpv4-sp-d16 +else ifeq ($(PLATFORM),routeros) + # MikroTik RouterOS container (ARM64 Linux). Packaging is consumer-side. + BUILDNAME := routeros + PROCESSOR := aarch64-generic + OS := linux +else + ifeq ($(origin PLATFORM),undefined) + # No platform specified -- auto-detect host + UNAME_S := $(shell uname -s 2>/dev/null || echo Unknown) + UNAME_M := $(shell uname -m 2>/dev/null || echo unknown) + + ifneq ($(findstring MINGW,$(UNAME_S)),) + PLATFORM := windows + OS := windows + else ifneq ($(findstring MSYS,$(UNAME_S)),) + PLATFORM := windows + OS := windows + else ifneq ($(findstring CYGWIN,$(UNAME_S)),) + PLATFORM := windows + OS := windows + else ifeq ($(UNAME_S),Unknown) + # no uname, probably native Windows - assume x86_64 + PLATFORM := windows + OS := windows + UNAME_M := x86_64 + else ifeq ($(UNAME_S),) + # cmd.exe / PowerShell: shell couldn't honour `|| echo Unknown` either + OS := windows + UNAME_M := x86_64 + else + OS ?= linux + endif + + PLATFORM := $(OS) + + ifndef ARCH + ifeq ($(UNAME_M),x86_64) + ARCH := x86_64 + else ifeq ($(UNAME_M),amd64) + ARCH := x86_64 + else ifeq ($(UNAME_M),i686) + ARCH := x86 + else ifeq ($(UNAME_M),i386) + ARCH := x86 + else ifeq ($(UNAME_M),aarch64) + ARCH := arm64 + else ifeq ($(UNAME_M),arm64) + ARCH := arm64 + else ifeq ($(UNAME_M),armv7l) + ARCH := arm + else ifeq ($(UNAME_M),riscv64) + ARCH := riscv64 + endif + endif + endif +endif + +# Bare-processor fallback: if PLATFORM was set but didn't match any known +# platform above, treat it as a raw processor name (e.g., make PLATFORM=e906). +ifndef PROCESSOR + ifdef PLATFORM + ifneq ($(PLATFORM),$(OS)) + PROCESSOR := $(PLATFORM) + endif + endif +endif + +# ======================================================================= +# PROCESSOR -- CPU core identity +# +# Sets: ARCH, MARCH, MABI. Pure ISA/compiler-target config. +# OS is set with ?= only as a fallback for bare-processor builds; +# PLATFORM always takes precedence. +# ======================================================================= + +ifdef PROCESSOR + ifeq ($(PROCESSOR),aarch64-generic) + ARCH = arm64 + else ifeq ($(PROCESSOR),cortex-a7) + ARCH = arm + MARCH = cortex-a7 + else ifeq ($(PROCESSOR),cortex-m4) + ARCH = thumb + MARCH = cortex-m4 + else ifeq ($(PROCESSOR),arm968e-s) + ARCH = arm + MARCH = arm968e-s + MABI = soft + OS ?= baremetal + else ifeq ($(PROCESSOR),cortex-m33) + ARCH = thumb + MARCH = cortex-m33 + else ifeq ($(PROCESSOR),cortex-m7) + ARCH = thumb + MARCH = cortex-m7 + else ifeq ($(PROCESSOR),l106) + ARCH = xtensa + else ifeq ($(PROCESSOR),lx6) + ARCH = xtensa + MATTR = +fp,+loop,+mac16,+dfpaccel + else ifeq ($(PROCESSOR),lx7) + ARCH = xtensa + else ifeq ($(PROCESSOR),k210) + ARCH = riscv64 + MARCH = rv64imafdc + MATTR = +m,+a,+f,+d,+c,+zicsr,+zifencei + MABI = lp64d + OS ?= baremetal + else ifeq ($(PROCESSOR),c906) + ARCH = riscv64 + MARCH = rv64imafdc + MATTR = +m,+a,+f,+d,+c,+unaligned-scalar-mem,+xtheadba,+xtheadbb,+xtheadbs,+xtheadcmo,+xtheadcondmov,+xtheadfmemidx,+xtheadmac,+xtheadmemidx,+xtheadsync + MABI = lp64d + OS ?= baremetal + else ifeq ($(PROCESSOR),e902) + ARCH = riscv + MARCH = rv32emc + MATTR = +e,+m,+c + MABI = ilp32e + OS ?= baremetal + else ifeq ($(PROCESSOR),e906) + ARCH = riscv + MARCH = rv32imc + MATTR = +m,+c + MABI = ilp32 + OS ?= freertos + else ifeq ($(PROCESSOR),e907) + ARCH = riscv + MARCH = rv32imafc + MATTR = +m,+a,+f,+c + MABI = ilp32f + OS ?= freertos + else ifeq ($(PROCESSOR),esp32p4) + ARCH = riscv + MARCH = rv32imafdcv + MATTR = +m,+a,+f,+d,+c,+v + MABI = ilp32f + OS ?= freertos + endif +endif + +ifndef BUILDNAME + ifdef PROCESSOR + BUILDNAME := $(PROCESSOR) + else + BUILDNAME := $(ARCH)_$(OS) + endif +endif + +# ======================================================================= +# Compiler auto-selection: cross-compilation targets use LDC +# ======================================================================= + +ifeq ($(COMPILER),dmd) +ifdef ARCH +ifneq ($(ARCH),x86_64) +ifneq ($(ARCH),x86) + COMPILER = ldc +endif +endif +endif +endif + +# ======================================================================= +# Toolchain discovery (Espressif) +# ======================================================================= + +ESPRESSIF_PATH ?= $(wildcard $(HOME)/.espressif) +ifdef ESPRESSIF_PATH + ESPRESSIF_XTENSA_BIN := $(lastword $(sort $(wildcard $(ESPRESSIF_PATH)/tools/xtensa-esp-elf/*/xtensa-esp-elf/bin))) + ESPRESSIF_RISCV32_BIN := $(lastword $(sort $(wildcard $(ESPRESSIF_PATH)/tools/riscv32-esp-elf/*/riscv32-esp-elf/bin))) +endif + +# ======================================================================= +# URT_SOURCES -- urt/**.d + sys//**.d +# +# Caller appends app sources separately. C glue (mbedtls) is host-only. +# ======================================================================= + +URT_SOURCES := $(shell find "$(URT_SRCDIR)" -type f -name '*.d' -not -path '$(URT_SRCDIR)/sys/*') +URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/baremetal" -type f -name '*.d') + +# mbedtls C glue needs host mbedtls headers -- exclude for embedded targets +ifeq ($(filter freertos baremetal,$(OS)),) + URT_SOURCES := $(URT_SOURCES) $(URT_SRCDIR)/urt/internal/mbedtls.c +endif + +ifeq ($(PLATFORM),bl808) + ifeq ($(PROCESSOR),c906) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/bl808" -type f -name '*.d') + else ifeq ($(PROCESSOR),e907) + # BL808 M0 core -- E907 uses same peripheral drivers as BL618 + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/bl618" -type f -name '*.d') + endif +endif +ifeq ($(PLATFORM),bl618) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/bl618" -type f -name '*.d') +endif +ifeq ($(PLATFORM),rp2350) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/rp2350" -type f -name '*.d') +endif +ifneq ($(filter bk7231n bk7231t,$(PLATFORM)),) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/bk7231" -type f -name '*.d' 2>/dev/null) +endif +ifneq ($(filter esp%,$(PLATFORM)),) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/esp32" -type f -name '*.d') +endif +ifdef STM32_VARIANT + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/stm32" -type f -name '*.d') +endif +ifeq ($(OS),windows) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/windows" -type f -name '*.d') +endif +ifneq ($(filter linux ubuntu freebsd,$(OS)),) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/posix" -type f -name '*.d') +endif + +# ======================================================================= +# DFLAGS -- version flags + previews +# +# Only platform/processor *version* flags live here (so URT's sys/ code +# can `version (BL808)` etc.). Consumer-side `-J platforms/` string +# imports stay in the consumer Makefile. +# ======================================================================= + +DFLAGS := $(DFLAGS) -preview=bitfields -preview=rvaluerefparam -preview=in #-preview=nosharedaccess <- TODO + +# OS-level versions +ifeq ($(OS),freertos) + DFLAGS := $(DFLAGS) -d-version=FreeRTOS +endif +ifeq ($(OS),baremetal) + DFLAGS := $(DFLAGS) -d-version=BareMetal +endif +ifneq ($(filter freertos baremetal,$(OS)),) + DFLAGS := $(DFLAGS) -d-version=Embedded +endif + +# "Tiny" targets: <~350KB RAM and <2MB flash, or no external memory at all. +# Code gates nice-to-haves (verbose help, optional protocols) behind +# `version (Tiny)` to minimise binary size. Override with TINY=1/0. +ifneq ($(filter esp8266 bk7231n bk7231t esp32-c2 esp32-h2 esp32-s2,$(PLATFORM)),) + TINY ?= 1 +endif +ifeq ($(PLATFORM),bl808) + ifeq ($(PROCESSOR),e907) + TINY ?= 1 + endif +endif +ifeq ($(TINY),1) + DFLAGS := $(DFLAGS) -d-version=Tiny +endif + +# Vendor/family versions consumed by URT's sys/ code +ifneq ($(filter esp%,$(PLATFORM)),) + DFLAGS := $(DFLAGS) -d-version=Espressif -d-version=lwIP -d-version=CRuntime_Picolibc +endif +ifeq ($(PLATFORM),bl808) + ifeq ($(PROCESSOR),c906) + DFLAGS := $(DFLAGS) -d-version=BL808 -d-version=Bouffalo -d-version=CRuntime_Picolibc + else ifeq ($(PROCESSOR),e907) + DFLAGS := $(DFLAGS) -d-version=BL808 -d-version=BL808_M0 -d-version=Bouffalo -d-version=CRuntime_Picolibc + endif +endif +ifeq ($(PLATFORM),bl618) + DFLAGS := $(DFLAGS) -d-version=BL618 -d-version=Bouffalo -d-version=CRuntime_Picolibc +endif +ifeq ($(PLATFORM),rp2350) + DFLAGS := $(DFLAGS) -d-version=RP2350 -d-version=CRuntime_Picolibc +endif +ifdef STM32_VARIANT + DFLAGS := $(DFLAGS) -d-version=STM32 -d-version=CRuntime_Picolibc + ifeq ($(STM32_VARIANT),f4) + DFLAGS := $(DFLAGS) -d-version=STM32F4 + else ifeq ($(STM32_VARIANT),f7) + DFLAGS := $(DFLAGS) -d-version=STM32F7 + endif +endif +ifneq ($(filter bk7231n bk7231t,$(PLATFORM)),) + DFLAGS := $(DFLAGS) -d-version=Beken -d-version=CRuntime_Picolibc + ifeq ($(PLATFORM),bk7231n) + DFLAGS := $(DFLAGS) -d-version=BK7231N + else + DFLAGS := $(DFLAGS) -d-version=BK7231T + endif +endif + +# Chip-specific versions +ifeq ($(PLATFORM),esp8266) + DFLAGS := $(DFLAGS) -d-version=ESP8266 +else ifeq ($(PLATFORM),esp32) + DFLAGS := $(DFLAGS) -d-version=ESP32 +else ifeq ($(PLATFORM),esp32-s2) + DFLAGS := $(DFLAGS) -d-version=ESP32_S2 +else ifeq ($(PLATFORM),esp32-s3) + DFLAGS := $(DFLAGS) -d-version=ESP32_S3 +else ifeq ($(PLATFORM),esp32-c2) + DFLAGS := $(DFLAGS) -d-version=ESP32_C2 +else ifeq ($(PLATFORM),esp32-c3) + DFLAGS := $(DFLAGS) -d-version=ESP32_C3 +else ifeq ($(PLATFORM),esp32-c5) + DFLAGS := $(DFLAGS) -d-version=ESP32_C5 +else ifeq ($(PLATFORM),esp32-c6) + DFLAGS := $(DFLAGS) -d-version=ESP32_C6 +else ifeq ($(PLATFORM),esp32-h2) + DFLAGS := $(DFLAGS) -d-version=ESP32_H2 +else ifeq ($(PLATFORM),esp32-p4) + DFLAGS := $(DFLAGS) -d-version=ESP32_P4 +endif + +ifeq ($(CONFIG),unittest) + DFLAGS := $(DFLAGS) -unittest +endif + +# ======================================================================= +# Compiler configuration -- triple, mattr, link flags +# ======================================================================= + +ifeq ($(COMPILER),ldc) + # Prefer dlang-installer LDC (avoids system package conflicts with cross-compile) + DC := $(lastword $(sort $(wildcard $(HOME)/dlang/ldc-*/bin/ldc2))) + DC := $(if $(DC),$(DC),ldc2) + + # Strip druntime/phobos -- URT brings its own object.d and runtime support. + # OpenWatt's ldc2.conf does the same via `switches = ["-defaultlib="]` for + # builds invoked from its tree; setting it here too means URT-standalone + # builds (e.g. URT's own CI) don't need their own ldc2.conf. + # + # -frame-pointer=all: keep the frame pointer in every function. URT's + # exception/unwind machinery walks EBP/RBP chains directly (notably on + # x86 Windows SEH); leaf-FPO would leave gaps and crash the walker. + DFLAGS := $(DFLAGS) -defaultlib= -frame-pointer=all -I $(URT_SRCDIR) + + ifeq ($(ARCH),x86_64) +# DFLAGS := $(DFLAGS) -mtriple=x86_64-linux-gnu + else ifeq ($(ARCH),x86) + ifeq ($(OS),windows) + DFLAGS := $(DFLAGS) -mtriple=i686-windows-msvc + else + DFLAGS := $(DFLAGS) -mtriple=i686-linux-gnu + endif + else ifeq ($(ARCH),arm64) + ifeq ($(OS),freertos) + DFLAGS := $(DFLAGS) -mtriple=aarch64-none-elf + else ifeq ($(OS),windows) + DFLAGS := $(DFLAGS) -mtriple=aarch64-windows-msvc + else + DFLAGS := $(DFLAGS) -mtriple=aarch64-linux-gnu + # Cross-compile linker, fully static for minimal container size + DFLAGS := $(DFLAGS) -gcc=aarch64-linux-gnu-gcc -static -L-static + endif + else ifeq ($(ARCH),thumb) + ifeq ($(MARCH),cortex-m33) + DFLAGS := $(DFLAGS) -mtriple=thumbv8m.main-none-eabihf -gcc=arm-none-eabi-gcc + else + DFLAGS := $(DFLAGS) -mtriple=thumbv7em-none-eabihf -gcc=arm-none-eabi-gcc + endif + ifdef MARCH + DFLAGS := $(DFLAGS) -mcpu=$(MARCH) + endif + else ifeq ($(ARCH),arm) + ifeq ($(OS),baremetal) + ifeq ($(MABI),soft) + # ARMv5TE has no atomic instructions -- single-thread model + # makes LLVM lower atomics to plain loads/stores + DFLAGS := $(DFLAGS) -mtriple=armv5te-none-eabi -float-abi=soft --thread-model=single + else + DFLAGS := $(DFLAGS) -mtriple=arm-none-eabihf + endif + else ifeq ($(OS),freertos) + DFLAGS := $(DFLAGS) -mtriple=arm-none-eabihf + else ifeq ($(OS),windows) + DFLAGS := $(DFLAGS) -mtriple=armv7-windows-msvc + else + DFLAGS := $(DFLAGS) -mtriple=armv7-linux-gnueabihf + endif + DFLAGS := $(DFLAGS) -gcc=arm-none-eabi-gcc + ifdef MARCH + DFLAGS := $(DFLAGS) -mcpu=$(MARCH) + endif + else ifeq ($(ARCH),riscv64) + DFLAGS := $(DFLAGS) -mtriple=riscv64-unknown-elf -gcc=riscv64-unknown-elf-gcc -code-model=medium + DFLAGS := $(DFLAGS) -mattr=$(MATTR) + # ImportC needs picolibc headers for C imports (stdio.h etc.) + PICOLIBC_INCLUDE := $(firstword $(wildcard /usr/riscv64-unknown-elf/include /usr/lib/picolibc/riscv64-unknown-elf/include)) + DFLAGS := $(DFLAGS) $(if $(PICOLIBC_INCLUDE),-P=-isystem -P=$(PICOLIBC_INCLUDE)) + else ifeq ($(ARCH),riscv) + RISCV32_GCC ?= $(or $(if $(ESPRESSIF_RISCV32_BIN),$(ESPRESSIF_RISCV32_BIN)/riscv32-esp-elf-gcc),$(shell which riscv32-esp-elf-gcc 2>/dev/null),riscv64-unknown-elf-gcc) + DFLAGS := $(DFLAGS) -mtriple=riscv32-unknown-elf -gcc=$(RISCV32_GCC) + DFLAGS := $(DFLAGS) -mattr=$(MATTR) -mabi=$(MABI) + ifeq ($(PROCESSOR),e902) + DFLAGS := $(DFLAGS) -d-version=RISCV32E + endif + else ifeq ($(ARCH),xtensa) + # Xtensa -- requires Espressif toolchain (chip-specific GCC wrappers). + # Two-stage codegen: LDC emits bitcode, Espressif's llc does codegen + # (upstream LLVM Xtensa backend crashes on invoke+landingpad at -O1+). + XTENSA_GCC_DIR ?= $(or $(if $(ESPRESSIF_XTENSA_BIN),$(ESPRESSIF_XTENSA_BIN)/),$(dir $(shell which xtensa-esp-elf-gcc 2>/dev/null))) + # Base features common to all ESP32 Xtensa cores (LX6, S2 LX7, S3 LX7). + # +fp and +loop are NOT universal -- S2 lacks both. + XTENSA_MATTR := -mattr=+density,+mul16,+mul32,+mul32high,+div32 \ + -mattr=+sext,+nsa,+clamps,+minmax,+bool \ + -mattr=+windowed,+threadptr \ + -mattr=+exception,+interrupt,+highpriinterrupts,+debug + ifdef MATTR + XTENSA_MATTR := $(XTENSA_MATTR) -mattr=$(MATTR) + endif + # Workarounds: + # - single-thread model: no atomic instructions, lower atomics to plain loads/stores + # - emulated TLS: @TPOFF symbol suffixes incompatible with GNU ld + # - align-all-functions=2: ensures 4-byte alignment for l32r literal targets + DFLAGS := $(DFLAGS) -mtriple=xtensa-none-elf --thread-model=single -emulated-tls \ + --align-all-functions=2 $(XTENSA_MATTR) \ + -gcc=$(XTENSA_GCC_DIR)$(XTENSA_GCC) + ESPRESSIF_LLC := $(lastword $(sort $(wildcard $(HOME)/.espressif/tools/esp-clang/*/esp-clang/bin/llc))) + XTENSA_TWO_STAGE := 1 + else + $(error "Unsupported ARCH: $(ARCH)") + endif + + # Embedded baremetal: assemble cross-gcc flags + libc/libm/libgcc paths. + # Caller supplies BAREMETAL_LD (linker script) -- without it we emit `-c` + # (compile-only; sufficient for CI link-check of URT-only builds). + ifneq ($(filter freertos baremetal,$(OS)),) + ifeq ($(PLATFORM),bl808) + ifeq ($(PROCESSOR),c906) + BAREMETAL_DIR := $(URT_SRCDIR)/sys/bl808 + BAREMETAL_SRCS := start.S hbn_ram.c + else ifeq ($(PROCESSOR),e907) + BAREMETAL_DIR := $(URT_SRCDIR)/sys/bl618 + BAREMETAL_SRCS := start.S + endif + else ifeq ($(PLATFORM),bl618) + BAREMETAL_DIR := $(URT_SRCDIR)/sys/bl618 + BAREMETAL_SRCS := start.S + else ifneq ($(filter bk7231n bk7231t,$(PLATFORM)),) + BAREMETAL_DIR := $(URT_SRCDIR)/sys/bk7231 + BAREMETAL_SRCS := start.S + else ifeq ($(PLATFORM),rp2350) + BAREMETAL_DIR := $(URT_SRCDIR)/sys/rp2350 + BAREMETAL_SRCS := start.S boot2.S + else ifdef STM32_VARIANT + BAREMETAL_DIR := $(URT_SRCDIR)/sys/stm32 + BAREMETAL_SRCS := start.S + endif + + ifdef BAREMETAL_DIR + ifeq ($(ARCH),arm) + BAREMETAL_GCC := arm-none-eabi-gcc + BAREMETAL_CFLAGS := -mcpu=$(MARCH) -marm -mfloat-abi=$(MABI) + else ifeq ($(ARCH),thumb) + BAREMETAL_GCC := arm-none-eabi-gcc + MFPU ?= fpv5-sp-d16 + BAREMETAL_CFLAGS := -mcpu=$(MARCH) -mthumb -mfloat-abi=hard -mfpu=$(MFPU) + else + BAREMETAL_GCC := riscv64-unknown-elf-gcc + BAREMETAL_CFLAGS := -march=$(MARCH) -mabi=$(MABI) + endif + BAREMETAL_LIBGCC := $(shell $(BAREMETAL_GCC) $(BAREMETAL_CFLAGS) --print-libgcc-file-name) + # picolibc/newlib via --specs=picolibc.specs first, then plain gcc, then multilib fallback + PICOLIBC_MULTIDIR := $(shell $(BAREMETAL_GCC) $(BAREMETAL_CFLAGS) --print-multi-directory 2>/dev/null) + BAREMETAL_LIBC := $(or $(filter /%,$(shell $(BAREMETAL_GCC) --specs=picolibc.specs $(BAREMETAL_CFLAGS) --print-file-name=libc.a 2>/dev/null)),$(filter /%,$(shell $(BAREMETAL_GCC) $(BAREMETAL_CFLAGS) --print-file-name=libc.a 2>/dev/null)),$(wildcard /usr/lib/picolibc/riscv64-unknown-elf/lib/$(PICOLIBC_MULTIDIR)/libc.a)) + BAREMETAL_LIBM := $(or $(filter /%,$(shell $(BAREMETAL_GCC) --specs=picolibc.specs $(BAREMETAL_CFLAGS) --print-file-name=libm.a 2>/dev/null)),$(filter /%,$(shell $(BAREMETAL_GCC) $(BAREMETAL_CFLAGS) --print-file-name=libm.a 2>/dev/null)),$(wildcard /usr/lib/picolibc/riscv64-unknown-elf/lib/$(PICOLIBC_MULTIDIR)/libm.a)) + # Caller adds: -L-T + any vendor blob archives + DFLAGS := $(DFLAGS) -L--gc-sections --link-internally -L-z -Lnorelro -L$(BAREMETAL_LIBC) -L$(BAREMETAL_LIBM) -L$(BAREMETAL_LIBGCC) + else ifeq ($(ARCH),xtensa) + # Xtensa: emit LLVM bitcode for two-stage codegen via Espressif's llc + # (upstream LLVM Xtensa backend crashes on invoke+landingpad at -O1+). + DFLAGS := $(DFLAGS) --output-bc + else + # No link script wired up (e.g. ESP RISC-V variants without ESP-IDF): + # compile-only object output. + DFLAGS := $(DFLAGS) -c + endif + endif + + ifeq ($(CONFIG),release) + ifeq ($(ARCH),xtensa) + DFLAGS := $(DFLAGS) -release --enable-asserts -Oz -enable-inlining + else + DFLAGS := $(DFLAGS) -release --enable-asserts -O3 -enable-inlining + endif + else ifdef BAREMETAL_DIR + # Embedded debug/unittest: still optimize to fit in firmware partition + DFLAGS := $(DFLAGS) --enable-asserts -O2 -enable-inlining + else ifeq ($(ARCH),xtensa) + # Xtensa: -Oz to fit in flash; bitcode emission set above + DFLAGS := $(DFLAGS) --enable-asserts -Oz -enable-inlining -d-debug + else + DFLAGS := $(DFLAGS) -g -d-debug + endif + +else ifeq ($(COMPILER),dmd) + DC ?= dmd + + # Strip druntime/phobos, use URT's own object.d. + # Consumers may need to prepend their own -I to shadow druntime's + # __importc_builtins.di (e.g. OpenWatt's third_party/dmd/ for MSVC va_list). + DFLAGS := $(DFLAGS) -defaultlib= -I=$(URT_SRCDIR) + + ifeq ($(ARCH),x86_64) +# DFLAGS := $(DFLAGS) -m64 + else ifeq ($(ARCH),x86) + DFLAGS := $(DFLAGS) -m32 + else + $(error "DMD: unsupported ARCH=$(ARCH) for PLATFORM=$(PLATFORM) (use COMPILER=ldc)") + endif + + ifeq ($(CONFIG),release) + DFLAGS := $(DFLAGS) -release -O -inline + else + DFLAGS := $(DFLAGS) -g -debug + endif +else + $(error "Unknown D compiler: $(COMPILER)") +endif diff --git a/platforms/bk7231/README.txt b/platforms/bk7231/README.txt new file mode 100644 index 0000000..e10ad6d --- /dev/null +++ b/platforms/bk7231/README.txt @@ -0,0 +1,90 @@ +BK7231 (Beken) -- ARM968E-S @ 120 MHz, ARMv5TE +============================================== + +Two variants in this directory: + + bk7231n Wi-Fi 802.11b/g/n + BLE 5.0 + bk7231t Wi-Fi only (no BLE) + +Both share the same MCU core and most peripherals. SRAM layout differs +(N reserves TCM regions for the BLE stack; T uses full SRAM). They are +NOT interchangeable -- flashing the wrong image will not boot. + +Setup +----- + +Toolchain (Ubuntu 22.04+): + + # D compiler -- LDC with the official upstream build. + curl -fsS https://dlang.org/install.sh | bash -s ldc + source ~/dlang/ldc-*/activate + + # ARM cross-toolchain and picolibc. + sudo apt-get install gcc-arm-none-eabi picolibc-arm-none-eabi + +Flash tool (Python; bundled with OpenBK7231T_App): + + git clone https://github.com/openshwprojects/OpenBK7231T_App + pip install pyserial # only dependency hid_download.py needs + +The flasher is at OpenBK7231T_App/scripts/hid_download.py -- run it +directly, no install step. + +Windows: LDC installer from https://github.com/ldc-developers/ldc/releases; +ARM GNU Toolchain installer from +https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads. + +Build the unittest image +------------------------ + +From the URT root: + + make PLATFORM=bk7231n CONFIG=unittest + make PLATFORM=bk7231t CONFIG=unittest + +Outputs: + + bin/bk7231n_unittest/urt_test.bin + bin/bk7231t_unittest/urt_test.bin + +Toolchain required: ldc, gcc-arm-none-eabi, picolibc-arm-none-eabi. +Linker scripts: bk7231n.ld (192 KB SRAM, BLE TCM carve-out) and +bk7231t.ld (256 KB SRAM, no carve-out). + +This URT-only build does NOT include the Beken SDK -- no Wi-Fi, no BLE, +no SDK-provided UART driver beyond what URT ships. The full OpenWatt +build links against libbeken.a + WiFi/BLE blobs; the URT unittest does +not. The image will boot, run unittests, print results to UART, halt. + +Flash +----- + +Use hid_download_py from the OpenBK7231T_App project: + + git clone https://github.com/openshwprojects/OpenBK7231T_App + cd OpenBK7231T_App/scripts + python hid_download.py -d COM5 -f bin/bk7231n_unittest/urt_test.bin \ + -c bk7231n -a 0x011000 + +(Use bk7231t / -a 0x011000 for the T variant.) + +The bootloader expects the application at offset 0x11000 in flash. Hold +the CEN/RESET button while invoking the tool to enter download mode. + +Console +------- + +UART1 (the SDK calls it "uart_print"), 921600 baud, 8N1. Pins are board- +specific; on most modules UART1 is broken out to a header. The unittest +harness prints results then halts. + +Notes +----- + +* ARMv5TE has no atomic instructions. URT's start.S sets the LLVM + thread-model to "single" so atomics lower to plain loads/stores -- + fine for single-core unittests, NOT safe if you ever bring up + the SDK's RTOS scheduler alongside URT code. +* Vector table lives in the bootloader (no VTOR on ARM968E-S) -- IRQs + must be installed via SDK API in real applications. The URT unittest + runs polled, so this is not exercised. diff --git a/platforms/bk7231/bk7231n.ld b/platforms/bk7231/bk7231n.ld new file mode 100644 index 0000000..411821e --- /dev/null +++ b/platforms/bk7231/bk7231n.ld @@ -0,0 +1,139 @@ +/* BK7231N (ARM968E-S, ARMv5TE) linker script + * + * Beken BK7231N -- ARM968E-S @ 120MHz, no FPU, no MMU. + * Wi-Fi 802.11b/g/n + BLE 5.0. + * + * Flash partition table (from BkDriverFlash.c): + * Bootloader: 0x000000, 68KB (0x11000) + * Application: 0x011000, 1156KB (0x121000) + * OTA: 0x12A000, 664KB (0xA6000) + * RF Firmware: 0x1D0000, 4KB + * NET Param: 0x1D1000, 4KB + * + * SRAM layout (OTA build, from SDK bk7231_ota.ld): + * TCM: 0x003F0000, 60KB - 512 (BLE/WiFi critical paths) + * ITCM: 0x003FEE00, 4608 bytes (interrupt entry) + * RAM: 0x00400100, 192KB - 256 (application) + * + * The BLE 5.x stack claims 64KB from general SRAM as TCM regions. + * Application gets 192KB minus the 256-byte reserved header. + * + * Non-OTA: set FLASH LENGTH to 1788K (up to RF firmware at 0x1D0000). + */ + +ENTRY(Reset_Handler) + +EXTERN(_write _read _close _lseek _fstat _isatty _sbrk _exit _kill _getpid) +EXTERN(_Dmodule_ref __errno_location stdout) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x00011000, LENGTH = 4M /* TODO: restore to 1156K */ + SRAM (rwx) : ORIGIN = 0x00400100, LENGTH = 192K - 0x100 +} + +SECTIONS +{ + .text : ALIGN(4) + { + KEEP(*(.vector_table)) + *(.text .text.*) + } > FLASH + + .rodata : ALIGN(8) + { + *(.rodata .rodata.*) + } > FLASH + + .eh_frame : ALIGN(8) + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + KEEP(*(.eh_frame_hdr)) + } > FLASH + + .gcc_except_table : ALIGN(4) + { + *(.gcc_except_table .gcc_except_table.*) + } > FLASH + + .init_array : ALIGN(4) + { + __init_array_start = .; + KEEP(*(.init_array)) + __init_array_end = .; + } > FLASH + + .ARM.exidx : ALIGN(4) + { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } > FLASH + + .ARM.extab : ALIGN(4) + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + .got : ALIGN(4) + { + _got_load = LOADADDR(.got); + _got_start = .; + *(.got .got.*) + *(.got.plt) + _got_end = .; + } > SRAM AT > FLASH + + .data : ALIGN(4) + { + _data_start = .; + *(.data .data.*) + _data_end = .; + } > SRAM AT > FLASH + + _data_load = LOADADDR(.data); + __ram_load_addr = _data_load; + __ram_data_start__ = _data_start; + __ram_data_end__ = _data_end; + + .tdata : ALIGN(4) + { + _tdata_start = .; + *(.tdata .tdata.*) + _tdata_end = .; + } > SRAM AT > FLASH + + _tdata_load = LOADADDR(.tdata); + + .tbss (NOLOAD) : ALIGN(4) + { + _tbss_start = .; + *(.tbss .tbss.*) + _tbss_end = .; + } > SRAM + + .bss (NOLOAD) : ALIGN(4) + { + __bss_start__ = .; + _bss_start = .; + *(.bss .bss.*) + *(COMMON) + _bss_end = .; + __bss_end__ = .; + } > SRAM + + __heap_start = _bss_end; + __heap_end = ORIGIN(SRAM) + LENGTH(SRAM) - 4K; + + _stack_top = ORIGIN(SRAM) + LENGTH(SRAM); + __StackTop = _stack_top; + + /DISCARD/ : + { + *(.comment) + *(.note.*) + *(.gnu.hash) + *(.gnu.linkonce.*) + } +} diff --git a/platforms/bk7231/bk7231t.ld b/platforms/bk7231/bk7231t.ld new file mode 100644 index 0000000..73b9222 --- /dev/null +++ b/platforms/bk7231/bk7231t.ld @@ -0,0 +1,136 @@ +/* BK7231T (ARM968E-S, ARMv5TE) linker script + * + * Beken BK7231T -- ARM968E-S @ 120MHz, no FPU, no MMU. + * Wi-Fi 802.11b/g/n only (no BLE). + * + * Flash partition table (from BkDriverFlash.c): + * Bootloader: 0x000000, 68KB (0x11000) + * Application: 0x011000, 1156KB (0x121000) + * OTA: 0x132000, 600KB (0x96000) + * RF Firmware: 0x1E0000, 4KB + * NET Param: 0x1E1000, 4KB + * + * SRAM layout (OTA build, from SDK bk7231_ota.ld): + * RAM: 0x00400020, 256KB - 32 (full SRAM minus 32-byte header) + * + * No BLE stack means no TCM carve-out -- application gets full 256KB SRAM. + * + * Non-OTA: set FLASH LENGTH to 1812K (up to RF firmware at 0x1E0000). + */ + +ENTRY(Reset_Handler) + +EXTERN(_write _read _close _lseek _fstat _isatty _sbrk _exit _kill _getpid) +EXTERN(_Dmodule_ref __errno_location stdout) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x00011000, LENGTH = 1156K + SRAM (rwx) : ORIGIN = 0x00400020, LENGTH = 256K - 0x20 +} + +SECTIONS +{ + .text : ALIGN(4) + { + KEEP(*(.vector_table)) + *(.text .text.*) + } > FLASH + + .rodata : ALIGN(8) + { + *(.rodata .rodata.*) + } > FLASH + + .eh_frame : ALIGN(8) + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + KEEP(*(.eh_frame_hdr)) + } > FLASH + + .gcc_except_table : ALIGN(4) + { + *(.gcc_except_table .gcc_except_table.*) + } > FLASH + + .init_array : ALIGN(4) + { + __init_array_start = .; + KEEP(*(.init_array)) + __init_array_end = .; + } > FLASH + + .ARM.exidx : ALIGN(4) + { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } > FLASH + + .ARM.extab : ALIGN(4) + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + .got : ALIGN(4) + { + _got_load = LOADADDR(.got); + _got_start = .; + *(.got .got.*) + *(.got.plt) + _got_end = .; + } > SRAM AT > FLASH + + .data : ALIGN(4) + { + _data_start = .; + *(.data .data.*) + _data_end = .; + } > SRAM AT > FLASH + + _data_load = LOADADDR(.data); + __ram_load_addr = _data_load; + __ram_data_start__ = _data_start; + __ram_data_end__ = _data_end; + + .tdata : ALIGN(4) + { + _tdata_start = .; + *(.tdata .tdata.*) + _tdata_end = .; + } > SRAM AT > FLASH + + _tdata_load = LOADADDR(.tdata); + + .tbss (NOLOAD) : ALIGN(4) + { + _tbss_start = .; + *(.tbss .tbss.*) + _tbss_end = .; + } > SRAM + + .bss (NOLOAD) : ALIGN(4) + { + __bss_start__ = .; + _bss_start = .; + *(.bss .bss.*) + *(COMMON) + _bss_end = .; + __bss_end__ = .; + } > SRAM + + __heap_start = _bss_end; + __heap_end = ORIGIN(SRAM) + LENGTH(SRAM) - 4K; + + _stack_top = ORIGIN(SRAM) + LENGTH(SRAM); + __StackTop = _stack_top; + + /DISCARD/ : + { + *(.comment) + *(.note.*) + *(.gnu.hash) + *(.gnu.linkonce.*) + } +} diff --git a/platforms/bl618/README.txt b/platforms/bl618/README.txt new file mode 100644 index 0000000..74b12e2 --- /dev/null +++ b/platforms/bl618/README.txt @@ -0,0 +1,74 @@ +BL618 (Bouffalo Lab) -- T-Head E907 RV32IMAFC @ 320 MHz +======================================================== + +Reference board: Sipeed M0P Dock. + +Setup +----- + +Toolchain (Ubuntu 22.04+): + + # D compiler -- LDC with the official upstream build (includes RISC-V). + # The Ubuntu apt 'ldc' package also works but may lag the latest release. + curl -fsS https://dlang.org/install.sh | bash -s ldc + source ~/dlang/ldc-*/activate + + # RISC-V cross-toolchain and picolibc + sudo apt-get install gcc-riscv64-unknown-elf picolibc-riscv64-unknown-elf + +Flash tool (Python, host-side; no target dependency): + + pip install bflb-mcu-tool + +Windows: LDC installer from https://github.com/ldc-developers/ldc/releases. +RISC-V toolchain from the xpack distribution +(https://xpack.github.io/dev-tools/riscv-none-elf-gcc/) -- put its bin +directory on PATH; picolibc is bundled. Or use WSL2 with the Ubuntu +instructions above. + +Build the unittest image +------------------------ + +From the URT root: + + make PLATFORM=bl618 CONFIG=unittest + +Outputs (in bin/bl618_unittest/): + + urt_test ELF with debug symbols + urt_test.bin Raw binary, ready to flash + +Toolchain required: ldc, gcc-riscv64-unknown-elf, picolibc-riscv64-unknown-elf. +Linker script: third_party/urt/platforms/bl618/bl618.ld (uses flash XIP from +0xA0000000, OCRAM at 0x22020000, DTCM at 0x20000000). + +Flash +----- + +Bouffalo's official tool is BLDevCube (GUI) or bflb-mcu-tool (CLI): + + pip install bflb-mcu-tool + bflb-mcu-tool --chipname bl616 --interface uart \ + --port /dev/ttyUSB0 --baudrate 2000000 \ + --firmware bin/bl618_unittest/urt_test.bin \ + --addr 0x0 + +Hold BOOT, tap RESET, release BOOT to enter the ROM bootloader before +running the command. The chipname is "bl616" -- BL618 is a pin/package +variant of the same die, the tool only knows the bl616 family identifier. + +Console +------- + +UART0 on the default pins (GPIO14 TX, GPIO15 RX on the M0P Dock, exposed +on the onboard USB-serial bridge). 2 Mbaud, 8N1. The unittest harness +prints test results to stdout, then halts; tap RESET to re-run. + +Notes +----- + +* The memory map in bl618.ld is from the BL616 reference manual and may + need tweaking for non-Sipeed boards. Verify against the vendor BSP for + your specific carrier. +* No FreeRTOS, no SDK -- bare metal. URT brings its own start.S, syscall + stubs, UART driver, and IRQ table. diff --git a/platforms/bl618/bl618.ld b/platforms/bl618/bl618.ld new file mode 100644 index 0000000..fd8d9c6 --- /dev/null +++ b/platforms/bl618/bl618.ld @@ -0,0 +1,136 @@ +/* BL618 (T-Head E907 RV32IMAFC) linker script + * + * Single-core Bouffalo BL616/BL618 — no M0/D0 split. + * Code executes from flash via XIP or from OCRAM. + * + * Memory layout: + * FLASH XIP: 0xA0000000, 8MB (execute-in-place, read-only) + * OCRAM: 0x62020000, 480KB (on-chip RAM, uncached) + * OCRAM$: 0x22020000, 480KB (same RAM, cached alias) + * DTCM: 0x20000000, 64KB (tightly-coupled data memory) + * + * TODO: Verify memory map against actual BL618 hardware. + * The addresses below are from the BL616 reference manual + * and may need adjustment for the Sipeed M0P board. + */ + +ENTRY(_start) + +/* Force-link newlib syscall stubs and D runtime symbols */ +EXTERN(_write _read _close _lseek _fstat _isatty _sbrk _exit _kill _getpid) +EXTERN(_Dmodule_ref __errno_location stdout) + +MEMORY +{ + /* Flash XIP — code and read-only data */ + FLASH (rx) : ORIGIN = 0xA0000000, LENGTH = 8M + /* DTCM — fast tightly-coupled memory for stack and small data */ + DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K + /* OCRAM — main writable RAM (cached alias for performance) */ + RAM (rwx) : ORIGIN = 0x22020000, LENGTH = 480K +} + +SECTIONS +{ + .text : ALIGN(4) + { + KEEP(*(.text.entry)) + . = ALIGN(4); + *(.text .text.*) + } > FLASH + + .rodata : ALIGN(8) + { + *(.rodata .rodata.*) + } > FLASH + + .eh_frame : ALIGN(8) + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + KEEP(*(.eh_frame_hdr)) + } > FLASH + + .gcc_except_table : ALIGN(4) + { + *(.gcc_except_table .gcc_except_table.*) + } > FLASH + + .init_array : ALIGN(4) + { + __init_array_start = .; + KEEP(*(.init_array)) + __init_array_end = .; + } > FLASH + + /* GOT and small data in DTCM for fast access. + * LMA in FLASH, start.S copies to DTCM at boot. */ + .got : ALIGN(4) + { + PROVIDE(__global_pointer$ = . + 0x800); + *(.got .got.*) + *(.got.plt) + *(.sdata .sdata.*) + *(.srodata .srodata.*) + } > DTCM AT > FLASH + + _got_load = LOADADDR(.got); + _got_start = ADDR(.got); + _got_end = _got_start + SIZEOF(.got); + + .data : ALIGN(4) + { + *(.data .data.*) + } > RAM AT > FLASH + + _data_load = LOADADDR(.data); + _data_start = ADDR(.data); + _data_end = _data_start + SIZEOF(.data); + __ram_load_addr = _data_load; + __ram_data_start__ = _data_start; + __ram_data_end__ = _data_end; + + .bss (NOLOAD) : ALIGN(4) + { + __bss_start__ = .; + _bss_start = .; + *(.bss .bss.*) + *(.sbss .sbss.*) + *(COMMON) + _bss_end = .; + __bss_end__ = .; + } > RAM + + /* Thread-local data/BSS in DTCM */ + .tdata : ALIGN(4) + { + *(.tdata .tdata.*) + } > DTCM AT > FLASH + + _tdata_load = LOADADDR(.tdata); + _tdata_start = ADDR(.tdata); + _tdata_end = _tdata_start + SIZEOF(.tdata); + + .tbss (NOLOAD) : ALIGN(4) + { + _tbss_start = .; + *(.tbss .tbss.*) + _tbss_end = .; + } > DTCM + + /* Heap in OCRAM after BSS */ + __heap_start = _bss_end; + __heap_end = ORIGIN(RAM) + LENGTH(RAM); + + /* Stack in DTCM (fast access) */ + _stack_top = ORIGIN(DTCM) + LENGTH(DTCM); + __StackTop = _stack_top; + + /DISCARD/ : + { + *(.comment) + *(.note.*) + *(.gnu.hash) + *(.gnu.linkonce.*) + } +} diff --git a/platforms/bl808/README.txt b/platforms/bl808/README.txt new file mode 100644 index 0000000..b615895 --- /dev/null +++ b/platforms/bl808/README.txt @@ -0,0 +1,94 @@ +BL808 (Bouffalo Lab) -- multi-core SoC +====================================== + +Two heterogeneous cores in one package: + + D0 T-Head C906 RV64GC @ 480 MHz (application/multimedia) + M0 T-Head E907 RV32IMAFC @ 320 MHz (boot core, MCU domain) + +Reference board: Sipeed M1s Dock. + +There is also an LP core (E902) in the LP domain; URT does not target it. + +Setup +----- + +Toolchain (Ubuntu 22.04+): + + # D compiler -- LDC with the official upstream build. + curl -fsS https://dlang.org/install.sh | bash -s ldc + source ~/dlang/ldc-*/activate + + # RISC-V cross-toolchain and picolibc (covers both D0 RV64 and M0 RV32). + sudo apt-get install gcc-riscv64-unknown-elf picolibc-riscv64-unknown-elf + +Flash tool (Python, host-side): + + pip install bflb-mcu-tool + +The C906 (D0) core uses the T-Head xthead extensions; recent +gcc-riscv64-unknown-elf and LDC both accept the +xthead* mattr flags. +If you see "unsupported mattr" errors, your toolchain is too old -- +upgrade to Ubuntu 24.04 or install LDC 1.36+ from the upstream tarball. + +Windows: LDC installer from https://github.com/ldc-developers/ldc/releases; +xpack RISC-V toolchain (https://xpack.github.io/dev-tools/riscv-none-elf-gcc/); +or use WSL2 with the Ubuntu instructions above. Older xpack builds may +not include the xthead extensions needed for C906; if so, use the +T-Head toolchain from https://www.xrvm.cn/community/download. + +Build the unittest image +------------------------ + +From the URT root: + + make PLATFORM=bl808 PROCESSOR=c906 CONFIG=unittest (D0 image) + make PLATFORM=bl808 PROCESSOR=e907 CONFIG=unittest (M0 image) + +Outputs: + + bin/bl808-d0_unittest/urt_test.bin D0 firmware, loads to PSRAM + bin/bl808-m0_unittest/urt_test.bin M0 firmware, runs XIP from flash + +Linker scripts: bl808_d0.ld (D0 expects to run from PSRAM at 0x50100000; +M0 firmware copies it from flash at boot). bl808_m0.ld (M0 runs XIP from +flash at 0x58000000). + +Boot dependency +--------------- + +D0 cannot boot standalone -- only M0 starts at power-on. Before D0 can +fetch its first instruction, M0 must: + + 1. Initialize clocks and PLLs. + 2. Bring up the flash controller and PSRAM controller. + 3. Copy the D0 firmware from flash (typically 0x580F0000) to PSRAM. + 4. Release D0 from reset by writing its boot-address register. + +This means a D0-only urt_test.bin is a valid build artifact but is NOT +flashable on its own. To run the D0 unittests on hardware, flash both +images together: an M0 firmware that performs the handoff, plus the D0 +image at the address M0 expects. + +Flash +----- + +Same tool as BL618, with chipname=bl808: + + pip install bflb-mcu-tool + bflb-mcu-tool --chipname bl808 --interface uart \ + --port /dev/ttyUSB0 --baudrate 2000000 \ + --firmware bin/bl808-m0_unittest/urt_test.bin \ + --addr 0x58000000 + +For a paired D0+M0 image, flash D0 at 0x580F0000 in the same command +(refer to BLDevCube's partition table editor for the proper layout). + +Hold BOOT, tap RESET, release BOOT to enter ROM bootloader. + +Console +------- + +UART0 on the default pins (exposed via the onboard USB-serial bridge on +the M1s Dock). 2 Mbaud, 8N1. M0 brings up UART early; D0 prints once it +has been released and reaches main(). diff --git a/platforms/bl808/bl808_d0.ld b/platforms/bl808/bl808_d0.ld new file mode 100644 index 0000000..5332f76 --- /dev/null +++ b/platforms/bl808/bl808_d0.ld @@ -0,0 +1,160 @@ +/* BL808 D0 core (T-Head C906 RV64GC) linker script + * + * M0 copies D0 firmware from Flash (0x580F0000) to PSRAM and starts D0 + * executing from PSRAM at 0x50100000. This is NOT XIP — code runs from RAM. + * + * Memory layout: + * CODE PSRAM: 0x50100000, 2MB (D0 firmware, copied by M0 from Flash) + * DATA PSRAM: 0x50300000, 61MB (writable data, BSS, heap) + * SRAM: 0x3eff8000, 64KB (stack, GOT, fast data) + */ + +ENTRY(_start) + +/* Force-link newlib syscall stubs and D runtime symbols + * (otherwise --gc-sections strips them before newlib resolves) */ +EXTERN(_write _read _close _lseek _fstat _isatty _sbrk _exit _kill _getpid) +EXTERN(_Dmodule_ref __errno_location stdout) +EXTERN(socket close poll _accept _recv _recvfrom _sendmsg) +EXTERN(_shutdown _bind _listen _connect setsockopt getsockname getpeername) +EXTERN(getaddrinfo freeaddrinfo) + +MEMORY +{ + /* D0 firmware is loaded by M0 to PSRAM at this address */ + CODE (rx) : ORIGIN = 0x50100000, LENGTH = 2M + /* Writable data starts after the 2MB firmware region */ + DATA (rwx) : ORIGIN = 0x50300000, LENGTH = 61M + SRAM (rwx) : ORIGIN = 0x3eff8000, LENGTH = 64K + /* HBN RAM: 4KB, survives hibernate if VBAT maintained */ + HBNRAM (rw) : ORIGIN = 0x20010000, LENGTH = 4K +} + +SECTIONS +{ + .text : ALIGN(4) + { + KEEP(*(.text.entry)) + . = ALIGN(4); + *(.text.psram_early) + *(.text .text.*) + } > CODE + + .rodata : ALIGN(8) + { + *(.rodata .rodata.*) + } > CODE + + /* DWARF exception handling — required for D throw/catch (fibre abort, etc.). + * eh_frame: stack frame descriptions for the unwinder. + * gcc_except_table: LSDA (landing pads, catch clause type filters). */ + .eh_frame : ALIGN(8) + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + KEEP(*(.eh_frame_hdr)) + } > CODE + + .gcc_except_table : ALIGN(4) + { + *(.gcc_except_table .gcc_except_table.*) + } > CODE + + /* Init array — LDC module info registration functions. + * Called from start.S before main(). */ + .init_array : ALIGN(8) + { + __init_array_start = .; + KEEP(*(.init_array)) + __init_array_end = .; + } > CODE + + /* GOT and small data in fast SRAM. + * LMA is in CODE (PSRAM), start.S copies to SRAM at boot. */ + .got : ALIGN(8) + { + PROVIDE(__global_pointer$ = . + 0x800); + *(.got .got.*) + *(.got.plt) + *(.sdata .sdata.*) + *(.srodata .srodata.*) + } > SRAM AT > CODE + + _got_load = LOADADDR(.got); + _got_start = ADDR(.got); + _got_end = _got_start + SIZEOF(.got); + + .data : ALIGN(8) + { + *(.data .data.*) + } > DATA AT > CODE + + _data_load = LOADADDR(.data); + _data_start = ADDR(.data); + _data_end = _data_start + SIZEOF(.data); + __ram_load_addr = _data_load; + __ram_data_start__ = _data_start; + __ram_data_end__ = _data_end; + + .bss (NOLOAD) : ALIGN(8) + { + __bss_start__ = .; + _bss_start = .; + *(.bss .bss.*) + *(.sbss .sbss.*) + *(COMMON) + _bss_end = .; + __bss_end__ = .; + } > DATA + + /* Thread-local data/BSS in SRAM */ + .tdata : ALIGN(8) + { + *(.tdata .tdata.*) + } > SRAM AT > CODE + + _tdata_load = LOADADDR(.tdata); + _tdata_start = ADDR(.tdata); + _tdata_end = _tdata_start + SIZEOF(.tdata); + + .tbss (NOLOAD) : ALIGN(8) + { + _tbss_start = .; + *(.tbss .tbss.*) + _tbss_end = .; + } > SRAM + + /* HBN RAM: persistent across hibernate, not initialized by startup */ + .hbn_ram (NOLOAD) : ALIGN(4) + { + *(.hbn_ram) + } > HBNRAM + + /* Heap in DATA PSRAM after BSS */ + __heap_start = _bss_end; + __heap_end = ORIGIN(DATA) + LENGTH(DATA); + + /* Stack in fast on-chip SRAM */ + _stack_top = ORIGIN(SRAM) + LENGTH(SRAM); + __StackTop = _stack_top; + + /* Vendor start_load.c expects these symbols for ITCM/DTCM/system RAM + * copy loops. We don't use those sections, so define them as empty. */ + PROVIDE(__itcm_load_addr = 0); + PROVIDE(__tcm_code_start__ = 0); + PROVIDE(__tcm_code_end__ = 0); + PROVIDE(__dtcm_load_addr = 0); + PROVIDE(__tcm_data_start__ = 0); + PROVIDE(__tcm_data_end__ = 0); + PROVIDE(__system_ram_load_addr = 0); + PROVIDE(__system_ram_data_start__ = 0); + PROVIDE(__system_ram_data_end__ = 0); + + /DISCARD/ : + { + *(.comment) + *(.note.*) + *(.gnu.hash) + *(.gnu.linkonce.*) + } +} diff --git a/platforms/bl808/bl808_m0.ld b/platforms/bl808/bl808_m0.ld new file mode 100644 index 0000000..2d09302 --- /dev/null +++ b/platforms/bl808/bl808_m0.ld @@ -0,0 +1,126 @@ +/* BL808 M0 core (T-Head E907 RV32IMAFC) linker script + * + * The M0 core is the first to boot on BL808. It initializes clocks, + * GPIO muxing, and peripherals before optionally starting the D0 core. + * + * Memory layout (MCU domain): + * FLASH XIP: 0x58000000, 2MB (M0 firmware, execute-in-place) + * OCRAM: 0x22020000, 64KB (on-chip RAM, cached alias) + * DTCM: 0x20000000, 64KB (tightly-coupled data memory) + * + * TODO: Verify memory map on actual hardware. The M0's view of memory + * differs from D0's — M0 has its own PLIC, own UART IRQ domain, + * and cannot directly access the MM-domain PSRAM used by D0. + */ + +ENTRY(_start) + +EXTERN(_write _read _close _lseek _fstat _isatty _sbrk _exit _kill _getpid) +EXTERN(_Dmodule_ref __errno_location stdout) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x58000000, LENGTH = 2M + DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K + RAM (rwx) : ORIGIN = 0x22020000, LENGTH = 64K +} + +SECTIONS +{ + .text : ALIGN(4) + { + KEEP(*(.text.entry)) + . = ALIGN(4); + *(.text .text.*) + } > FLASH + + .rodata : ALIGN(8) + { + *(.rodata .rodata.*) + } > FLASH + + .eh_frame : ALIGN(8) + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + KEEP(*(.eh_frame_hdr)) + } > FLASH + + .gcc_except_table : ALIGN(4) + { + *(.gcc_except_table .gcc_except_table.*) + } > FLASH + + .init_array : ALIGN(4) + { + __init_array_start = .; + KEEP(*(.init_array)) + __init_array_end = .; + } > FLASH + + .got : ALIGN(4) + { + PROVIDE(__global_pointer$ = . + 0x800); + *(.got .got.*) + *(.got.plt) + *(.sdata .sdata.*) + *(.srodata .srodata.*) + } > DTCM AT > FLASH + + _got_load = LOADADDR(.got); + _got_start = ADDR(.got); + _got_end = _got_start + SIZEOF(.got); + + .data : ALIGN(4) + { + *(.data .data.*) + } > RAM AT > FLASH + + _data_load = LOADADDR(.data); + _data_start = ADDR(.data); + _data_end = _data_start + SIZEOF(.data); + __ram_load_addr = _data_load; + __ram_data_start__ = _data_start; + __ram_data_end__ = _data_end; + + .bss (NOLOAD) : ALIGN(4) + { + __bss_start__ = .; + _bss_start = .; + *(.bss .bss.*) + *(.sbss .sbss.*) + *(COMMON) + _bss_end = .; + __bss_end__ = .; + } > RAM + + .tdata : ALIGN(4) + { + *(.tdata .tdata.*) + } > DTCM AT > FLASH + + _tdata_load = LOADADDR(.tdata); + _tdata_start = ADDR(.tdata); + _tdata_end = _tdata_start + SIZEOF(.tdata); + + .tbss (NOLOAD) : ALIGN(4) + { + _tbss_start = .; + *(.tbss .tbss.*) + _tbss_end = .; + } > DTCM + + __heap_start = _bss_end; + __heap_end = ORIGIN(RAM) + LENGTH(RAM); + + _stack_top = ORIGIN(DTCM) + LENGTH(DTCM); + __StackTop = _stack_top; + + /DISCARD/ : + { + *(.comment) + *(.note.*) + *(.gnu.hash) + *(.gnu.linkonce.*) + } +} diff --git a/platforms/rp2350/README.txt b/platforms/rp2350/README.txt new file mode 100644 index 0000000..f0212e7 --- /dev/null +++ b/platforms/rp2350/README.txt @@ -0,0 +1,90 @@ +RP2350 (Raspberry Pi) -- dual Cortex-M33 @ 150 MHz +================================================== + +520 KB SRAM, XIP from external QSPI flash. URT targets the Arm cores +(the chip also has dual Hazard3 RISC-V cores selectable at boot; URT +does not currently support that mode). + +Reference board: Raspberry Pi Pico 2. + +Setup +----- + +Toolchain (Ubuntu 22.04+): + + # D compiler -- LDC with the official upstream build. + curl -fsS https://dlang.org/install.sh | bash -s ldc + source ~/dlang/ldc-*/activate + + # ARM cross-toolchain and picolibc. + sudo apt-get install gcc-arm-none-eabi picolibc-arm-none-eabi + +Flash tool (picotool, for ELF -> UF2 conversion): + + sudo apt-get install build-essential cmake libusb-1.0-0-dev + git clone https://github.com/raspberrypi/picotool + cd picotool && mkdir build && cd build && cmake .. && make + sudo cp picotool /usr/local/bin/ + +Pre-built picotool binaries are also published at +https://github.com/raspberrypi/pico-sdk-tools/releases for those who +don't want to build from source. + +Windows: LDC installer from https://github.com/ldc-developers/ldc/releases; +ARM GNU Toolchain installer from +https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads; +picotool prebuilt from the pico-sdk-tools releases page noted above. + +Build the unittest image +------------------------ + +From the URT root: + + make PLATFORM=rp2350 CONFIG=unittest + +Outputs (in bin/rp2350_unittest/): + + urt_test ELF with debug symbols + urt_test.bin Raw binary + +Toolchain required: ldc, gcc-arm-none-eabi, picolibc-arm-none-eabi. +Linker script: rp2350.ld. + +Flash +----- + +The RP2350 has a USB mass-storage bootloader -- hold BOOTSEL while +plugging in USB to enter it. Drag-and-drop a .uf2 file onto the +mounted RPI-RP2 volume. + +Convert urt_test (the ELF, not the .bin) to UF2: + + picotool uf2 convert bin/rp2350_unittest/urt_test \ + bin/rp2350_unittest/urt_test.uf2 \ + --family rp2350-arm-s + +(Install picotool from https://github.com/raspberrypi/picotool.) + +Then drop urt_test.uf2 onto the RPI-RP2 volume. The board reboots and +runs immediately. + +Alternatively, flash directly via SWD with openocd + the Pi-supplied +config files (slower; UF2 is the path of least resistance). + +Console +------- + +UART0 (GP0=TX, GP1=RX on the Pico 2 header), 115200 baud, 8N1. Or use +the picoprobe firmware on a second Pico for SWD + virtual UART. + +The unittest harness prints results and halts. Tap RESET (or unplug/ +replug USB) to re-run. + +Notes +----- + +* boot2.S is included in URT's start sources -- a second-stage bootloader + that configures XIP for the QSPI flash chip on the Pico 2 board. Other + boards with different flash chips may need a different boot2. +* The RP2350 has secure/non-secure split (TrustZone-M). URT runs + entirely in secure mode; non-secure callable veneers are not set up. diff --git a/platforms/rp2350/rp2350.ld b/platforms/rp2350/rp2350.ld new file mode 100644 index 0000000..f423ae1 --- /dev/null +++ b/platforms/rp2350/rp2350.ld @@ -0,0 +1,154 @@ +/* RP2350 (ARM Cortex-M33) linker script + * + * Raspberry Pi RP2350B — dual Cortex-M33 @ 150MHz, 520KB SRAM, XIP QSPI flash. + * + * Memory layout: + * BOOT2: 0x10000000, 256B (second-stage bootloader, copied to SRAM by ROM) + * FLASH: 0x10000100, ~4MB (XIP execute-in-place, read-only) + * SRAM: 0x20000000, 520KB (8 banks x 64KB + 8KB scratch) + * + * The RP2350 ROM loads the 256-byte boot2 block from the start of flash, + * executes it from SRAM to configure the QSPI interface, then jumps to + * the vector table at the start of .text. + */ + +ENTRY(Reset_Handler) + +/* Force-link newlib syscall stubs and D runtime symbols */ +EXTERN(_write _read _close _lseek _fstat _isatty _sbrk _exit _kill _getpid) +EXTERN(_Dmodule_ref __errno_location stdout) + +MEMORY +{ + /* Second-stage bootloader — first 256 bytes of flash */ + BOOT2 (rx) : ORIGIN = 0x10000000, LENGTH = 256 + /* Application flash — XIP, code and read-only data */ + FLASH (rx) : ORIGIN = 0x10000100, LENGTH = 4M - 256 + /* SRAM — all 8 banks contiguous (banks 0-7: 8 x 64KB = 512KB) + 8KB scratch */ + SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 520K +} + +SECTIONS +{ + /* Boot2 — second-stage bootloader for QSPI flash init */ + .boot2 : ALIGN(4) + { + KEEP(*(.boot2)) + } > BOOT2 + + /* Vector table must be first in FLASH (right after boot2) */ + .vector_table : ALIGN(4) + { + KEEP(*(.vector_table)) + } > FLASH + + .text : ALIGN(4) + { + *(.text .text.*) + } > FLASH + + .rodata : ALIGN(8) + { + *(.rodata .rodata.*) + } > FLASH + + .eh_frame : ALIGN(8) + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + KEEP(*(.eh_frame_hdr)) + } > FLASH + + .gcc_except_table : ALIGN(4) + { + *(.gcc_except_table .gcc_except_table.*) + } > FLASH + + .init_array : ALIGN(4) + { + __init_array_start = .; + KEEP(*(.init_array)) + __init_array_end = .; + } > FLASH + + /* ARM exception unwind tables */ + .ARM.exidx : ALIGN(4) + { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } > FLASH + + .ARM.extab : ALIGN(4) + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + /* GOT in SRAM for fast access — LMA in FLASH, copied at startup */ + .got : ALIGN(4) + { + _got_load = LOADADDR(.got); + _got_start = .; + *(.got .got.*) + *(.got.plt) + _got_end = .; + } > SRAM AT > FLASH + + /* Initialized data — LMA in FLASH, copied to SRAM at startup */ + .data : ALIGN(4) + { + _data_start = .; + *(.data .data.*) + _data_end = .; + } > SRAM AT > FLASH + + _data_load = LOADADDR(.data); + __ram_load_addr = _data_load; + __ram_data_start__ = _data_start; + __ram_data_end__ = _data_end; + + /* Thread-local initialized data — LMA in FLASH, copied to SRAM at startup */ + .tdata : ALIGN(4) + { + _tdata_start = .; + *(.tdata .tdata.*) + _tdata_end = .; + } > SRAM AT > FLASH + + _tdata_load = LOADADDR(.tdata); + + /* Thread-local zero-initialized data */ + .tbss (NOLOAD) : ALIGN(4) + { + _tbss_start = .; + *(.tbss .tbss.*) + _tbss_end = .; + } > SRAM + + /* Zero-initialized data */ + .bss (NOLOAD) : ALIGN(4) + { + __bss_start__ = .; + _bss_start = .; + *(.bss .bss.*) + *(COMMON) + _bss_end = .; + __bss_end__ = .; + } > SRAM + + /* Heap grows up from end of BSS */ + __heap_start = _bss_end; + __heap_end = ORIGIN(SRAM) + LENGTH(SRAM) - 4K; + + /* Stack at top of SRAM (4KB reserved) */ + _stack_top = ORIGIN(SRAM) + LENGTH(SRAM); + __StackTop = _stack_top; + + /DISCARD/ : + { + *(.comment) + *(.note.*) + *(.gnu.hash) + *(.gnu.linkonce.*) + } +} diff --git a/platforms/stm32/README.txt b/platforms/stm32/README.txt new file mode 100644 index 0000000..58161de --- /dev/null +++ b/platforms/stm32/README.txt @@ -0,0 +1,105 @@ +STM32 (STMicroelectronics) -- Cortex-M4F / Cortex-M7F +===================================================== + +Two PLATFORM aliases supported: + + stm4xx STM32F4 family (Cortex-M4F, FPv4-SP-D16) + stm7xx STM32F7 family (Cortex-M7F, FPv5-D16) + +Linker scripts: stm32_f4.ld and stm32_f7.ld respectively. Each is a +generic-ish memory map -- override flash/RAM sizes in the script if +your specific part has more or less than the defaults. + +Setup +----- + +Toolchain (Ubuntu 22.04+): + + # D compiler -- LDC with the official upstream build. + curl -fsS https://dlang.org/install.sh | bash -s ldc + source ~/dlang/ldc-*/activate + + # ARM cross-toolchain and picolibc. + sudo apt-get install gcc-arm-none-eabi picolibc-arm-none-eabi + +Flash tools (pick one; you do not need all three): + + # ST-LINK + STM32CubeProgrammer -- official ST tool, GUI + CLI. + # Download from https://www.st.com/en/development-tools/stm32cubeprog.html + # (free with registration). Adds STM32_Programmer_CLI to PATH. + + # OpenOCD with ST-LINK or J-Link -- open-source. + sudo apt-get install openocd + + # dfu-util for the ROM bootloader path. + sudo apt-get install dfu-util + +Windows: LDC installer from https://github.com/ldc-developers/ldc/releases; +ARM GNU Toolchain installer from +https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads; +STM32CubeProgrammer Windows installer (it also ships the ST-LINK USB +drivers, which you will need separately on first connect even with +OpenOCD). + +Build the unittest image +------------------------ + +From the URT root: + + make PLATFORM=stm4xx CONFIG=unittest + make PLATFORM=stm7xx CONFIG=unittest + +Outputs: + + bin/stm4xx_unittest/urt_test.bin + bin/stm7xx_unittest/urt_test.bin + +Toolchain required: ldc, gcc-arm-none-eabi, picolibc-arm-none-eabi. + +Flash +----- + +Three common paths: + +1. ST-LINK + STM32CubeProgrammer (GUI or CLI): + + STM32_Programmer_CLI -c port=SWD -d bin/stm4xx_unittest/urt_test.bin 0x08000000 -rst + +2. openocd + ST-LINK or J-Link: + + openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \ + -c "program bin/stm4xx_unittest/urt_test.bin 0x08000000 verify reset exit" + + (Use target/stm32f7x.cfg for stm7xx.) + +3. DFU bootloader (ROM-resident on most parts): + + dfu-util -a 0 -s 0x08000000 -D bin/stm4xx_unittest/urt_test.bin + + Requires entering DFU mode -- typically BOOT0 high at reset. + +The reset vector and image base are at 0x08000000 in all three cases +(internal flash bank 1). + +Console +------- + +USART2 by default (PA2=TX, PA3=RX on most Nucleo and Discovery boards; +exposed via the ST-LINK USB-CDC bridge). 115200 baud, 8N1. Some boards +mux the ST-LINK VCP to USART3 instead; check the board user manual. + +The unittest harness prints results and halts. Tap RESET to re-run. + +Notes +----- + +* The default linker script assumes 1 MB flash + 192 KB RAM (a common + F4/F7 mid-range part). Edit the MEMORY block in stm32_f4.ld or + stm32_f7.ld for your specific MCU. +* No HAL, no LL drivers, no CubeMX -- URT brings its own start.S, + vector table stub, and minimal UART driver. If you need on-chip + peripherals beyond UART you will need to write them or pull in the + vendor headers manually. +* MFPU defaults: F4 = fpv4-sp-d16 (single-precision only), F7 = + fpv5-d16 (double-precision). The Makefile picks these per + STM32_VARIANT; override with MFPU=... if your part differs. diff --git a/platforms/stm32/stm32_f4.ld b/platforms/stm32/stm32_f4.ld new file mode 100644 index 0000000..b719f72 --- /dev/null +++ b/platforms/stm32/stm32_f4.ld @@ -0,0 +1,139 @@ +/* STM32F4 (ARM Cortex-M4) linker script + * + * Default memory map for STM32F407VG (Discovery board): + * FLASH: 1MB at 0x08000000 + * RAM: 128KB at 0x20000000 (SRAM1 112KB + SRAM2 16KB contiguous) + * + * STM32F4 also has 64KB CCM RAM at 0x10000000 (no DMA access). + * Not used here -- add a DTCM region if you need it for stack/GOT. + * + * Adjust MEMORY sizes for your specific F4 variant. + */ + +ENTRY(Reset_Handler) + +EXTERN(_write _read _close _lseek _fstat _isatty _sbrk _exit _kill _getpid) +EXTERN(_Dmodule_ref __errno_location stdout) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1M + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K +} + +SECTIONS +{ + .vector_table : ALIGN(4) + { + KEEP(*(.vector_table)) + } > FLASH + + .text : ALIGN(4) + { + *(.text .text.*) + } > FLASH + + .rodata : ALIGN(8) + { + *(.rodata .rodata.*) + } > FLASH + + .eh_frame : ALIGN(8) + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + KEEP(*(.eh_frame_hdr)) + } > FLASH + + .gcc_except_table : ALIGN(4) + { + *(.gcc_except_table .gcc_except_table.*) + } > FLASH + + .init_array : ALIGN(4) + { + __init_array_start = .; + KEEP(*(.init_array)) + __init_array_end = .; + } > FLASH + + .ARM.exidx : ALIGN(4) + { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } > FLASH + + .ARM.extab : ALIGN(4) + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + /* GOT in RAM for fast access -- LMA in FLASH, copied at startup */ + .got : ALIGN(4) + { + _got_load = LOADADDR(.got); + _got_start = .; + *(.got .got.*) + *(.got.plt) + _got_end = .; + } > RAM AT > FLASH + + /* Initialized data -- LMA in FLASH, copied to RAM at startup */ + .data : ALIGN(4) + { + _data_start = .; + *(.data .data.*) + _data_end = .; + } > RAM AT > FLASH + + _data_load = LOADADDR(.data); + __ram_load_addr = _data_load; + __ram_data_start__ = _data_start; + __ram_data_end__ = _data_end; + + /* Thread-local initialized data -- LMA in FLASH, copied to RAM at startup */ + .tdata : ALIGN(4) + { + _tdata_start = .; + *(.tdata .tdata.*) + _tdata_end = .; + } > RAM AT > FLASH + + _tdata_load = LOADADDR(.tdata); + + /* Thread-local zero-initialized data */ + .tbss (NOLOAD) : ALIGN(4) + { + _tbss_start = .; + *(.tbss .tbss.*) + _tbss_end = .; + } > RAM + + /* Zero-initialized data */ + .bss (NOLOAD) : ALIGN(4) + { + __bss_start__ = .; + _bss_start = .; + *(.bss .bss.*) + *(COMMON) + _bss_end = .; + __bss_end__ = .; + } > RAM + + /* Heap grows up from end of BSS */ + __heap_start = _bss_end; + __heap_end = ORIGIN(RAM) + LENGTH(RAM) - 4K; + + /* Stack at top of RAM (4KB reserved) */ + _stack_top = ORIGIN(RAM) + LENGTH(RAM); + __StackTop = _stack_top; + + /DISCARD/ : + { + *(.comment) + *(.note.*) + *(.gnu.hash) + *(.gnu.linkonce.*) + } +} diff --git a/platforms/stm32/stm32_f7.ld b/platforms/stm32/stm32_f7.ld new file mode 100644 index 0000000..8a4ae48 --- /dev/null +++ b/platforms/stm32/stm32_f7.ld @@ -0,0 +1,141 @@ +/* STM32F7 (ARM Cortex-M7) linker script + * + * Default memory map for STM32F746NG (Discovery board): + * FLASH: 1MB at 0x08000000 + * DTCM: 64KB at 0x20000000 (tightly-coupled, fast, no DMA) + * RAM: 256KB at 0x20010000 (SRAM1 240KB + SRAM2 16KB contiguous) + * + * DTCM is used for stack and GOT (zero-wait-state single-cycle access). + * Main RAM is used for .data, .bss, and heap. + * + * Adjust MEMORY sizes for your specific F7 variant. + */ + +ENTRY(Reset_Handler) + +EXTERN(_write _read _close _lseek _fstat _isatty _sbrk _exit _kill _getpid) +EXTERN(_Dmodule_ref __errno_location stdout) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1M + DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K + RAM (rwx) : ORIGIN = 0x20010000, LENGTH = 256K +} + +SECTIONS +{ + .vector_table : ALIGN(4) + { + KEEP(*(.vector_table)) + } > FLASH + + .text : ALIGN(4) + { + *(.text .text.*) + } > FLASH + + .rodata : ALIGN(8) + { + *(.rodata .rodata.*) + } > FLASH + + .eh_frame : ALIGN(8) + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + KEEP(*(.eh_frame_hdr)) + } > FLASH + + .gcc_except_table : ALIGN(4) + { + *(.gcc_except_table .gcc_except_table.*) + } > FLASH + + .init_array : ALIGN(4) + { + __init_array_start = .; + KEEP(*(.init_array)) + __init_array_end = .; + } > FLASH + + .ARM.exidx : ALIGN(4) + { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } > FLASH + + .ARM.extab : ALIGN(4) + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + /* GOT in DTCM for fast access -- LMA in FLASH, copied at startup */ + .got : ALIGN(4) + { + _got_load = LOADADDR(.got); + _got_start = .; + *(.got .got.*) + *(.got.plt) + _got_end = .; + } > DTCM AT > FLASH + + /* Initialized data -- LMA in FLASH, copied to RAM at startup */ + .data : ALIGN(4) + { + _data_start = .; + *(.data .data.*) + _data_end = .; + } > RAM AT > FLASH + + _data_load = LOADADDR(.data); + __ram_load_addr = _data_load; + __ram_data_start__ = _data_start; + __ram_data_end__ = _data_end; + + /* Thread-local initialized data in DTCM -- LMA in FLASH */ + .tdata : ALIGN(4) + { + _tdata_start = .; + *(.tdata .tdata.*) + _tdata_end = .; + } > DTCM AT > FLASH + + _tdata_load = LOADADDR(.tdata); + + /* Thread-local zero-initialized data */ + .tbss (NOLOAD) : ALIGN(4) + { + _tbss_start = .; + *(.tbss .tbss.*) + _tbss_end = .; + } > DTCM + + /* Zero-initialized data */ + .bss (NOLOAD) : ALIGN(4) + { + __bss_start__ = .; + _bss_start = .; + *(.bss .bss.*) + *(COMMON) + _bss_end = .; + __bss_end__ = .; + } > RAM + + /* Heap grows up from end of BSS in RAM */ + __heap_start = _bss_end; + __heap_end = ORIGIN(RAM) + LENGTH(RAM); + + /* Stack at top of DTCM (fast access) */ + _stack_top = ORIGIN(DTCM) + LENGTH(DTCM); + __StackTop = _stack_top; + + /DISCARD/ : + { + *(.comment) + *(.note.*) + *(.gnu.hash) + *(.gnu.linkonce.*) + } +} diff --git a/src/sys/baremetal/timer.d b/src/sys/baremetal/timer.d index b047557..79971ef 100644 --- a/src/sys/baremetal/timer.d +++ b/src/sys/baremetal/timer.d @@ -1,6 +1,6 @@ module sys.baremetal.timer; -import urt.time : Duration; +import urt.time : Duration, dur; version (BL808_M0) public import sys.bl618.timer; @@ -203,7 +203,7 @@ unittest static if (has_mtime && has_timer_stop) { __gshared bool tick_fired = false; - periodic_set(Duration.from!"msecs"(50), () { tick_fired = true; }); + periodic_set(dur!"msecs"(50), () { tick_fired = true; }); periodic_stop(); // We can't easily test that the callback fires without // waiting + running the interrupt, but at least verify diff --git a/src/sys/bk7231/start.S b/src/sys/bk7231/start.S index 45c2842..a22f40b 100644 --- a/src/sys/bk7231/start.S +++ b/src/sys/bk7231/start.S @@ -106,3 +106,14 @@ Reset_Handler: b .Lhalt .size Reset_Handler, . - Reset_Handler + +/* ================================================================ + * __aeabi_read_tp -- ARM EABI thread pointer for TLS access + * Single-threaded bare-metal: return fixed pointer to .tdata + * ================================================================ */ + .global __aeabi_read_tp + .type __aeabi_read_tp, %function +__aeabi_read_tp: + ldr r0, =_tdata_start + bx lr + .size __aeabi_read_tp, . - __aeabi_read_tp diff --git a/src/sys/stm32/start.S b/src/sys/stm32/start.S index daeec69..403806e 100644 --- a/src/sys/stm32/start.S +++ b/src/sys/stm32/start.S @@ -193,3 +193,15 @@ SysTick_Handler: .type Default_Handler, %function Default_Handler: b . + +/* ================================================================ + * __aeabi_read_tp -- ARM EABI thread pointer for TLS access + * Single-threaded bare-metal: return fixed pointer to .tdata + * ================================================================ */ + .global __aeabi_read_tp + .type __aeabi_read_tp, %function + .thumb_func +__aeabi_read_tp: + ldr r0, =_tdata_start + bx lr + .size __aeabi_read_tp, . - __aeabi_read_tp diff --git a/src/urt/digest/sha.d b/src/urt/digest/sha.d index 53c619f..db8c033 100644 --- a/src/urt/digest/sha.d +++ b/src/urt/digest/sha.d @@ -82,7 +82,8 @@ struct SHA256Context enum uint[StateElements] initState = [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ]; - + } + version (Espressif_Modern) {} else { __gshared immutable uint[64] K = [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, @@ -98,7 +99,7 @@ struct SHA256Context void sha_init(Context)(ref Context ctx) { - static if (esp_hardware) + static if (esp_hardware!Context) { version (ESP32) { @@ -127,7 +128,7 @@ void sha_init(Context)(ref Context ctx) void sha_update(Context)(ref Context ctx, const void[] input) { - static if (esp_hardware) + static if (esp_hardware!Context) { version (ESP32) { @@ -164,7 +165,7 @@ void sha_update(Context)(ref Context ctx, const void[] input) ubyte[Context.DigestLen] sha_finalise(Context)(ref Context ctx) { - static if (esp_hardware) + static if (esp_hardware!Context) { version (ESP32) { @@ -314,12 +315,17 @@ unittest private: -version (Espressif_Modern) - enum esp_hardware = true; -else version (Espressif) - enum esp_hardware = !is(Context == SHA224Context); -else - enum esp_hardware = false; +template esp_hardware(Context) +{ + version (Espressif_Modern) + enum esp_hardware = true; + else version (Espressif) + // SHA-224 hardware on ESP32 Classic would need SHA-256 with custom IV + // (see TODO on SHA224Context); fall through to software for now. + enum esp_hardware = !is(Context == SHA224Context); + else + enum esp_hardware = false; +} version (Espressif) {} else void sha1_transform(Context)(ref Context ctx, const ubyte[] data) From 15bf75468b51b1883a01b38629fd9189995c4f73 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 26 Apr 2026 15:57:32 +1000 Subject: [PATCH 132/138] Relocate the drivers and make the module arrangement sensible. --- platforms.mk | 48 ++--- src/{sys => urt/driver}/baremetal/exception.d | 2 +- src/{sys/bl618 => urt/driver/bk7231}/alloc.d | 3 +- src/{sys => urt/driver}/bk7231/irq.d | 2 +- src/{sys => urt/driver}/bk7231/package.d | 10 +- src/{sys => urt/driver}/bk7231/start.S | 0 src/{sys => urt/driver}/bk7231/syscalls.d | 4 +- src/{sys => urt/driver}/bk7231/timer.d | 2 +- src/{sys => urt/driver}/bk7231/uart.d | 4 +- src/{sys/bk7231 => urt/driver/bl618}/alloc.d | 3 +- src/{sys => urt/driver}/bl618/irq.d | 2 +- src/{sys => urt/driver}/bl618/package.d | 8 +- src/{sys => urt/driver}/bl618/start.S | 2 +- src/{sys => urt/driver}/bl618/syscalls.d | 4 +- src/{sys => urt/driver}/bl618/timer.d | 2 +- src/{sys => urt/driver}/bl618/uart.d | 4 +- src/{sys => urt/driver}/bl808/alloc.d | 2 +- src/{sys => urt/driver}/bl808/exception.d | 6 +- src/{sys => urt/driver}/bl808/gpio.d | 2 +- src/{sys => urt/driver}/bl808/hbn_ram.c | 0 src/{sys => urt/driver}/bl808/i2c.d | 2 +- src/{sys => urt/driver}/bl808/ipc.d | 4 +- src/{sys => urt/driver}/bl808/irq.d | 2 +- src/{sys => urt/driver}/bl808/package.d | 12 +- src/{sys => urt/driver}/bl808/spi.d | 2 +- src/{sys => urt/driver}/bl808/start.S | 0 src/{sys => urt/driver}/bl808/syscalls.d | 4 +- src/{sys => urt/driver}/bl808/timer.d | 6 +- src/{sys => urt/driver}/bl808/uart.d | 6 +- src/{sys => urt/driver}/bl808/wifi.d | 2 +- src/{sys => urt/driver}/bl808/xram.d | 2 +- src/{sys/baremetal => urt/driver}/ble.d | 6 +- src/{sys/baremetal => urt/driver}/can.d | 4 +- src/{sys => urt/driver}/esp32/alloc.d | 2 +- src/{sys => urt/driver}/esp32/ble.d | 4 +- src/{sys => urt/driver}/esp32/can.d | 4 +- src/{sys => urt/driver}/esp32/exception.d | 2 +- src/{sys => urt/driver}/esp32/irq.d | 2 +- src/{sys => urt/driver}/esp32/main.c | 0 src/{sys => urt/driver}/esp32/ow_shim.c | 0 src/{sys => urt/driver}/esp32/timer.d | 2 +- src/{sys => urt/driver}/esp32/uart.d | 4 +- src/{sys => urt/driver}/esp32/wifi.d | 4 +- src/urt/driver/freertos/fibre.d | 101 ++++++++++ src/{sys/baremetal => urt/driver}/irq.d | 16 +- src/{sys => urt/driver}/posix/alloc.d | 2 +- src/{sys => urt/driver}/posix/exception.d | 4 +- src/{sys => urt/driver}/rp2350/alloc.d | 2 +- src/{sys => urt/driver}/rp2350/boot2.S | 0 src/{sys => urt/driver}/rp2350/irq.d | 2 +- src/{sys => urt/driver}/rp2350/package.d | 10 +- src/{sys => urt/driver}/rp2350/start.S | 0 src/{sys => urt/driver}/rp2350/syscalls.d | 4 +- src/{sys => urt/driver}/rp2350/timer.d | 2 +- src/{sys => urt/driver}/rp2350/uart.d | 4 +- src/{sys => urt/driver}/stm32/alloc.d | 2 +- src/{sys => urt/driver}/stm32/irq.d | 2 +- src/{sys => urt/driver}/stm32/package.d | 10 +- src/{sys => urt/driver}/stm32/start.S | 0 src/{sys => urt/driver}/stm32/syscalls.d | 4 +- src/{sys => urt/driver}/stm32/timer.d | 2 +- src/{sys => urt/driver}/stm32/uart.d | 4 +- src/{sys/baremetal => urt/driver}/timer.d | 16 +- src/{sys/baremetal => urt/driver}/uart.d | 16 +- src/{sys/baremetal => urt/driver}/wifi.d | 4 +- src/{sys => urt/driver}/windows/alloc.d | 5 +- src/{sys => urt/driver}/windows/ble.d | 4 +- src/{sys => urt/driver}/windows/exception.d | 2 +- src/urt/driver/windows/fibre.d | 92 +++++++++ src/urt/exception.d | 2 +- src/urt/fibre.d | 185 +----------------- src/urt/internal/dwarfeh.d | 4 +- src/urt/internal/exception.d | 8 +- src/urt/io.d | 2 +- src/urt/mem/alloc.d | 22 +-- src/urt/package.d | 2 +- src/urt/system.d | 6 +- src/urt/time.d | 2 +- 78 files changed, 376 insertions(+), 353 deletions(-) rename src/{sys => urt/driver}/baremetal/exception.d (99%) rename src/{sys/bl618 => urt/driver/bk7231}/alloc.d (95%) rename src/{sys => urt/driver}/bk7231/irq.d (98%) rename src/{sys => urt/driver}/bk7231/package.d (79%) rename src/{sys => urt/driver}/bk7231/start.S (100%) rename src/{sys => urt/driver}/bk7231/syscalls.d (95%) rename src/{sys => urt/driver}/bk7231/timer.d (99%) rename src/{sys => urt/driver}/bk7231/uart.d (99%) rename src/{sys/bk7231 => urt/driver/bl618}/alloc.d (96%) rename src/{sys => urt/driver}/bl618/irq.d (95%) rename src/{sys => urt/driver}/bl618/package.d (89%) rename src/{sys => urt/driver}/bl618/start.S (98%) rename src/{sys => urt/driver}/bl618/syscalls.d (93%) rename src/{sys => urt/driver}/bl618/timer.d (97%) rename src/{sys => urt/driver}/bl618/uart.d (95%) rename src/{sys => urt/driver}/bl808/alloc.d (96%) rename src/{sys => urt/driver}/bl808/exception.d (95%) rename src/{sys => urt/driver}/bl808/gpio.d (99%) rename src/{sys => urt/driver}/bl808/hbn_ram.c (100%) rename src/{sys => urt/driver}/bl808/i2c.d (81%) rename src/{sys => urt/driver}/bl808/ipc.d (96%) rename src/{sys => urt/driver}/bl808/irq.d (99%) rename src/{sys => urt/driver}/bl808/package.d (87%) rename src/{sys => urt/driver}/bl808/spi.d (81%) rename src/{sys => urt/driver}/bl808/start.S (100%) rename src/{sys => urt/driver}/bl808/syscalls.d (97%) rename src/{sys => urt/driver}/bl808/timer.d (98%) rename src/{sys => urt/driver}/bl808/uart.d (99%) rename src/{sys => urt/driver}/bl808/wifi.d (88%) rename src/{sys => urt/driver}/bl808/xram.d (99%) rename src/{sys/baremetal => urt/driver}/ble.d (99%) rename src/{sys/baremetal => urt/driver}/can.d (99%) rename src/{sys => urt/driver}/esp32/alloc.d (98%) rename src/{sys => urt/driver}/esp32/ble.d (99%) rename src/{sys => urt/driver}/esp32/can.d (98%) rename src/{sys => urt/driver}/esp32/exception.d (98%) rename src/{sys => urt/driver}/esp32/irq.d (97%) rename src/{sys => urt/driver}/esp32/main.c (100%) rename src/{sys => urt/driver}/esp32/ow_shim.c (100%) rename src/{sys => urt/driver}/esp32/timer.d (96%) rename src/{sys => urt/driver}/esp32/uart.d (96%) rename src/{sys => urt/driver}/esp32/wifi.d (99%) create mode 100644 src/urt/driver/freertos/fibre.d rename src/{sys/baremetal => urt/driver}/irq.d (94%) rename src/{sys => urt/driver}/posix/alloc.d (97%) rename src/{sys => urt/driver}/posix/exception.d (99%) rename src/{sys => urt/driver}/rp2350/alloc.d (95%) rename src/{sys => urt/driver}/rp2350/boot2.S (100%) rename src/{sys => urt/driver}/rp2350/irq.d (98%) rename src/{sys => urt/driver}/rp2350/package.d (94%) rename src/{sys => urt/driver}/rp2350/start.S (100%) rename src/{sys => urt/driver}/rp2350/syscalls.d (94%) rename src/{sys => urt/driver}/rp2350/timer.d (98%) rename src/{sys => urt/driver}/rp2350/uart.d (98%) rename src/{sys => urt/driver}/stm32/alloc.d (96%) rename src/{sys => urt/driver}/stm32/irq.d (98%) rename src/{sys => urt/driver}/stm32/package.d (94%) rename src/{sys => urt/driver}/stm32/start.S (100%) rename src/{sys => urt/driver}/stm32/syscalls.d (94%) rename src/{sys => urt/driver}/stm32/timer.d (98%) rename src/{sys => urt/driver}/stm32/uart.d (98%) rename src/{sys/baremetal => urt/driver}/timer.d (94%) rename src/{sys/baremetal => urt/driver}/uart.d (97%) rename src/{sys/baremetal => urt/driver}/wifi.d (99%) rename src/{sys => urt/driver}/windows/alloc.d (96%) rename src/{sys => urt/driver}/windows/ble.d (99%) rename src/{sys => urt/driver}/windows/exception.d (99%) create mode 100644 src/urt/driver/windows/fibre.d diff --git a/platforms.mk b/platforms.mk index 2e6ccb3..379d2f5 100644 --- a/platforms.mk +++ b/platforms.mk @@ -19,7 +19,7 @@ # Outputs (resolved variables for caller to consume): # ARCH, OS, MARCH, MATTR, MABI, PROCESSOR, BUILDNAME, COMPILER, DC # DFLAGS - augmented with triple, mattr, platform version flags -# URT_SOURCES - urt/**.d + sys//**.d (+ mbedtls.c on host) +# URT_SOURCES - urt/**.d + urt/driver//**.d (+ mbedtls.c on host) # BAREMETAL_DIR - dir containing start.S etc. (empty on host targets) # BAREMETAL_SRCS - basenames of asm/c sources for cross-gcc # BAREMETAL_GCC - cross-gcc path @@ -42,7 +42,7 @@ CONFIG ?= debug COMPILER ?= dmd # Windows always sets env OS=Windows_NT -- normalize so OS-conditional blocks -# (sys/windows source selection, host triple) recognize it. Plain `:=` (not +# (driver/windows source selection, host triple) recognize it. Plain `:=` (not # `override`) so platform blocks can still set OS=baremetal/freertos for cross. ifeq ($(OS),Windows_NT) OS := windows @@ -327,13 +327,14 @@ ifdef ESPRESSIF_PATH endif # ======================================================================= -# URT_SOURCES -- urt/**.d + sys//**.d +# URT_SOURCES -- urt/**.d + urt/driver//**.d # # Caller appends app sources separately. C glue (mbedtls) is host-only. # ======================================================================= -URT_SOURCES := $(shell find "$(URT_SRCDIR)" -type f -name '*.d' -not -path '$(URT_SRCDIR)/sys/*') -URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/baremetal" -type f -name '*.d') +URT_SOURCES := $(shell find "$(URT_SRCDIR)" -type f -name '*.d' -not -path '$(URT_SRCDIR)/urt/driver/*') +URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver" -maxdepth 1 -type f -name '*.d') +URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/baremetal" -type f -name '*.d') # mbedtls C glue needs host mbedtls headers -- exclude for embedded targets ifeq ($(filter freertos baremetal,$(OS)),) @@ -342,38 +343,41 @@ endif ifeq ($(PLATFORM),bl808) ifeq ($(PROCESSOR),c906) - URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/bl808" -type f -name '*.d') + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/bl808" -type f -name '*.d') else ifeq ($(PROCESSOR),e907) # BL808 M0 core -- E907 uses same peripheral drivers as BL618 - URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/bl618" -type f -name '*.d') + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/bl618" -type f -name '*.d') endif endif ifeq ($(PLATFORM),bl618) - URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/bl618" -type f -name '*.d') + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/bl618" -type f -name '*.d') endif ifeq ($(PLATFORM),rp2350) - URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/rp2350" -type f -name '*.d') + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/rp2350" -type f -name '*.d') endif ifneq ($(filter bk7231n bk7231t,$(PLATFORM)),) - URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/bk7231" -type f -name '*.d' 2>/dev/null) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/bk7231" -type f -name '*.d' 2>/dev/null) endif ifneq ($(filter esp%,$(PLATFORM)),) - URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/esp32" -type f -name '*.d') + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/esp32" -type f -name '*.d') endif ifdef STM32_VARIANT - URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/stm32" -type f -name '*.d') + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/stm32" -type f -name '*.d') endif ifeq ($(OS),windows) - URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/windows" -type f -name '*.d') + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/windows" -type f -name '*.d') endif ifneq ($(filter linux ubuntu freebsd,$(OS)),) - URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/sys/posix" -type f -name '*.d') + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/posix" -type f -name '*.d') +endif +ifeq ($(OS),freertos) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/freertos" -type f -name '*.d') endif # ======================================================================= # DFLAGS -- version flags + previews # -# Only platform/processor *version* flags live here (so URT's sys/ code +# Only platform/processor *version* flags live here (so URT's urt/driver/ code # can `version (BL808)` etc.). Consumer-side `-J platforms/` string # imports stay in the consumer Makefile. # ======================================================================= @@ -406,7 +410,7 @@ ifeq ($(TINY),1) DFLAGS := $(DFLAGS) -d-version=Tiny endif -# Vendor/family versions consumed by URT's sys/ code +# Vendor/family versions consumed by URT's urt/driver/ code ifneq ($(filter esp%,$(PLATFORM)),) DFLAGS := $(DFLAGS) -d-version=Espressif -d-version=lwIP -d-version=CRuntime_Picolibc endif @@ -579,23 +583,23 @@ ifeq ($(COMPILER),ldc) ifneq ($(filter freertos baremetal,$(OS)),) ifeq ($(PLATFORM),bl808) ifeq ($(PROCESSOR),c906) - BAREMETAL_DIR := $(URT_SRCDIR)/sys/bl808 + BAREMETAL_DIR := $(URT_SRCDIR)/urt/driver/bl808 BAREMETAL_SRCS := start.S hbn_ram.c else ifeq ($(PROCESSOR),e907) - BAREMETAL_DIR := $(URT_SRCDIR)/sys/bl618 + BAREMETAL_DIR := $(URT_SRCDIR)/urt/driver/bl618 BAREMETAL_SRCS := start.S endif else ifeq ($(PLATFORM),bl618) - BAREMETAL_DIR := $(URT_SRCDIR)/sys/bl618 + BAREMETAL_DIR := $(URT_SRCDIR)/urt/driver/bl618 BAREMETAL_SRCS := start.S else ifneq ($(filter bk7231n bk7231t,$(PLATFORM)),) - BAREMETAL_DIR := $(URT_SRCDIR)/sys/bk7231 + BAREMETAL_DIR := $(URT_SRCDIR)/urt/driver/bk7231 BAREMETAL_SRCS := start.S else ifeq ($(PLATFORM),rp2350) - BAREMETAL_DIR := $(URT_SRCDIR)/sys/rp2350 + BAREMETAL_DIR := $(URT_SRCDIR)/urt/driver/rp2350 BAREMETAL_SRCS := start.S boot2.S else ifdef STM32_VARIANT - BAREMETAL_DIR := $(URT_SRCDIR)/sys/stm32 + BAREMETAL_DIR := $(URT_SRCDIR)/urt/driver/stm32 BAREMETAL_SRCS := start.S endif diff --git a/src/sys/baremetal/exception.d b/src/urt/driver/baremetal/exception.d similarity index 99% rename from src/sys/baremetal/exception.d rename to src/urt/driver/baremetal/exception.d index f3e72a2..40bb9d4 100644 --- a/src/sys/baremetal/exception.d +++ b/src/urt/driver/baremetal/exception.d @@ -7,7 +7,7 @@ /// /// Requires the compiler to keep the frame pointer live /// (`-frame-pointer=all` on LDC). Without it the fp chain is garbage. -module sys.baremetal.exception; +module urt.driver.baremetal.exception; version (BareMetal): diff --git a/src/sys/bl618/alloc.d b/src/urt/driver/bk7231/alloc.d similarity index 95% rename from src/sys/bl618/alloc.d rename to src/urt/driver/bk7231/alloc.d index aad4caa..af693c4 100644 --- a/src/sys/bl618/alloc.d +++ b/src/urt/driver/bk7231/alloc.d @@ -1,4 +1,4 @@ -module sys.bl618.alloc; +module urt.driver.bk7231.alloc; import urt.mem.alloc : MemFlags; @@ -35,3 +35,4 @@ private: extern(C) void* malloc(size_t size) pure; extern(C) void free(void* ptr) pure; + diff --git a/src/sys/bk7231/irq.d b/src/urt/driver/bk7231/irq.d similarity index 98% rename from src/sys/bk7231/irq.d rename to src/urt/driver/bk7231/irq.d index e63d899..21e1a70 100644 --- a/src/sys/bk7231/irq.d +++ b/src/urt/driver/bk7231/irq.d @@ -2,7 +2,7 @@ // // ARM968E-S uses a custom Beken interrupt controller (not ARM GIC/NVIC). // The BK7231N ICU (Interrupt Control Unit) is at 0x0080_2000. -module sys.bk7231.irq; +module urt.driver.bk7231.irq; import core.volatile; diff --git a/src/sys/bk7231/package.d b/src/urt/driver/bk7231/package.d similarity index 79% rename from src/sys/bk7231/package.d rename to src/urt/driver/bk7231/package.d index d51ce04..0e35501 100644 --- a/src/sys/bk7231/package.d +++ b/src/urt/driver/bk7231/package.d @@ -2,13 +2,13 @@ // // Provides sys_init() as the single entry point for all // hardware initialization. Called from start.S before main(). -module sys.bk7231; +module urt.driver.bk7231; -public import sys.bk7231.uart; -public import sys.bk7231.irq; -public import sys.bk7231.timer; +public import urt.driver.bk7231.uart; +public import urt.driver.bk7231.irq; +public import urt.driver.bk7231.timer; -import sys.baremetal.uart : UartConfig; +import urt.driver.uart : UartConfig; @nogc nothrow: diff --git a/src/sys/bk7231/start.S b/src/urt/driver/bk7231/start.S similarity index 100% rename from src/sys/bk7231/start.S rename to src/urt/driver/bk7231/start.S diff --git a/src/sys/bk7231/syscalls.d b/src/urt/driver/bk7231/syscalls.d similarity index 95% rename from src/sys/bk7231/syscalls.d rename to src/urt/driver/bk7231/syscalls.d index e4d8f3d..e89078f 100644 --- a/src/sys/bk7231/syscalls.d +++ b/src/urt/driver/bk7231/syscalls.d @@ -2,7 +2,7 @@ // // Minimal stubs to satisfy picolibc's syscall requirements. // Same pattern as RP2350 -- most are no-ops for baremetal. -module sys.bk7231.syscalls; +module urt.driver.bk7231.syscalls; @nogc nothrow: @@ -28,7 +28,7 @@ extern(C) void* _sbrk(ptrdiff_t incr) extern(C) int _write(int fd, const void* buf, size_t count) { - import sys.bk7231.uart : uart0_hw_puts; + import urt.driver.bk7231.uart : uart0_hw_puts; if (fd == 1 || fd == 2) uart0_hw_puts((cast(const(char)*)buf)[0 .. count]); return cast(int)count; diff --git a/src/sys/bk7231/timer.d b/src/urt/driver/bk7231/timer.d similarity index 99% rename from src/sys/bk7231/timer.d rename to src/urt/driver/bk7231/timer.d index b1f9193..13f627c 100644 --- a/src/sys/bk7231/timer.d +++ b/src/urt/driver/bk7231/timer.d @@ -15,7 +15,7 @@ // Register layout verified against Beken SDK: // sdk/OpenBK7231N/platforms/bk7231n/bk7231n_os/beken378/driver/pwm/bk_timer.h // sdk/OpenBK7231N/platforms/bk7231n/bk7231n_os/beken378/driver/pwm/bk_timer.c -module sys.bk7231.timer; +module urt.driver.bk7231.timer; import core.volatile; diff --git a/src/sys/bk7231/uart.d b/src/urt/driver/bk7231/uart.d similarity index 99% rename from src/sys/bk7231/uart.d rename to src/urt/driver/bk7231/uart.d index dd18d50..73e39f8 100644 --- a/src/sys/bk7231/uart.d +++ b/src/urt/driver/bk7231/uart.d @@ -9,11 +9,11 @@ // Register layout and init sequence verified against Beken SDK: // sdk/OpenBK7231N/platforms/bk7231n/bk7231n_os/beken378/driver/uart/uart.h // sdk/OpenBK7231N/platforms/bk7231n/bk7231n_os/beken378/driver/uart/uart_bk.c -module sys.bk7231.uart; +module urt.driver.bk7231.uart; import core.volatile; -import sys.baremetal.uart : FlowControl, Parity, StopBits, UartConfig; +import urt.driver.uart : FlowControl, Parity, StopBits, UartConfig; nothrow @nogc: diff --git a/src/sys/bk7231/alloc.d b/src/urt/driver/bl618/alloc.d similarity index 96% rename from src/sys/bk7231/alloc.d rename to src/urt/driver/bl618/alloc.d index 7b6a5b8..4fb991e 100644 --- a/src/sys/bk7231/alloc.d +++ b/src/urt/driver/bl618/alloc.d @@ -1,4 +1,4 @@ -module sys.bk7231.alloc; +module urt.driver.bl618.alloc; import urt.mem.alloc : MemFlags; @@ -35,4 +35,3 @@ private: extern(C) void* malloc(size_t size) pure; extern(C) void free(void* ptr) pure; - diff --git a/src/sys/bl618/irq.d b/src/urt/driver/bl618/irq.d similarity index 95% rename from src/sys/bl618/irq.d rename to src/urt/driver/bl618/irq.d index 7c04137..20d8406 100644 --- a/src/sys/bl618/irq.d +++ b/src/urt/driver/bl618/irq.d @@ -1,5 +1,5 @@ // BL618 interrupt controller driver (CLIC-style) -module sys.bl618.irq; +module urt.driver.bl618.irq; @nogc nothrow: diff --git a/src/sys/bl618/package.d b/src/urt/driver/bl618/package.d similarity index 89% rename from src/sys/bl618/package.d rename to src/urt/driver/bl618/package.d index f56a9ff..28c93f9 100644 --- a/src/sys/bl618/package.d +++ b/src/urt/driver/bl618/package.d @@ -2,11 +2,11 @@ /// /// Provides sys_init() as the single entry point for all /// hardware initialization. Call from main() before anything else. -module sys.bl618; +module urt.driver.bl618; -public import sys.bl618.uart; -public import sys.bl618.irq; -public import sys.bl618.timer; +public import urt.driver.bl618.uart; +public import urt.driver.bl618.irq; +public import urt.driver.bl618.timer; @nogc nothrow: diff --git a/src/sys/bl618/start.S b/src/urt/driver/bl618/start.S similarity index 98% rename from src/sys/bl618/start.S rename to src/urt/driver/bl618/start.S index 717c01e..bf40de5 100644 --- a/src/sys/bl618/start.S +++ b/src/urt/driver/bl618/start.S @@ -202,5 +202,5 @@ _trap_handler: .weak _irq_handler .type _irq_handler, @function _irq_handler: - /* TODO: PLIC claim/complete dispatch — wire to sys.bl618.irq */ + /* TODO: PLIC claim/complete dispatch — wire to driver.bl618.irq */ j _irq_handler diff --git a/src/sys/bl618/syscalls.d b/src/urt/driver/bl618/syscalls.d similarity index 93% rename from src/sys/bl618/syscalls.d rename to src/urt/driver/bl618/syscalls.d index 3597412..fee9617 100644 --- a/src/sys/bl618/syscalls.d +++ b/src/urt/driver/bl618/syscalls.d @@ -2,7 +2,7 @@ /// /// Minimal stubs to satisfy picolibc's syscall requirements. /// Same pattern as BL808 — most are no-ops for baremetal. -module sys.bl618.syscalls; +module urt.driver.bl618.syscalls; @nogc nothrow: @@ -30,7 +30,7 @@ extern(C) void* _sbrk(ptrdiff_t incr) extern(C) int _write(int fd, const void* buf, size_t count) { - import sys.bl618.uart : uart0_hw_puts; + import urt.driver.bl618.uart : uart0_hw_puts; if (fd == 1 || fd == 2) uart0_hw_puts((cast(const(char)*) buf)[0 .. count]); return cast(int) count; diff --git a/src/sys/bl618/timer.d b/src/urt/driver/bl618/timer.d similarity index 97% rename from src/sys/bl618/timer.d rename to src/urt/driver/bl618/timer.d index 6bd3883..d5cba73 100644 --- a/src/sys/bl618/timer.d +++ b/src/urt/driver/bl618/timer.d @@ -2,7 +2,7 @@ // // T-Head E907 (RV32IMAFC) has standard RISC-V mtime/mtimecmp. // mtime runs at 1MHz (same as BL808's C906). -module sys.bl618.timer; +module urt.driver.bl618.timer; @nogc nothrow: diff --git a/src/sys/bl618/uart.d b/src/urt/driver/bl618/uart.d similarity index 95% rename from src/sys/bl618/uart.d rename to src/urt/driver/bl618/uart.d index 60c2be4..58c4a23 100644 --- a/src/sys/bl618/uart.d +++ b/src/urt/driver/bl618/uart.d @@ -10,9 +10,9 @@ /// /// TODO: Implement full driver from BL616 register map. /// Stubbed to provide the API surface needed by serial.d. -module sys.bl618.uart; +module urt.driver.bl618.uart; -import sys.baremetal.uart : Parity, StopBits, UartConfig; +import urt.driver.uart : Parity, StopBits, UartConfig; nothrow @nogc: diff --git a/src/sys/bl808/alloc.d b/src/urt/driver/bl808/alloc.d similarity index 96% rename from src/sys/bl808/alloc.d rename to src/urt/driver/bl808/alloc.d index 260bb65..9cbc158 100644 --- a/src/sys/bl808/alloc.d +++ b/src/urt/driver/bl808/alloc.d @@ -1,4 +1,4 @@ -module sys.bl808.alloc; +module urt.driver.bl808.alloc; import urt.mem.alloc : MemFlags; diff --git a/src/sys/bl808/exception.d b/src/urt/driver/bl808/exception.d similarity index 95% rename from src/sys/bl808/exception.d rename to src/urt/driver/bl808/exception.d index 8bfd42e..a982397 100644 --- a/src/sys/bl808/exception.d +++ b/src/urt/driver/bl808/exception.d @@ -3,10 +3,10 @@ /// Called from _trap_exception in start.S. Prints exception info, /// register dump, and stack backtrace to UART0. /// Requires -frame-pointer=all for reliable backtrace. -module sys.bl808.exception; +module urt.driver.bl808.exception; -import sys.baremetal.exception : walk_fp_chain; -import sys.bl808.uart; +import urt.driver.baremetal.exception : walk_fp_chain; +import urt.driver.bl808.uart; private: diff --git a/src/sys/bl808/gpio.d b/src/urt/driver/bl808/gpio.d similarity index 99% rename from src/sys/bl808/gpio.d rename to src/urt/driver/bl808/gpio.d index 5000206..63e7ee5 100644 --- a/src/sys/bl808/gpio.d +++ b/src/urt/driver/bl808/gpio.d @@ -9,7 +9,7 @@ /// bit[25] = pull-down enable /// /// WS2812B on the M1s Dock board: GPIO8 -module sys.bl808.gpio; +module urt.driver.bl808.gpio; @nogc nothrow: diff --git a/src/sys/bl808/hbn_ram.c b/src/urt/driver/bl808/hbn_ram.c similarity index 100% rename from src/sys/bl808/hbn_ram.c rename to src/urt/driver/bl808/hbn_ram.c diff --git a/src/sys/bl808/i2c.d b/src/urt/driver/bl808/i2c.d similarity index 81% rename from src/sys/bl808/i2c.d rename to src/urt/driver/bl808/i2c.d index 289d245..06ce7c1 100644 --- a/src/sys/bl808/i2c.d +++ b/src/urt/driver/bl808/i2c.d @@ -2,4 +2,4 @@ /// /// Direct register access to BL808 I2C controller. /// TODO: implement when needed for hardware I/O. -module sys.bl808.i2c; +module urt.driver.bl808.i2c; diff --git a/src/sys/bl808/ipc.d b/src/urt/driver/bl808/ipc.d similarity index 96% rename from src/sys/bl808/ipc.d rename to src/urt/driver/bl808/ipc.d index 6ca6444..faaeb7f 100644 --- a/src/sys/bl808/ipc.d +++ b/src/urt/driver/bl808/ipc.d @@ -2,9 +2,9 @@ /// /// Initializes XRAM ring buffers and provides typed send/receive /// for peripheral commands and network frames. -module sys.bl808.ipc; +module urt.driver.bl808.ipc; -import sys.bl808.xram; +import urt.driver.bl808.xram; @nogc nothrow: diff --git a/src/sys/bl808/irq.d b/src/urt/driver/bl808/irq.d similarity index 99% rename from src/sys/bl808/irq.d rename to src/urt/driver/bl808/irq.d index 8324463..6d5dce6 100644 --- a/src/sys/bl808/irq.d +++ b/src/urt/driver/bl808/irq.d @@ -1,4 +1,4 @@ -module sys.bl808.irq; +module urt.driver.bl808.irq; import core.volatile; diff --git a/src/sys/bl808/package.d b/src/urt/driver/bl808/package.d similarity index 87% rename from src/sys/bl808/package.d rename to src/urt/driver/bl808/package.d index 9fd26b2..2ca8a6b 100644 --- a/src/sys/bl808/package.d +++ b/src/urt/driver/bl808/package.d @@ -2,13 +2,13 @@ /// /// Provides sys_init() as the single entry point for all /// hardware initialization. Call from main() before anything else. -module sys.bl808; +module urt.driver.bl808; -public import sys.bl808.uart; -public import sys.bl808.irq; -public import sys.bl808.timer; -public import sys.bl808.xram; -public import sys.bl808.ipc; +public import urt.driver.bl808.uart; +public import urt.driver.bl808.irq; +public import urt.driver.bl808.timer; +public import urt.driver.bl808.xram; +public import urt.driver.bl808.ipc; @nogc nothrow: diff --git a/src/sys/bl808/spi.d b/src/urt/driver/bl808/spi.d similarity index 81% rename from src/sys/bl808/spi.d rename to src/urt/driver/bl808/spi.d index 12a4f08..3393b81 100644 --- a/src/sys/bl808/spi.d +++ b/src/urt/driver/bl808/spi.d @@ -2,4 +2,4 @@ /// /// Direct register access to BL808 SPI controller. /// TODO: implement when needed for hardware I/O. -module sys.bl808.spi; +module urt.driver.bl808.spi; diff --git a/src/sys/bl808/start.S b/src/urt/driver/bl808/start.S similarity index 100% rename from src/sys/bl808/start.S rename to src/urt/driver/bl808/start.S diff --git a/src/sys/bl808/syscalls.d b/src/urt/driver/bl808/syscalls.d similarity index 97% rename from src/sys/bl808/syscalls.d rename to src/urt/driver/bl808/syscalls.d index 6d5e253..8795081 100644 --- a/src/sys/bl808/syscalls.d +++ b/src/urt/driver/bl808/syscalls.d @@ -2,9 +2,9 @@ /// /// _write routes stdout/stderr to UART0. /// Network stubs return -1 until replaced by XRAM WiFi IPC. -module sys.bl808.syscalls; +module urt.driver.bl808.syscalls; -import sys.bl808.uart; +import urt.driver.bl808.uart; private: diff --git a/src/sys/bl808/timer.d b/src/urt/driver/bl808/timer.d similarity index 98% rename from src/sys/bl808/timer.d rename to src/urt/driver/bl808/timer.d index 7b49a42..5860897 100644 --- a/src/sys/bl808/timer.d +++ b/src/urt/driver/bl808/timer.d @@ -23,7 +23,7 @@ /// HBN_TIME_H @ +0x08 — compare value high (alarm) /// HBN_RTC_TIME_L @ +0x0C — latched counter low (read-only) /// HBN_RTC_TIME_H @ +0x10 — latched counter high [7:0] + latch trigger [31] -module sys.bl808.timer; +module urt.driver.bl808.timer; import core.volatile; @@ -91,7 +91,7 @@ private __gshared void function() @nogc nothrow tick_callback = null; /// callback: called from _timer_irq_handler (keep it short!) void timer_set_periodic(ulong interval_us, void function() @nogc nothrow callback) { - import sys.bl808.irq : IrqClass, enable_irq; + import urt.driver.bl808.irq : IrqClass, enable_irq; tick_interval = interval_us; tick_callback = callback; @@ -104,7 +104,7 @@ void timer_set_periodic(ulong interval_us, void function() @nogc nothrow callbac /// Stop the periodic timer void timer_stop() { - import sys.bl808.irq : IrqClass, disable_irq; + import urt.driver.bl808.irq : IrqClass, disable_irq; disable_irq(IrqClass.timer); mtimecmp_write(ulong.max); diff --git a/src/sys/bl808/uart.d b/src/urt/driver/bl808/uart.d similarity index 99% rename from src/sys/bl808/uart.d rename to src/urt/driver/bl808/uart.d index 0ca6863..6bfefe3 100644 --- a/src/sys/bl808/uart.d +++ b/src/urt/driver/bl808/uart.d @@ -19,12 +19,12 @@ // // All UARTs use software ring buffers (512 bytes RX, 512 bytes TX). // UART3 fills/drains them via ISR. UART0/1/2 require explicit uart_poll(). -module sys.bl808.uart; +module urt.driver.bl808.uart; import core.volatile; -import sys.bl808.irq; +import urt.driver.bl808.irq; -import sys.baremetal.uart : Parity, StopBits, UartConfig; +import urt.driver.uart : Parity, StopBits, UartConfig; nothrow @nogc: diff --git a/src/sys/bl808/wifi.d b/src/urt/driver/bl808/wifi.d similarity index 88% rename from src/sys/bl808/wifi.d rename to src/urt/driver/bl808/wifi.d index cbac5ee..c97a5ae 100644 --- a/src/sys/bl808/wifi.d +++ b/src/urt/driver/bl808/wifi.d @@ -3,4 +3,4 @@ /// M0 runs the WiFi/BT stack. D0 sends commands (connect, disconnect) /// and exchanges Ethernet frames via the XRAM NET ring buffer. /// TODO: implement using ipc module. -module sys.bl808.wifi; +module urt.driver.bl808.wifi; diff --git a/src/sys/bl808/xram.d b/src/urt/driver/bl808/xram.d similarity index 99% rename from src/sys/bl808/xram.d rename to src/urt/driver/bl808/xram.d index 7c24185..9e65fdf 100644 --- a/src/sys/bl808/xram.d +++ b/src/urt/driver/bl808/xram.d @@ -10,7 +10,7 @@ /// 2 = NET Ethernet frames (WiFi bridge) /// 3 = PERIPHERAL GPIO/SPI/PWM/Flash control /// 4 = RPC Remote procedure calls -module sys.bl808.xram; +module urt.driver.bl808.xram; import core.volatile; diff --git a/src/sys/baremetal/ble.d b/src/urt/driver/ble.d similarity index 99% rename from src/sys/baremetal/ble.d rename to src/urt/driver/ble.d index 8c93ea2..84bf670 100644 --- a/src/sys/baremetal/ble.d +++ b/src/urt/driver/ble.d @@ -1,12 +1,12 @@ -module sys.baremetal.ble; +module urt.driver.ble; import urt.result : Result, InternalResult; import urt.uuid : GUID; version (Windows) - public import sys.windows.ble; + public import urt.driver.windows.ble; else version (Espressif) - public import sys.esp32.ble; + public import urt.driver.esp32.ble; else enum uint num_ble = 0; diff --git a/src/sys/baremetal/can.d b/src/urt/driver/can.d similarity index 99% rename from src/sys/baremetal/can.d rename to src/urt/driver/can.d index 87ce650..b03ce07 100644 --- a/src/sys/baremetal/can.d +++ b/src/urt/driver/can.d @@ -1,9 +1,9 @@ -module sys.baremetal.can; +module urt.driver.can; import urt.result : Result, InternalResult; version (Espressif) - public import sys.esp32.can; + public import urt.driver.esp32.can; else { enum uint num_can = 0; diff --git a/src/sys/esp32/alloc.d b/src/urt/driver/esp32/alloc.d similarity index 98% rename from src/sys/esp32/alloc.d rename to src/urt/driver/esp32/alloc.d index 9ebcaef..e465e91 100644 --- a/src/sys/esp32/alloc.d +++ b/src/urt/driver/esp32/alloc.d @@ -1,4 +1,4 @@ -module sys.esp32.alloc; +module urt.driver.esp32.alloc; import urt.mem.alloc : MemFlags; diff --git a/src/sys/esp32/ble.d b/src/urt/driver/esp32/ble.d similarity index 99% rename from src/sys/esp32/ble.d rename to src/urt/driver/esp32/ble.d index 5cc3d4d..372f661 100644 --- a/src/sys/esp32/ble.d +++ b/src/urt/driver/esp32/ble.d @@ -8,9 +8,9 @@ // // BLE controller count per chip (ESP-IDF v6.0): // ESP32, S3, C2, C3, C5, C6, H2: 1 S2, P4: 0 -module sys.esp32.ble; +module urt.driver.esp32.ble; -import sys.baremetal.ble; +import urt.driver.ble; import urt.uuid : GUID; diff --git a/src/sys/esp32/can.d b/src/urt/driver/esp32/can.d similarity index 98% rename from src/sys/esp32/can.d rename to src/urt/driver/esp32/can.d index 54519f5..088ecda 100644 --- a/src/sys/esp32/can.d +++ b/src/urt/driver/esp32/can.d @@ -5,9 +5,9 @@ // // Controller count per chip (ESP-IDF v6.0): // ESP32, S3, C3, H2: 1 C5, C6: 2 P4: 3 S2, C2, C61: 0 -module sys.esp32.can; +module urt.driver.esp32.can; -import sys.baremetal.can : CanConfig, CanFrame, CanError, CanBusState; +import urt.driver.can : CanConfig, CanFrame, CanError, CanBusState; nothrow @nogc: diff --git a/src/sys/esp32/exception.d b/src/urt/driver/esp32/exception.d similarity index 98% rename from src/sys/esp32/exception.d rename to src/urt/driver/esp32/exception.d index 7295b4e..b639efc 100644 --- a/src/sys/esp32/exception.d +++ b/src/urt/driver/esp32/exception.d @@ -4,7 +4,7 @@ /// by the ESP-IDF toolchain). No on-device symbol resolution - /// decode addresses offline with `xtensa-esp32-elf-addr2line` / /// `riscv32-esp-elf-addr2line`. -module sys.esp32.exception; +module urt.driver.esp32.exception; version (Espressif): diff --git a/src/sys/esp32/irq.d b/src/urt/driver/esp32/irq.d similarity index 97% rename from src/sys/esp32/irq.d rename to src/urt/driver/esp32/irq.d index c2bac3b..36438cd 100644 --- a/src/sys/esp32/irq.d +++ b/src/urt/driver/esp32/irq.d @@ -3,7 +3,7 @@ // ESP-IDF manages interrupts via esp_intr_alloc(). Direct vector table // access is discouraged since FreeRTOS owns it. Global disable/enable // uses FreeRTOS critical section primitives. -module sys.esp32.irq; +module urt.driver.esp32.irq; nothrow @nogc: diff --git a/src/sys/esp32/main.c b/src/urt/driver/esp32/main.c similarity index 100% rename from src/sys/esp32/main.c rename to src/urt/driver/esp32/main.c diff --git a/src/sys/esp32/ow_shim.c b/src/urt/driver/esp32/ow_shim.c similarity index 100% rename from src/sys/esp32/ow_shim.c rename to src/urt/driver/esp32/ow_shim.c diff --git a/src/sys/esp32/timer.d b/src/urt/driver/esp32/timer.d similarity index 96% rename from src/sys/esp32/timer.d rename to src/urt/driver/esp32/timer.d index fe36e83..d1a2f2c 100644 --- a/src/sys/esp32/timer.d +++ b/src/urt/driver/esp32/timer.d @@ -2,7 +2,7 @@ // // Uses esp_timer_get_time() for monotonic microsecond clock. // Periodic tick uses FreeRTOS tick or esp_timer under the hood. -module sys.esp32.timer; +module urt.driver.esp32.timer; nothrow @nogc: diff --git a/src/sys/esp32/uart.d b/src/urt/driver/esp32/uart.d similarity index 96% rename from src/sys/esp32/uart.d rename to src/urt/driver/esp32/uart.d index e96891b..bf76bab 100644 --- a/src/sys/esp32/uart.d +++ b/src/urt/driver/esp32/uart.d @@ -5,9 +5,9 @@ // // 3 UART ports: UART0 (console), UART1, UART2. // UART0 TX/RX defaults set by bootloader (typically GPIO43/44 on S3). -module sys.esp32.uart; +module urt.driver.esp32.uart; -import sys.baremetal.uart : Parity, StopBits, UartConfig; +import urt.driver.uart : Parity, StopBits, UartConfig; nothrow @nogc: diff --git a/src/sys/esp32/wifi.d b/src/urt/driver/esp32/wifi.d similarity index 99% rename from src/sys/esp32/wifi.d rename to src/urt/driver/esp32/wifi.d index 4257c08..22d7a1e 100644 --- a/src/sys/esp32/wifi.d +++ b/src/urt/driver/esp32/wifi.d @@ -11,9 +11,9 @@ // calls ESP-IDF directly. // // ESP32 has one WiFi port (port 0). -module sys.esp32.wifi; +module urt.driver.esp32.wifi; -import sys.baremetal.wifi; +import urt.driver.wifi; nothrow @nogc: diff --git a/src/urt/driver/freertos/fibre.d b/src/urt/driver/freertos/fibre.d new file mode 100644 index 0000000..e19606e --- /dev/null +++ b/src/urt/driver/freertos/fibre.d @@ -0,0 +1,101 @@ +// FreeRTOS task-based fibre implementation. +// Each fibre is a FreeRTOS task that blocks on a task notification. +// co_switch does a notify-target + wait-for-notification handoff, +// giving us cooperative coroutine semantics on top of FreeRTOS. +// +// C-side ow_task_* shims live in driver/esp32/ow_shim.c (and equivalent +// per-platform shim modules); they translate to FreeRTOS task-notify APIs. +module urt.driver.freertos.fibre; + +import urt.fibre : cothread_t, coentry_t; +import urt.mem; + +nothrow @nogc: + + +extern(C) int ow_task_create(void function(void*), const(char)*, uint, void*, uint, void**) nothrow @nogc; +extern(C) void ow_task_delete(void*) nothrow @nogc; +extern(C) void* ow_task_current() nothrow @nogc; +extern(C) void ow_task_notify_give(void*) nothrow @nogc; +extern(C) uint ow_task_notify_take(uint) nothrow @nogc; +extern(C) uint ow_task_priority_get(void*) nothrow @nogc; + +struct co_fibre_data +{ + void* user_data; + uint stack_size; + void* task_handle; + coentry_t coentry; +} + +co_fibre_data main_fibre_data; +cothread_t co_active_handle = null; + +inout(co_fibre_data)* co_get_fibre_data(inout cothread_t fibre) pure + => cast(co_fibre_data*)fibre; + +cothread_t co_active() +{ + if (!co_active_handle) + { + main_fibre_data.task_handle = ow_task_current(); + co_active_handle = &main_fibre_data; + } + return co_active_handle; +} + +void* co_data() + => (cast(co_fibre_data*)co_active_handle).user_data; + +cothread_t co_derive(void[] memory, coentry_t entry, void* data) +{ + return null; // not supported with FreeRTOS tasks +} + +extern(C) static void co_freertos_entry(void* param) nothrow @nogc +{ + co_active_handle = cast(cothread_t)param; // set TLS for this task + ow_task_notify_take(0xFFFFFFFF); // block until first co_switch + (cast(co_fibre_data*)param).coentry(); +} + +cothread_t co_create(size_t stack_size, coentry_t entry, void* data) +{ + assert(stack_size <= uint.max, "Stack size too large"); + co_active(); // ensure main fibre initialized + + auto fdata = defaultAllocator().allocT!co_fibre_data(); + if (!fdata) return null; + + fdata.user_data = data; + fdata.stack_size = cast(uint)stack_size; + fdata.coentry = entry; + + auto priority = ow_task_priority_get(null); + + if (!ow_task_create(&co_freertos_entry, "fibre", cast(uint)stack_size, + fdata, priority, &fdata.task_handle)) + { + defaultAllocator().freeT(fdata); + return null; + } + + return fdata; +} + +void co_delete(cothread_t handle) +{ + auto fdata = cast(co_fibre_data*)handle; + if (fdata && fdata != &main_fibre_data) + { + if (fdata.task_handle) + ow_task_delete(fdata.task_handle); + defaultAllocator().freeT(fdata); + } +} + +void co_switch(cothread_t handle) +{ + ow_task_notify_give((cast(co_fibre_data*)handle).task_handle); + ow_task_notify_take(0xFFFFFFFF); +} diff --git a/src/sys/baremetal/irq.d b/src/urt/driver/irq.d similarity index 94% rename from src/sys/baremetal/irq.d rename to src/urt/driver/irq.d index 6cfd9df..1e330a8 100644 --- a/src/sys/baremetal/irq.d +++ b/src/urt/driver/irq.d @@ -3,22 +3,22 @@ // Normalizes the IRQ API across RISC-V PLIC, ARM NVIC, Beken ICU, etc. // Platform drivers export capabilities; the baremetal layer re-exports // them and provides a uniform function surface. -module sys.baremetal.irq; +module urt.driver.irq; version (BL808_M0) - public import sys.bl618.irq; + public import urt.driver.bl618.irq; else version (BL808) - public import sys.bl808.irq; + public import urt.driver.bl808.irq; else version (BL618) - public import sys.bl618.irq; + public import urt.driver.bl618.irq; else version (Beken) - public import sys.bk7231.irq; + public import urt.driver.bk7231.irq; else version (RP2350) - public import sys.rp2350.irq; + public import urt.driver.rp2350.irq; else version (STM32) - public import sys.stm32.irq; + public import urt.driver.stm32.irq; else version (Espressif) - public import sys.esp32.irq; + public import urt.driver.esp32.irq; else { enum bool has_plic = false; diff --git a/src/sys/posix/alloc.d b/src/urt/driver/posix/alloc.d similarity index 97% rename from src/sys/posix/alloc.d rename to src/urt/driver/posix/alloc.d index 497414e..909edd8 100644 --- a/src/sys/posix/alloc.d +++ b/src/urt/driver/posix/alloc.d @@ -1,4 +1,4 @@ -module sys.posix.alloc; +module urt.driver.posix.alloc; import urt.mem.alloc : MemFlags; diff --git a/src/sys/posix/exception.d b/src/urt/driver/posix/exception.d similarity index 99% rename from src/sys/posix/exception.d rename to src/urt/driver/posix/exception.d index 0f87694..e8ac1f3 100644 --- a/src/sys/posix/exception.d +++ b/src/urt/driver/posix/exception.d @@ -7,8 +7,8 @@ /// /// The DWARF exception-handling runtime (_d_throw_exception, personality /// function, etc.) lives in urt.internal.dwarfeh so it compiles on bare- -/// metal builds too where sys.posix.exception is not in the source list. -module sys.posix.exception; +/// metal builds too where urt.driver.posix.exception is not in the source list. +module urt.driver.posix.exception; // Windows has its own driver; BareMetal has its own. Everything else // (Linux, macOS, BSD) lands here. Compiled always (not just `debug`) diff --git a/src/sys/rp2350/alloc.d b/src/urt/driver/rp2350/alloc.d similarity index 95% rename from src/sys/rp2350/alloc.d rename to src/urt/driver/rp2350/alloc.d index 695118e..44d71e9 100644 --- a/src/sys/rp2350/alloc.d +++ b/src/urt/driver/rp2350/alloc.d @@ -1,4 +1,4 @@ -module sys.rp2350.alloc; +module urt.driver.rp2350.alloc; import urt.mem.alloc : MemFlags; diff --git a/src/sys/rp2350/boot2.S b/src/urt/driver/rp2350/boot2.S similarity index 100% rename from src/sys/rp2350/boot2.S rename to src/urt/driver/rp2350/boot2.S diff --git a/src/sys/rp2350/irq.d b/src/urt/driver/rp2350/irq.d similarity index 98% rename from src/sys/rp2350/irq.d rename to src/urt/driver/rp2350/irq.d index fe24fd8..d461f77 100644 --- a/src/sys/rp2350/irq.d +++ b/src/urt/driver/rp2350/irq.d @@ -2,7 +2,7 @@ // // Cortex-M33 uses the standard ARM NVIC (Nested Vectored Interrupt Controller). // RP2350 has 52 peripheral interrupts (IRQ 0-51). -module sys.rp2350.irq; +module urt.driver.rp2350.irq; @nogc nothrow: diff --git a/src/sys/rp2350/package.d b/src/urt/driver/rp2350/package.d similarity index 94% rename from src/sys/rp2350/package.d rename to src/urt/driver/rp2350/package.d index f02ddf1..b19fd8d 100644 --- a/src/sys/rp2350/package.d +++ b/src/urt/driver/rp2350/package.d @@ -2,13 +2,13 @@ // // Provides sys_init() as the single entry point for all // hardware initialization. Called from start.S before main(). -module sys.rp2350; +module urt.driver.rp2350; -public import sys.rp2350.uart; -public import sys.rp2350.irq; -public import sys.rp2350.timer; +public import urt.driver.rp2350.uart; +public import urt.driver.rp2350.irq; +public import urt.driver.rp2350.timer; -import sys.baremetal.uart : UartConfig; +import urt.driver.uart : UartConfig; import core.volatile; @nogc nothrow: diff --git a/src/sys/rp2350/start.S b/src/urt/driver/rp2350/start.S similarity index 100% rename from src/sys/rp2350/start.S rename to src/urt/driver/rp2350/start.S diff --git a/src/sys/rp2350/syscalls.d b/src/urt/driver/rp2350/syscalls.d similarity index 94% rename from src/sys/rp2350/syscalls.d rename to src/urt/driver/rp2350/syscalls.d index 1614115..bb603b4 100644 --- a/src/sys/rp2350/syscalls.d +++ b/src/urt/driver/rp2350/syscalls.d @@ -2,7 +2,7 @@ // // Minimal stubs to satisfy picolibc's syscall requirements. // Same pattern as BL618 -- most are no-ops for baremetal. -module sys.rp2350.syscalls; +module urt.driver.rp2350.syscalls; @nogc nothrow: @@ -28,7 +28,7 @@ extern(C) void* _sbrk(ptrdiff_t incr) extern(C) int _write(int fd, const void* buf, size_t count) { - import sys.rp2350.uart : uart0_hw_puts; + import urt.driver.rp2350.uart : uart0_hw_puts; if (fd == 1 || fd == 2) uart0_hw_puts((cast(const(char)*)buf)[0 .. count]); return cast(int)count; diff --git a/src/sys/rp2350/timer.d b/src/urt/driver/rp2350/timer.d similarity index 98% rename from src/sys/rp2350/timer.d rename to src/urt/driver/rp2350/timer.d index 1bd4b64..dd7c64d 100644 --- a/src/sys/rp2350/timer.d +++ b/src/urt/driver/rp2350/timer.d @@ -5,7 +5,7 @@ // // RP2350 also has a 64-bit microsecond timer at 0x400B_0000 (TIMER0) // which can be used for wall-clock time -- not yet implemented here. -module sys.rp2350.timer; +module urt.driver.rp2350.timer; import core.volatile; diff --git a/src/sys/rp2350/uart.d b/src/urt/driver/rp2350/uart.d similarity index 98% rename from src/sys/rp2350/uart.d rename to src/urt/driver/rp2350/uart.d index 4807ca0..ccee2a1 100644 --- a/src/sys/rp2350/uart.d +++ b/src/urt/driver/rp2350/uart.d @@ -5,11 +5,11 @@ // UART1 0x4007_8000 General purpose (GPIO4=TX, GPIO5=RX typical) // // PL011 register layout (ARM PrimeCell UART). -module sys.rp2350.uart; +module urt.driver.rp2350.uart; import core.volatile; -import sys.baremetal.uart : Parity, StopBits, UartConfig; +import urt.driver.uart : Parity, StopBits, UartConfig; nothrow @nogc: diff --git a/src/sys/stm32/alloc.d b/src/urt/driver/stm32/alloc.d similarity index 96% rename from src/sys/stm32/alloc.d rename to src/urt/driver/stm32/alloc.d index 9136bf8..806bbd5 100644 --- a/src/sys/stm32/alloc.d +++ b/src/urt/driver/stm32/alloc.d @@ -1,4 +1,4 @@ -module sys.stm32.alloc; +module urt.driver.stm32.alloc; import urt.mem.alloc : MemFlags; diff --git a/src/sys/stm32/irq.d b/src/urt/driver/stm32/irq.d similarity index 98% rename from src/sys/stm32/irq.d rename to src/urt/driver/stm32/irq.d index 397e6e6..d99f6c6 100644 --- a/src/sys/stm32/irq.d +++ b/src/urt/driver/stm32/irq.d @@ -2,7 +2,7 @@ // // Cortex-M4/M7 use the standard ARM NVIC (Nested Vectored Interrupt Controller). // STM32F4xx has up to 82 peripheral interrupts, STM32F7xx up to 98. -module sys.stm32.irq; +module urt.driver.stm32.irq; @nogc nothrow: diff --git a/src/sys/stm32/package.d b/src/urt/driver/stm32/package.d similarity index 94% rename from src/sys/stm32/package.d rename to src/urt/driver/stm32/package.d index d7e914f..4137525 100644 --- a/src/sys/stm32/package.d +++ b/src/urt/driver/stm32/package.d @@ -6,13 +6,13 @@ // At reset, STM32 runs on HSI at 16 MHz (no PLL). // sys_init brings up USART1 for console output and SysTick. // PLL configuration for full-speed operation is not yet implemented. -module sys.stm32; +module urt.driver.stm32; -public import sys.stm32.uart; -public import sys.stm32.irq; -public import sys.stm32.timer; +public import urt.driver.stm32.uart; +public import urt.driver.stm32.irq; +public import urt.driver.stm32.timer; -import sys.baremetal.uart : UartConfig; +import urt.driver.uart : UartConfig; import core.volatile; @nogc nothrow: diff --git a/src/sys/stm32/start.S b/src/urt/driver/stm32/start.S similarity index 100% rename from src/sys/stm32/start.S rename to src/urt/driver/stm32/start.S diff --git a/src/sys/stm32/syscalls.d b/src/urt/driver/stm32/syscalls.d similarity index 94% rename from src/sys/stm32/syscalls.d rename to src/urt/driver/stm32/syscalls.d index ab4bba5..3c5e95c 100644 --- a/src/sys/stm32/syscalls.d +++ b/src/urt/driver/stm32/syscalls.d @@ -2,7 +2,7 @@ // // Minimal stubs to satisfy picolibc's syscall requirements. // Same pattern as RP2350 -- most are no-ops for baremetal. -module sys.stm32.syscalls; +module urt.driver.stm32.syscalls; @nogc nothrow: @@ -28,7 +28,7 @@ extern(C) void* _sbrk(ptrdiff_t incr) extern(C) int _write(int fd, const void* buf, size_t count) { - import sys.stm32.uart : uart0_hw_puts; + import urt.driver.stm32.uart : uart0_hw_puts; if (fd == 1 || fd == 2) uart0_hw_puts((cast(const(char)*)buf)[0 .. count]); return cast(int)count; diff --git a/src/sys/stm32/timer.d b/src/urt/driver/stm32/timer.d similarity index 98% rename from src/sys/stm32/timer.d rename to src/urt/driver/stm32/timer.d index 52a1a14..7b546a8 100644 --- a/src/sys/stm32/timer.d +++ b/src/urt/driver/stm32/timer.d @@ -6,7 +6,7 @@ // // SysTick keeps running during WFI (SLEEP mode) since AHB stays active, // unlike DWT CYCCNT which stops on sleep. -module sys.stm32.timer; +module urt.driver.stm32.timer; import core.volatile; diff --git a/src/sys/stm32/uart.d b/src/urt/driver/stm32/uart.d similarity index 98% rename from src/sys/stm32/uart.d rename to src/urt/driver/stm32/uart.d index dfe346f..e6908a8 100644 --- a/src/sys/stm32/uart.d +++ b/src/urt/driver/stm32/uart.d @@ -4,11 +4,11 @@ // USART1-6 on F4, USART1-6 + UART7-8 on F7. // // Default console: USART1 on PA9 (TX) / PA10 (RX), configured in sys_init. -module sys.stm32.uart; +module urt.driver.stm32.uart; import core.volatile; -import sys.baremetal.uart : Parity, StopBits, UartConfig; +import urt.driver.uart : Parity, StopBits, UartConfig; nothrow @nogc: diff --git a/src/sys/baremetal/timer.d b/src/urt/driver/timer.d similarity index 94% rename from src/sys/baremetal/timer.d rename to src/urt/driver/timer.d index 79971ef..f5170a7 100644 --- a/src/sys/baremetal/timer.d +++ b/src/urt/driver/timer.d @@ -1,21 +1,21 @@ -module sys.baremetal.timer; +module urt.driver.timer; import urt.time : Duration, dur; version (BL808_M0) - public import sys.bl618.timer; + public import urt.driver.bl618.timer; else version (BL808) - public import sys.bl808.timer; + public import urt.driver.bl808.timer; else version (BL618) - public import sys.bl618.timer; + public import urt.driver.bl618.timer; else version (Beken) - public import sys.bk7231.timer; + public import urt.driver.bk7231.timer; else version (RP2350) - public import sys.rp2350.timer; + public import urt.driver.rp2350.timer; else version (STM32) - public import sys.stm32.timer; + public import urt.driver.stm32.timer; else version (Espressif) - public import sys.esp32.timer; + public import urt.driver.esp32.timer; else { enum uint mtime_freq_hz = 0; diff --git a/src/sys/baremetal/uart.d b/src/urt/driver/uart.d similarity index 97% rename from src/sys/baremetal/uart.d rename to src/urt/driver/uart.d index ce4762d..86f5ab4 100644 --- a/src/sys/baremetal/uart.d +++ b/src/urt/driver/uart.d @@ -1,22 +1,22 @@ -module sys.baremetal.uart; +module urt.driver.uart; import urt.result : Result, InternalResult; import urt.time : Duration; version (BL808_M0) - public import sys.bl618.uart; + public import urt.driver.bl618.uart; else version (BL808) - public import sys.bl808.uart; + public import urt.driver.bl808.uart; else version (BL618) - public import sys.bl618.uart; + public import urt.driver.bl618.uart; else version (Beken) - public import sys.bk7231.uart; + public import urt.driver.bk7231.uart; else version (RP2350) - public import sys.rp2350.uart; + public import urt.driver.rp2350.uart; else version (STM32) - public import sys.stm32.uart; + public import urt.driver.stm32.uart; else version (Espressif) - public import sys.esp32.uart; + public import urt.driver.esp32.uart; else enum uint num_uarts = 0; diff --git a/src/sys/baremetal/wifi.d b/src/urt/driver/wifi.d similarity index 99% rename from src/sys/baremetal/wifi.d rename to src/urt/driver/wifi.d index 8b03ba9..a602e60 100644 --- a/src/sys/baremetal/wifi.d +++ b/src/urt/driver/wifi.d @@ -1,9 +1,9 @@ -module sys.baremetal.wifi; +module urt.driver.wifi; import urt.result : Result, InternalResult; version (Espressif) - public import sys.esp32.wifi; + public import urt.driver.esp32.wifi; else enum uint num_wifi = 0; diff --git a/src/sys/windows/alloc.d b/src/urt/driver/windows/alloc.d similarity index 96% rename from src/sys/windows/alloc.d rename to src/urt/driver/windows/alloc.d index e179024..5642470 100644 --- a/src/sys/windows/alloc.d +++ b/src/urt/driver/windows/alloc.d @@ -1,9 +1,12 @@ -module sys.windows.alloc; +module urt.driver.windows.alloc; + +version (Windows): import urt.mem.alloc : MemFlags; nothrow @nogc: + enum has_realloc = true; enum has_expand = true; enum has_memsize = true; diff --git a/src/sys/windows/ble.d b/src/urt/driver/windows/ble.d similarity index 99% rename from src/sys/windows/ble.d rename to src/urt/driver/windows/ble.d index 4ec154d..d5e4063 100644 --- a/src/sys/windows/ble.d +++ b/src/urt/driver/windows/ble.d @@ -8,7 +8,7 @@ // // Windows has one BLE radio (port 0). Multiple radios are not // distinguished by WinRT -- it uses the system default adapter. -module sys.windows.ble; +module urt.driver.windows.ble; version (Windows): @@ -18,7 +18,7 @@ import urt.mem.allocator : defaultAllocator; import urt.thread : ThreadSafeQueue; import urt.uuid : GUID; -import sys.baremetal.ble; +import urt.driver.ble; nothrow @nogc: diff --git a/src/sys/windows/exception.d b/src/urt/driver/windows/exception.d similarity index 99% rename from src/sys/windows/exception.d rename to src/urt/driver/windows/exception.d index 75d62a8..433929c 100644 --- a/src/sys/windows/exception.d +++ b/src/urt/driver/windows/exception.d @@ -8,7 +8,7 @@ /// /// Stack-trace capture / symbol resolution uses DbgHelp (debug-only). /// Ported from druntime. -module sys.windows.exception; +module urt.driver.windows.exception; version (Windows): diff --git a/src/urt/driver/windows/fibre.d b/src/urt/driver/windows/fibre.d new file mode 100644 index 0000000..81aca31 --- /dev/null +++ b/src/urt/driver/windows/fibre.d @@ -0,0 +1,92 @@ +module urt.driver.windows.fibre; + +version (Windows): + +import urt.fibre : cothread_t, coentry_t; +import urt.mem; +import urt.internal.sys.windows.winbase; + +nothrow @nogc: + + +version (X86_64) +{ + import urt.system : NT_TIB, __readgsqword; + + void* GetCurrentFiber() + => cast(void*)__readgsqword(NT_TIB.FiberData.offsetof); + + void* GetFiberData() + => *cast(void**)__readgsqword(NT_TIB.FiberData.offsetof); +} +else version (X86) +{ + import urt.system : NT_TIB, __readfsdword; + + void* GetCurrentFiber() + => cast(void*)__readfsdword(NT_TIB.FiberData.offsetof); + + void* GetFiberData() + => *cast(void**)__readfsdword(NT_TIB.FiberData.offsetof); +} + +struct co_fibre_data +{ + void* fiber; + void* user_data; + uint stack_size; + coentry_t coentry; +} +co_fibre_data thread_fiber_data; + +inout(co_fibre_data)* co_get_fibre_data(inout cothread_t fibre) pure + => cast(co_fibre_data*)fibre; + +cothread_t co_active() +{ + if(!thread_fiber_data.fiber) + thread_fiber_data.fiber = ConvertThreadToFiber(&thread_fiber_data); + return GetFiberData(); +} + +void* co_data() + => (cast(co_fibre_data*)GetFiberData()).user_data; + +cothread_t co_derive(void[] memory, coentry_t entry, void* data) +{ + // Windows fibers do not allow users to supply their own memory + return null; +} + +cothread_t co_create(size_t stack_size, coentry_t entry, void* data) +{ + assert(stack_size <= uint.max, "Stack size too large"); + + co_active(); + + extern(Windows) static void co_thunk(void* codata) + { + (cast(co_fibre_data*)codata).coentry(); + assert(false, "Error: returned from fibre!"); + } + + auto fdata = defaultAllocator().allocT!co_fibre_data(); + fdata.user_data = data; + fdata.coentry = entry; + fdata.stack_size = cast(uint)stack_size; + fdata.fiber = CreateFiber(stack_size, &co_thunk, fdata); + return fdata; +} + +void co_delete(cothread_t cothread) +{ + auto fdata = cast(co_fibre_data*)cothread; + DeleteFiber(fdata.fiber); + defaultAllocator().freeT(fdata); +} + +void co_switch(cothread_t cothread) +{ + auto fdata = cast(co_fibre_data*)cothread; + SwitchToFiber(fdata.fiber); +} diff --git a/src/urt/exception.d b/src/urt/exception.d index d1ebc3f..f98ffbf 100644 --- a/src/urt/exception.d +++ b/src/urt/exception.d @@ -29,7 +29,7 @@ void urt_assert(string file, size_t line, string msg) nothrow @nogc version (BareMetal) { - import sys.baremetal.uart : uart0_puts; + import urt.driver.uart : uart0_puts; import urt.mem.temp : tconcat; uart0_puts(tconcat("\n*** ASSERT: ", msg, " at ", file, ':', line, '\n')); while (true) {} diff --git a/src/urt/fibre.d b/src/urt/fibre.d index 2952503..221ad0d 100644 --- a/src/urt/fibre.d +++ b/src/urt/fibre.d @@ -339,190 +339,13 @@ unittest //------------------------------------------- nothrow: -alias cothread_t = void*; -alias coentry_t = void function() nothrow @nogc; +package alias cothread_t = void*; +package alias coentry_t = void function() nothrow @nogc; version (UseWindowsFibreAPI) -{ - import urt.internal.sys.windows.winbase; - - version (X86_64) - { - import urt.system : NT_TIB, __readgsqword; - - void* GetCurrentFiber() - => cast(void*)__readgsqword(NT_TIB.FiberData.offsetof); - - void* GetFiberData() - => *cast(void**)__readgsqword(NT_TIB.FiberData.offsetof); - } - else version (X86) - { - import urt.system : NT_TIB, __readfsdword; - - void* GetCurrentFiber() - => cast(void*)__readfsdword(NT_TIB.FiberData.offsetof); - - void* GetFiberData() - => *cast(void**)__readfsdword(NT_TIB.FiberData.offsetof); - } - - struct co_fibre_data - { - void* fiber; - void* user_data; - uint stack_size; - coentry_t coentry; - } - co_fibre_data thread_fiber_data; - - private inout(co_fibre_data)* co_get_fibre_data(inout cothread_t fibre) pure - => cast(co_fibre_data*)fibre; - - cothread_t co_active() - { - if(!thread_fiber_data.fiber) - thread_fiber_data.fiber = ConvertThreadToFiber(&thread_fiber_data); - return GetFiberData(); - } - - void* co_data() - => (cast(co_fibre_data*)GetFiberData()).user_data; - - cothread_t co_derive(void[] memory, coentry_t entry, void* data) - { - // Windows fibers do not allow users to supply their own memory - return null; - } - - cothread_t co_create(size_t stack_size, coentry_t entry, void* data) - { - assert(stack_size <= uint.max, "Stack size too large"); - - co_active(); - - extern(Windows) static void co_thunk(void* codata) - { - (cast(co_fibre_data*)codata).coentry(); - assert(false, "Error: returned from fibre!"); - } - - auto fdata = defaultAllocator().allocT!co_fibre_data(); - fdata.user_data = data; - fdata.coentry = entry; - fdata.stack_size = cast(uint)stack_size; - fdata.fiber = CreateFiber(stack_size, &co_thunk, fdata); - return fdata; - } - - void co_delete(cothread_t cothread) - { - auto fdata = cast(co_fibre_data*)cothread; - DeleteFiber(fdata.fiber); - defaultAllocator().freeT(fdata); - } - - void co_switch(cothread_t cothread) - { - auto fdata = cast(co_fibre_data*)cothread; - SwitchToFiber(fdata.fiber); - } -} + public import urt.driver.windows.fibre; else version (FreeRTOS) -{ - // FreeRTOS task-based fibre implementation. - // Each fibre is a FreeRTOS task that blocks on a task notification. - // co_switch does a notify-target + wait-for-notification handoff, - // giving us cooperative coroutine semantics on top of FreeRTOS. - - // C wrappers for FreeRTOS macros (in platforms/esp32s3/main/freertos_fibre.c) - extern(C) int ow_task_create(void function(void*), const(char)*, uint, void*, uint, void**) nothrow @nogc; - extern(C) void ow_task_delete(void*) nothrow @nogc; - extern(C) void* ow_task_current() nothrow @nogc; - extern(C) void ow_task_notify_give(void*) nothrow @nogc; - extern(C) uint ow_task_notify_take(uint) nothrow @nogc; - extern(C) uint ow_task_priority_get(void*) nothrow @nogc; - - struct co_fibre_data - { - void* user_data; - uint stack_size; - void* task_handle; - coentry_t coentry; - } - - co_fibre_data main_fibre_data; - cothread_t co_active_handle = null; - - private inout(co_fibre_data)* co_get_fibre_data(inout cothread_t fibre) pure - => cast(co_fibre_data*)fibre; - - cothread_t co_active() - { - if (!co_active_handle) - { - main_fibre_data.task_handle = ow_task_current(); - co_active_handle = &main_fibre_data; - } - return co_active_handle; - } - - void* co_data() - => (cast(co_fibre_data*)co_active_handle).user_data; - - cothread_t co_derive(void[] memory, coentry_t entry, void* data) - { - return null; // not supported with FreeRTOS tasks - } - - extern(C) static void co_freertos_entry(void* param) nothrow @nogc - { - co_active_handle = cast(cothread_t)param; // set TLS for this task - ow_task_notify_take(0xFFFFFFFF); // block until first co_switch - (cast(co_fibre_data*)param).coentry(); - } - - cothread_t co_create(size_t stack_size, coentry_t entry, void* data) - { - assert(stack_size <= uint.max, "Stack size too large"); - co_active(); // ensure main fibre initialized - - auto fdata = defaultAllocator().allocT!co_fibre_data(); - if (!fdata) return null; - - fdata.user_data = data; - fdata.stack_size = cast(uint)stack_size; - fdata.coentry = entry; - - auto priority = ow_task_priority_get(null); - - if (!ow_task_create(&co_freertos_entry, "fibre", cast(uint)stack_size, - fdata, priority, &fdata.task_handle)) - { - defaultAllocator().freeT(fdata); - return null; - } - - return fdata; - } - - void co_delete(cothread_t handle) - { - auto fdata = cast(co_fibre_data*)handle; - if (fdata && fdata != &main_fibre_data) - { - if (fdata.task_handle) - ow_task_delete(fdata.task_handle); - defaultAllocator().freeT(fdata); - } - } - - void co_switch(cothread_t handle) - { - ow_task_notify_give((cast(co_fibre_data*)handle).task_handle); - ow_task_notify_take(0xFFFFFFFF); - } -} + public import urt.driver.freertos.fibre; else { align(16) struct co_fibre_data diff --git a/src/urt/internal/dwarfeh.d b/src/urt/internal/dwarfeh.d index 1ae55d6..71a4f87 100644 --- a/src/urt/internal/dwarfeh.d +++ b/src/urt/internal/dwarfeh.d @@ -6,8 +6,8 @@ /// DMD: _d_throwdwarf, __dmd_begin_catch, __dmd_personality_v0 /// LDC: _d_throw_exception, _d_eh_enter_catch, _d_eh_personality /// -/// Lives in urt.internal (not sys.posix) so it compiles on bare-metal -/// builds whose Makefile source list excludes sys/posix/**. +/// Lives in urt.internal (not urt.driver.posix) so it compiles on bare-metal +/// builds whose Makefile source list excludes urt/driver/posix/**. /// /// Ported from druntime rt/dwarfeh.d. module urt.internal.dwarfeh; diff --git a/src/urt/internal/exception.d b/src/urt/internal/exception.d index 15b2b11..5ef5a96 100644 --- a/src/urt/internal/exception.d +++ b/src/urt/internal/exception.d @@ -1,13 +1,13 @@ module urt.internal.exception; version (Windows) - import sys.windows.exception : _capture_trace, _caller_address, _resolve_address, _resolve_batch; + import urt.driver.windows.exception : _capture_trace, _caller_address, _resolve_address, _resolve_batch; else version (Espressif) - import sys.esp32.exception : _capture_trace, _caller_address, _resolve_address, _resolve_batch; + import urt.driver.esp32.exception : _capture_trace, _caller_address, _resolve_address, _resolve_batch; else version (BareMetal) - import sys.baremetal.exception : _capture_trace, _caller_address, _resolve_address, _resolve_batch; + import urt.driver.baremetal.exception : _capture_trace, _caller_address, _resolve_address, _resolve_batch; else - import sys.posix.exception : _capture_trace, _caller_address, _resolve_address, _resolve_batch; + import urt.driver.posix.exception : _capture_trace, _caller_address, _resolve_address, _resolve_batch; nothrow @nogc: diff --git a/src/urt/io.d b/src/urt/io.d index add2de3..bc6833d 100644 --- a/src/urt/io.d +++ b/src/urt/io.d @@ -25,7 +25,7 @@ template write_to(WriteTarget target, bool newline = false) } else version (FreeStanding) { - import sys.baremetal.uart : uart0_puts; + import urt.driver.uart : uart0_puts; uart0_puts(str); static if (newline) uart0_puts("\n"); diff --git a/src/urt/mem/alloc.d b/src/urt/mem/alloc.d index 208147b..f915b86 100644 --- a/src/urt/mem/alloc.d +++ b/src/urt/mem/alloc.d @@ -170,27 +170,27 @@ MemFlags get_flags(void* ptr) pure version (Espressif) - public import sys.esp32.alloc; + public import urt.driver.esp32.alloc; else version (BL808_M0) - public import sys.bl618.alloc; + public import urt.driver.bl618.alloc; else version (BL808) - public import sys.bl808.alloc; + public import urt.driver.bl808.alloc; else version (BL618) - public import sys.bl618.alloc; + public import urt.driver.bl618.alloc; else version (RP2350) - public import sys.rp2350.alloc; + public import urt.driver.rp2350.alloc; else version (BK7231N) - public import sys.bk7231.alloc; + public import urt.driver.bk7231.alloc; else version (BK7231T) - public import sys.bk7231.alloc; + public import urt.driver.bk7231.alloc; else version (STM32F4) - public import sys.stm32.alloc; + public import urt.driver.stm32.alloc; else version (STM32F7) - public import sys.stm32.alloc; + public import urt.driver.stm32.alloc; else version (Windows) - public import sys.windows.alloc; + public import urt.driver.windows.alloc; else version (Posix) - public import sys.posix.alloc; + public import urt.driver.posix.alloc; else static assert(false, "No alloc driver for this platform"); diff --git a/src/urt/package.d b/src/urt/package.d index 67b1000..5ac2f72 100644 --- a/src/urt/package.d +++ b/src/urt/package.d @@ -46,7 +46,7 @@ extern(C) int main(int argc, char** argv) nothrow @nogc @trusted import urt.time : init_clock; init_clock(); - import sys.baremetal.uart : uart_init, uart_deinit; + import urt.driver.uart : uart_init, uart_deinit; uart_init(); import urt.rand; diff --git a/src/urt/system.d b/src/urt/system.d index f3febc2..3619523 100644 --- a/src/urt/system.d +++ b/src/urt/system.d @@ -42,8 +42,8 @@ void sleep(Duration duration) } else version (Embedded) { - import sys.baremetal.timer; - import sys.baremetal.irq; + import urt.driver.timer; + import urt.driver.irq; static if (has_mtime) { @@ -198,7 +198,7 @@ unittest version (Embedded) { - import sys.baremetal.irq; + import urt.driver.irq; static if (has_irq_diagnostics) { writelnf(" IRQ total: {0}", irq_count); diff --git a/src/urt/time.d b/src/urt/time.d index 5e9f2b1..2111507 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -14,7 +14,7 @@ else version (Posix) } else version (Embedded) { - import sys.baremetal.timer; + import urt.driver.timer; } nothrow @nogc: From fa9d831c1cd2bb17d4e93abb350d99a3bd026d24 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 23 Apr 2026 01:01:12 +1000 Subject: [PATCH 133/138] Eliminate em-dash! --- src/object.d | 56 +++++++++++++------------- src/std/math.d | 2 +- src/urt/atomic.d | 4 +- src/urt/crypto/pki.d | 6 +-- src/urt/driver/bl618/package.d | 8 ++-- src/urt/driver/bl618/syscalls.d | 2 +- src/urt/driver/bl808/gpio.d | 2 +- src/urt/driver/bl808/ipc.d | 2 +- src/urt/driver/bl808/package.d | 10 ++--- src/urt/driver/bl808/timer.d | 20 ++++----- src/urt/driver/bl808/uart.d | 22 +++++----- src/urt/driver/bl808/xram.d | 2 +- src/urt/driver/wifi.d | 4 +- src/urt/fibre.d | 2 +- src/urt/internal/aa.d | 2 +- src/urt/internal/mbedtls.c | 4 +- src/urt/internal/mbedtls.d | 2 +- src/urt/internal/os.c | 2 +- src/urt/internal/stdc/stdio.d | 4 +- src/urt/internal/stdc/stdlib.d | 2 +- src/urt/internal/sys/windows/package.d | 2 +- src/urt/internal/sys/windows/winuser.d | 2 +- src/urt/io.d | 2 +- src/urt/math.d | 6 +-- 24 files changed, 85 insertions(+), 85 deletions(-) diff --git a/src/object.d b/src/object.d index 73f56be..0521480 100644 --- a/src/object.d +++ b/src/object.d @@ -1,4 +1,4 @@ -// Minimal object.d — replaces druntime's implicit root module. +// Minimal object.d - replaces druntime's implicit root module. // // This file is the auditing frontier: every symbol added here is a // symbol the compiler or linker demanded. Keep it as small as possible. @@ -129,7 +129,7 @@ static if (__VERSION__ >= 2113) } // ────────────────────────────────────────────────────────────────────── -// Object — root of the class hierarchy +// Object - root of the class hierarchy // ────────────────────────────────────────────────────────────────────── class Object @@ -154,7 +154,7 @@ class Object => try_copy_string(buffer, "Object"); } -// Free-function opEquals for class types — the compiler lowers `a == b` +// Free-function opEquals for class types - the compiler lowers `a == b` // on class objects to a call to this function. bool opEquals(LHS, RHS)(LHS lhs, RHS rhs) if ((is(LHS : const Object) || is(LHS : const shared Object)) && @@ -177,7 +177,7 @@ bool opEquals(LHS, RHS)(LHS lhs, RHS rhs) } // ────────────────────────────────────────────────────────────────────── -// TypeInfo — compiler generates references for typeid, AAs, etc. +// TypeInfo - compiler generates references for typeid, AAs, etc. // ────────────────────────────────────────────────────────────────────── class TypeInfo @@ -290,7 +290,7 @@ class TypeInfo_Class : TypeInfo } } -// TypeInfo_Struct — compiler generates static instances for every struct type. +// TypeInfo_Struct - compiler generates static instances for every struct type. // Field layout must exactly match what the compiler emits. class TypeInfo_Struct : TypeInfo { @@ -354,7 +354,7 @@ class TypeInfo_Struct : TypeInfo string mangledName; @property string name() nothrow const @trusted - => mangledName; // no demangling — avoids pulling in core.demangle + => mangledName; // no demangling - avoids pulling in core.demangle void[] m_init; @@ -450,7 +450,7 @@ class TypeInfo_Array : TypeInfo TypeInfo value; } -// Built-in array TypeInfo subclasses — the compiler generates references to +// Built-in array TypeInfo subclasses - the compiler generates references to // these for common array types. Empty subclasses are sufficient; the // compiler fills in the `value` field. class TypeInfo_Ah : TypeInfo_Array {} // ubyte[] @@ -618,7 +618,7 @@ class TypeInfo_Inout : TypeInfo_Const } // ────────────────────────────────────────────────────────────────────── -// TypeInfo for built-in types — compiler references these by mangled name +// TypeInfo for built-in types - compiler references these by mangled name // (e.g. TypeInfo_k for uint). Single-character suffixes follow D's type // encoding: a=char, b=bool, d=double, f=float, g=byte, h=ubyte, // i=int, k=uint, l=long, m=ulong, o=dchar, s=short, t=wchar, @@ -662,7 +662,7 @@ private struct _member_func } // ────────────────────────────────────────────────────────────────────── -// __ArrayDtor — compiler lowers dynamic array destruction to this +// __ArrayDtor - compiler lowers dynamic array destruction to this // ────────────────────────────────────────────────────────────────────── void __ArrayDtor(T)(scope T[] a) @@ -672,7 +672,7 @@ void __ArrayDtor(T)(scope T[] a) } // ────────────────────────────────────────────────────────────────────── -// Compiler hook templates — array & AA literal lowering +// Compiler hook templates - array & AA literal lowering // These are only called at runtime; CTFE evaluates literals directly. // ────────────────────────────────────────────────────────────────────── @@ -733,7 +733,7 @@ ref Tarr _d_arrayappendT(Tarr : T[], T)(return ref scope Tarr x, scope Tarr y) @ } // ────────────────────────────────────────────────────────────────────── -// Compiler hook templates — array operations, construction, etc. +// Compiler hook templates - array operations, construction, etc. // These are lowered by the compiler for various language constructs. // ────────────────────────────────────────────────────────────────────── @@ -916,7 +916,7 @@ extern(C) void _d_arraybounds(string file, uint line) nothrow @nogc assert_handler()(file, line, "array index out of bounds"); } -// Unittest assert hooks — the compiler generates these for assert() inside +// Unittest assert hooks - the compiler generates these for assert() inside // unittest blocks instead of the regular _d_assertp/_d_assert_msg. extern(C) void _d_unittestp(immutable(char)* file, uint line) nothrow @nogc @trusted { @@ -938,7 +938,7 @@ extern(C) void _d_unittest(string file, uint line) nothrow @nogc assert_handler()(file, line, "unittest assertion failure"); } -// GC allocation hook — compiler lowers `new` to this. In our @nogc world +// GC allocation hook - compiler lowers `new` to this. In our @nogc world // it should never be called from production code; provided so unittest // blocks that accidentally use `new` can at least link. extern(C) void* _d_allocmemory(size_t sz) nothrow @nogc @trusted @@ -1056,7 +1056,7 @@ extern(C) void[] _d_newarrayU(const TypeInfo ti, size_t length) nothrow @nogc @t return alloc(length * elemsize); } -// Unconditional halt — avoids circular dependency with assert. +// Unconditional halt - avoids circular dependency with assert. private void _halt() nothrow @nogc @trusted { version (D_InlineAsm_X86_64) @@ -1110,7 +1110,7 @@ template _d_delstructImpl(T) nothrow @nogc @trusted pure extern(C) void _d_delThrowable(scope Throwable) {} // ────────────────────────────────────────────────────────────────────── -// _arrayOp — compiler hook for vectorized array slice operations. +// _arrayOp - compiler hook for vectorized array slice operations. // DMD lowers `dest[] = a[] ^ b[]` to `_arrayOp!(T[], T[], T[], "^", "=")(dest, a, b)`. // Args are in Reverse Polish Notation (RPN). // ────────────────────────────────────────────────────────────────────── @@ -1223,7 +1223,7 @@ private enum _scalar_exp(Args...) = () { }(); // ────────────────────────────────────────────────────────────────────── -// _d_cast — dynamic class cast, walks TypeInfo_Class.base chain +// _d_cast - dynamic class cast, walks TypeInfo_Class.base chain // ────────────────────────────────────────────────────────────────────── void* _d_cast(To, From)(From o) @trusted @@ -1245,12 +1245,12 @@ void* _d_cast(To, From)(From o) @trusted if (is(From == class) && is(To == interface)) { if (o is null) return null; - // Walk interface list — not implemented yet, fall back to null + // Walk interface list - not implemented yet, fall back to null assert(false, "Interface cast not yet implemented"); } // ────────────────────────────────────────────────────────────────────── -// Throwable / Exception / Error — exception hierarchy +// Throwable / Exception / Error - exception hierarchy // ────────────────────────────────────────────────────────────────────── class Throwable : Object @@ -1324,7 +1324,7 @@ class Error : Throwable } // ────────────────────────────────────────────────────────────────────── -// destroy — compiler generates calls for scope guards, etc. +// destroy - compiler generates calls for scope guards, etc. // ────────────────────────────────────────────────────────────────────── void destroy(bool initialize = true, T)(ref T obj) if (is(T == struct)) @@ -1369,7 +1369,7 @@ void destroy(bool initialize = true, T)(ref T obj) if (!is(T == struct) && !is(T } // ────────────────────────────────────────────────────────────────────── -// .dup / .idup — array duplication properties +// .dup / .idup - array duplication properties // ────────────────────────────────────────────────────────────────────── @property immutable(T)[] idup(T)(T[] a) @trusted @@ -1403,7 +1403,7 @@ bool _xopCmp(in void*, in void*) => false; // ────────────────────────────────────────────────────────────────────── -// hashOf — used by AAs and anywhere .toHash is needed +// hashOf - used by AAs and anywhere .toHash is needed // ────────────────────────────────────────────────────────────────────── size_t hashOf(T)(auto ref T val, size_t seed = 0) pure nothrow @nogc @trusted @@ -1419,7 +1419,7 @@ size_t hashOf(T)(auto ref T val, size_t seed = 0) pure nothrow @nogc @trusted } else static if (is(T V : V*)) { - // Pointers — CTFE compatible + // Pointers - CTFE compatible if (__ctfe) { if (val is null) @@ -1431,7 +1431,7 @@ size_t hashOf(T)(auto ref T val, size_t seed = 0) pure nothrow @nogc @trusted } else static if (__traits(isIntegral, T)) { - // Integers — CTFE compatible, no reinterpreting cast + // Integers - CTFE compatible, no reinterpreting cast static if (T.sizeof <= size_t.sizeof) return _fnv(cast(size_t)val, seed); else @@ -1450,7 +1450,7 @@ size_t hashOf(T)(auto ref T val, size_t seed = 0) pure nothrow @nogc @trusted } else static if (is(T == struct)) { - // Structs — hash each field (CTFE compatible) + // Structs - hash each field (CTFE compatible) size_t h = seed; foreach (ref field; val.tupleof) h = hashOf(field, h); @@ -1493,7 +1493,7 @@ private size_t _fnv(size_t val, size_t seed) pure nothrow @nogc @trusted } // ────────────────────────────────────────────────────────────────────── -// ModuleInfo — compiler emits one per module with ctor/dtor/unittest info. +// ModuleInfo - compiler emits one per module with ctor/dtor/unittest info. // Variable-sized: fields are packed after the header based on flag bits. // ────────────────────────────────────────────────────────────────────── @@ -1629,7 +1629,7 @@ const: } // ────────────────────────────────────────────────────────────────────── -// Module registration — LDC uses _Dmodule_ref linked list +// Module registration - LDC uses _Dmodule_ref linked list // // On ELF targets the compiler generates .init_array entries that chain // ModuleReference structs into _Dmodule_ref. On Linux, glibc's crt0 @@ -1673,14 +1673,14 @@ version (linux) } // ────────────────────────────────────────────────────────────────────── -// TypeInfo for const/immutable char[] — compiler references by name +// TypeInfo for const/immutable char[] - compiler references by name // ────────────────────────────────────────────────────────────────────── class TypeInfo_Axa : TypeInfo_Array {} // const(char)[] class TypeInfo_Aya : TypeInfo_Array {} // immutable(char)[] = string // ────────────────────────────────────────────────────────────────────── -// _d_invariant — contract invariant hook (matches rt.invariant_ mangling) +// _d_invariant - contract invariant hook (matches rt.invariant_ mangling) // ────────────────────────────────────────────────────────────────────── pragma(mangle, "_D2rt10invariant_12_d_invariantFC6ObjectZv") diff --git a/src/std/math.d b/src/std/math.d index e6a1a4a..0690df8 100644 --- a/src/std/math.d +++ b/src/std/math.d @@ -1,4 +1,4 @@ -/// Minimal std.math stub — provides just the `pow` function that DMD's +/// Minimal std.math stub - provides just the `pow` function that DMD's /// `^^` operator lowering requires. Nothing else from Phobos is pulled in. module std.math; diff --git a/src/urt/atomic.d b/src/urt/atomic.d index d29b495..2b6c3a0 100644 --- a/src/urt/atomic.d +++ b/src/urt/atomic.d @@ -23,7 +23,7 @@ alias atomicFetchSub = atomic_fetch_sub; version (LDC) { // ----------------------------------------------------------------------- - // LDC: LLVM intrinsics — architecture-generic + // LDC: LLVM intrinsics - architecture-generic // ----------------------------------------------------------------------- nothrow @nogc @safe: @@ -594,7 +594,7 @@ else version (D_InlineAsm_X86) { static if (ms != MemoryOrder.relaxed) { - // x86 without guaranteed SSE2 — mfence may not exist. + // x86 without guaranteed SSE2 - mfence may not exist. // lock; add is a full barrier on all x86. asm pure nothrow @nogc @trusted { diff --git a/src/urt/crypto/pki.d b/src/urt/crypto/pki.d index 40bd161..60c73cc 100644 --- a/src/urt/crypto/pki.d +++ b/src/urt/crypto/pki.d @@ -461,7 +461,7 @@ Result associate_key(ref CertRef cert, ref KeyPair key) return Result(cast(uint)ss); } - // export as PKCS8 — only PKCS8 supports named key import for persistence + // export as PKCS8 - only PKCS8 supports named key import for persistence ULONG pkcs8_size = 0; ss = NCryptExportKey(htemp, null, NCRYPT_PKCS8_PRIVATE_KEY_BLOB.ptr, null, null, 0, &pkcs8_size, 0); if (ss != 0) @@ -961,7 +961,7 @@ bool parse_ec_sec1_der(const(ubyte)[] der, ref ubyte[32] d, ref ubyte[32] x, ref d[32 - d_len .. 32] = der[pos .. pos + d_len]; // right-align pos += d_len; - // optional [0] parameters — skip + // optional [0] parameters - skip if (pos < seq_end && der[pos] == 0xa0) { ++pos; @@ -996,7 +996,7 @@ bool parse_ec_sec1_der(const(ubyte)[] der, ref ubyte[32] d, ref ubyte[32] x, ref return true; } - // No public key in the SEC 1 structure — can't reconstruct without EC math + // No public key in the SEC 1 structure - can't reconstruct without EC math return false; } diff --git a/src/urt/driver/bl618/package.d b/src/urt/driver/bl618/package.d index 28c93f9..d6a7007 100644 --- a/src/urt/driver/bl618/package.d +++ b/src/urt/driver/bl618/package.d @@ -18,9 +18,9 @@ private ubyte[48] __eh_frame_object; // pre-allocated storage for libgcc /// Call once at the top of main() before any other OpenWatt code. /// /// Order matters: -/// 1. UART — so we have debug output for everything after -/// 2. IRQ table — already done by start.S (_init_interrupts) -/// 3. Timer — periodic tick for main loop +/// 1. UART - so we have debug output for everything after +/// 2. IRQ table - already done by start.S (_init_interrupts) +/// 3. Timer - periodic tick for main loop extern(C) void sys_init() { // Register .eh_frame with libgcc's unwinder so that DWARF exception @@ -37,5 +37,5 @@ extern(C) void sys_init() private void tick_stub() @nogc nothrow { - // placeholder — will drive urt.time / Application frame tick + // placeholder - will drive urt.time / Application frame tick } diff --git a/src/urt/driver/bl618/syscalls.d b/src/urt/driver/bl618/syscalls.d index fee9617..5212670 100644 --- a/src/urt/driver/bl618/syscalls.d +++ b/src/urt/driver/bl618/syscalls.d @@ -1,7 +1,7 @@ /// BL618 newlib/picolibc syscall stubs /// /// Minimal stubs to satisfy picolibc's syscall requirements. -/// Same pattern as BL808 — most are no-ops for baremetal. +/// Same pattern as BL808 - most are no-ops for baremetal. module urt.driver.bl618.syscalls; @nogc nothrow: diff --git a/src/urt/driver/bl808/gpio.d b/src/urt/driver/bl808/gpio.d index 63e7ee5..48c1f3c 100644 --- a/src/urt/driver/bl808/gpio.d +++ b/src/urt/driver/bl808/gpio.d @@ -26,7 +26,7 @@ enum uint GPIO_OUTPUT_HIGH = 1u << 17; // WS2812 LED pin on M1s Dock enum uint WS2812_PIN = 8; -// Timing loop counts — calibrated for ~480MHz D0 core clock. +// Timing loop counts - calibrated for ~480MHz D0 core clock. // WS2812B spec: T0H=400ns, T0L=850ns, T1H=800ns, T1L=450ns (±150ns). // At 480MHz: 1 cycle ≈ 2.08ns, so T0H ≈ 192 cycles, T1H ≈ 384 cycles. // Loop body (addi + bnez compressed) ≈ 2 cycles → divide by 2. diff --git a/src/urt/driver/bl808/ipc.d b/src/urt/driver/bl808/ipc.d index faaeb7f..83ef373 100644 --- a/src/urt/driver/bl808/ipc.d +++ b/src/urt/driver/bl808/ipc.d @@ -17,7 +17,7 @@ void ipc_init() { // TODO: read ring layout from XRAM_BASE // The M0 firmware sets up ring_pos structures at fixed offsets. - // For now this is a placeholder — actual offsets need to be + // For now this is a placeholder - actual offsets need to be // determined by examining the running M0 firmware's XRAM layout. } diff --git a/src/urt/driver/bl808/package.d b/src/urt/driver/bl808/package.d index 2ca8a6b..d5ef31d 100644 --- a/src/urt/driver/bl808/package.d +++ b/src/urt/driver/bl808/package.d @@ -20,10 +20,10 @@ private ubyte[48] __eh_frame_object; // pre-allocated storage for libgcc /// Call once at the top of main() before any other OpenWatt code. /// /// Order matters: -/// 1. UART — so we have debug output for everything after -/// 2. IRQ table — already done by start.S (_init_interrupts) -/// 3. Timer — periodic tick for main loop -/// 4. IPC — XRAM ring buffers to M0 +/// 1. UART - so we have debug output for everything after +/// 2. IRQ table - already done by start.S (_init_interrupts) +/// 3. Timer - periodic tick for main loop +/// 4. IPC - XRAM ring buffers to M0 extern(C) void sys_init() { // Register .eh_frame with libgcc's unwinder so that DWARF exception @@ -46,5 +46,5 @@ extern(C) void sys_init() private void tick_stub() @nogc nothrow { - // placeholder — will drive urt.time / Application frame tick + // placeholder - will drive urt.time / Application frame tick } diff --git a/src/urt/driver/bl808/timer.d b/src/urt/driver/bl808/timer.d index 5860897..8387af6 100644 --- a/src/urt/driver/bl808/timer.d +++ b/src/urt/driver/bl808/timer.d @@ -2,11 +2,11 @@ /// /// Two time sources: /// -/// 1. mtime (RISC-V standard) — 1 MHz monotonic counter. +/// 1. mtime (RISC-V standard) - 1 MHz monotonic counter. /// Read via rdtime. Survives WFI/clock scaling, resets on system reset. /// Used for monotonic timekeeping (getTime / Duration / Timer). /// -/// 2. HBN RTC — 32,768 Hz counter in the Hibernate block. +/// 2. HBN RTC - 32,768 Hz counter in the Hibernate block. /// 40-bit, survives deep sleep (HBN) if VBAT is maintained. /// Resets on full power cycle. Used with a stored UTC offset /// for wall-clock time across sleep/wake cycles. @@ -18,11 +18,11 @@ /// /// HBN layout: /// HBN_BASE = 0x2000_F000 -/// HBN_CTL @ +0x00 — bit 0: RTC enable -/// HBN_TIME_L @ +0x04 — compare value low (alarm) -/// HBN_TIME_H @ +0x08 — compare value high (alarm) -/// HBN_RTC_TIME_L @ +0x0C — latched counter low (read-only) -/// HBN_RTC_TIME_H @ +0x10 — latched counter high [7:0] + latch trigger [31] +/// HBN_CTL @ +0x00 - bit 0: RTC enable +/// HBN_TIME_L @ +0x04 - compare value low (alarm) +/// HBN_TIME_H @ +0x08 - compare value high (alarm) +/// HBN_RTC_TIME_L @ +0x0C - latched counter low (read-only) +/// HBN_RTC_TIME_H @ +0x10 - latched counter high [7:0] + latch trigger [31] module urt.driver.bl808.timer; import core.volatile; @@ -118,7 +118,7 @@ extern(C) void _timer_irq_handler() if (tick_interval > 0) { // Advance deadline relative to current compare value - // (not current time — avoids drift) + // (not current time - avoids drift) ulong cmp = mtimecmp_read(); mtimecmp_write(cmp + tick_interval); } @@ -173,7 +173,7 @@ private ulong mtimecmp_read() enum uint rtc_freq_hz = 32_768; /// Enable the HBN RTC counter (bit 0 of HBN_CTL). -/// Does NOT reset the counter — call rtc_reset() first if needed. +/// Does NOT reset the counter - call rtc_reset() first if needed. /// Note: read-modify-write on HBN_CTL is not interrupt-safe. /// If called after interrupts are enabled, wrap with mstatus.MIE guard. void rtc_enable() @@ -183,7 +183,7 @@ void rtc_enable() } /// Disable and reset the HBN RTC counter to zero. -/// Note: same mstatus.MIE guard applies — see rtc_enable(). +/// Note: same mstatus.MIE guard applies - see rtc_enable(). void rtc_reset() { auto ctl = cast(uint*)HBN_CTL; diff --git a/src/urt/driver/bl808/uart.d b/src/urt/driver/bl808/uart.d index 6bfefe3..ddea138 100644 --- a/src/urt/driver/bl808/uart.d +++ b/src/urt/driver/bl808/uart.d @@ -14,8 +14,8 @@ // UART1/2 require M0 cooperation or future GPIO mux support from D0. // // Interrupt availability from D0: -// UART3 — PLIC IRQ 20 (IRQ_NUM_BASE + 4), interrupt-driven -// UART0/1/2 — IRQs are in M0's PLIC domain, must be polled from D0 +// UART3 - PLIC IRQ 20 (IRQ_NUM_BASE + 4), interrupt-driven +// UART0/1/2 - IRQs are in M0's PLIC domain, must be polled from D0 // // All UARTs use software ring buffers (512 bytes RX, 512 bytes TX). // UART3 fills/drains them via ISR. UART0/1/2 require explicit uart_poll(). @@ -137,19 +137,19 @@ private enum : uint // FIFO depth private enum UART_FIFO_MAX = 32; -// RX FIFO threshold — interrupt fires when RX FIFO count >= this value. +// RX FIFO threshold - interrupt fires when RX FIFO count >= this value. // Set to 16 so we drain before the 32-byte FIFO overflows. private enum RX_FIFO_THRESHOLD = 16; -// TX FIFO threshold — interrupt fires when TX FIFO count >= this value +// TX FIFO threshold - interrupt fires when TX FIFO count >= this value // (i.e., space is available). Set low so we refill aggressively. private enum TX_FIFO_THRESHOLD = 8; -// RX timeout — number of bit periods of silence before RX timeout interrupt. +// RX timeout - number of bit periods of silence before RX timeout interrupt. // Catches partial frames shorter than RX_FIFO_THRESHOLD. private enum RX_TIMEOUT_BITS = 80; // ~10 byte times at any baud rate -// UART clock frequency — M0 sets all UARTs to XCLK = 40 MHz. +// UART clock frequency - M0 sets all UARTs to XCLK = 40 MHz. // TODO: read GLB_UART_CFG0 (GLB_BASE + 0x150) to determine actual clock // source. Bits: [2:0] = divider, [4] = clock enable, [7] = clk_sel, // [22] = clk_sel2. The 2-bit selector chooses between MCU PBCLK (0), @@ -157,7 +157,7 @@ private enum RX_TIMEOUT_BITS = 80; // ~10 byte times at any baud rate private enum uint UART_CLK_HZ = 40_000_000; -// Software ring buffer — reuse urt.mem.ring with fixed 512-byte capacity. +// Software ring buffer - reuse urt.mem.ring with fixed 512-byte capacity. import urt.mem.ring : RingBuffer; private alias Ring = RingBuffer!512; @@ -435,7 +435,7 @@ void fill_tx_fifo(uint id) } } -// PLIC IRQ handler — services UART3 interrupts. +// PLIC IRQ handler - services UART3 interrupts. // Chains to previous handler for non-UART IRQs. void uart_irq_handler(uint irq) { @@ -446,7 +446,7 @@ void uart_irq_handler(uint irq) immutable mask = reg_read(base, INT_MASK); immutable active = sts & ~mask; - // RX FIFO threshold or RX timeout — drain into ring + // RX FIFO threshold or RX timeout - drain into ring if (active & (INT_URX_FIFO | INT_URX_RTO)) { drain_rx_fifo(3); @@ -454,7 +454,7 @@ void uart_irq_handler(uint irq) reg_write(base, INT_CLEAR, INT_URX_RTO); } - // TX FIFO has space — refill from ring + // TX FIFO has space - refill from ring if (active & INT_UTX_FIFO) { fill_tx_fifo(3); @@ -526,7 +526,7 @@ void uart0_hex(ulong val) } } -// ── UART3 (COM8, MM domain — D0's console) ────────────────────────────────── +// ── UART3 (COM8, MM domain - D0's console) ────────────────────────────────── void uart3_putc(char c) { diff --git a/src/urt/driver/bl808/xram.d b/src/urt/driver/bl808/xram.d index 9e65fdf..6af9386 100644 --- a/src/urt/driver/bl808/xram.d +++ b/src/urt/driver/bl808/xram.d @@ -213,7 +213,7 @@ struct XramRing } } -/// Memory barrier — RISC-V fence instruction +/// Memory barrier - RISC-V fence instruction private void fence() @nogc nothrow { version (RISCV64) diff --git a/src/urt/driver/wifi.d b/src/urt/driver/wifi.d index a602e60..643e1bc 100644 --- a/src/urt/driver/wifi.d +++ b/src/urt/driver/wifi.d @@ -35,7 +35,7 @@ enum WifiVif : ubyte enum WifiMode : ubyte { none, // radio off, no virtual interfaces active - monitor, // radio on, raw 802.11 only — no stack + monitor, // radio on, raw 802.11 only - no stack sta, // station only ap, // access point only apsta, // concurrent AP + STA @@ -147,7 +147,7 @@ alias WifiRxCallback = void function(Wifi wifi, WifiVif vif, const(ubyte)[] data // Called from ISR/driver when a raw 802.11 frame is received // (promiscuous/monitor tap). Data is the full 802.11 frame // starting at the MAC header. Delivered independently of the -// Ethernet RX callback — both can be active simultaneously. +// Ethernet RX callback - both can be active simultaneously. alias WifiRawRxCallback = void function(Wifi wifi, const(ubyte)[] frame, byte rssi, ubyte channel) nothrow @nogc; // Called when a wifi event occurs. Replaces per-event callbacks diff --git a/src/urt/fibre.d b/src/urt/fibre.d index 221ad0d..535acec 100644 --- a/src/urt/fibre.d +++ b/src/urt/fibre.d @@ -708,7 +708,7 @@ else else static if (ProcFeatures.thumb) { // Thumb mode (Cortex-M): SP cannot appear in stm/ldm register lists. - // Layout: [r4..r11](0-28), [sp](32), [lr/pc](36) — matches co_init_stack + // Layout: [r4..r11](0-28), [sp](32), [lr/pc](36) - matches co_init_stack asm nothrow @nogc { ` diff --git a/src/urt/internal/aa.d b/src/urt/internal/aa.d index b597a43..c8e2fa6 100644 --- a/src/urt/internal/aa.d +++ b/src/urt/internal/aa.d @@ -1017,7 +1017,7 @@ version (none) unittest AA!(K, V) makeAA(K, V)(V[K] src) @trusted { assert(__ctfe, "makeAA Must only be called at compile time"); - // Iterate the built-in AA directly — .keys/.values are UFCS properties + // Iterate the built-in AA directly - .keys/.values are UFCS properties // that require druntime hooks we don't provide. K[] keys; V[] values; diff --git a/src/urt/internal/mbedtls.c b/src/urt/internal/mbedtls.c index 35d185d..113fa39 100644 --- a/src/urt/internal/mbedtls.c +++ b/src/urt/internal/mbedtls.c @@ -1,4 +1,4 @@ -// C wrappers for mbedtls — sizeof() for opaque types, and wrappers for +// C wrappers for mbedtls - sizeof() for opaque types, and wrappers for // functions that access internal struct layouts D cannot safely replicate. #if !defined(_WIN32) @@ -34,7 +34,7 @@ int urt_pk_gen_ec_p256_key(mbedtls_pk_context *pk, int (*f_rng)(void *, unsigned // Export the public key as an uncompressed EC point (0x04 || X || Y). // Returns 0 on success with *olen set to the number of bytes written. -// Uses mbedtls_pk_write_pubkey_der to avoid any direct ECP struct member access — +// Uses mbedtls_pk_write_pubkey_der to avoid any direct ECP struct member access - // the SubjectPublicKeyInfo for P-256 always ends with the 65-byte uncompressed point. int urt_pk_export_pubkey_xy(mbedtls_pk_context *pk, unsigned char *buf, size_t buflen, size_t *olen) { diff --git a/src/urt/internal/mbedtls.d b/src/urt/internal/mbedtls.d index db9dcfe..659c17a 100644 --- a/src/urt/internal/mbedtls.d +++ b/src/urt/internal/mbedtls.d @@ -1,4 +1,4 @@ -// minimal D bindings for mbedtls — only what pki.d and tls.d need +// minimal D bindings for mbedtls - only what pki.d and tls.d need module urt.internal.mbedtls; version (Posix): diff --git a/src/urt/internal/os.c b/src/urt/internal/os.c index 4fbd6fc..8841d6b 100644 --- a/src/urt/internal/os.c +++ b/src/urt/internal/os.c @@ -15,7 +15,7 @@ # include # include -// EWOULDBLOCK is #define EWOULDBLOCK EAGAIN on Linux — ImportC cannot resolve +// EWOULDBLOCK is #define EWOULDBLOCK EAGAIN on Linux - ImportC cannot resolve // chained macros, so re-define as a plain integer. # undef EWOULDBLOCK # define EWOULDBLOCK 11 /* same as EAGAIN on Linux */ diff --git a/src/urt/internal/stdc/stdio.d b/src/urt/internal/stdc/stdio.d index 5442939..7aabfa3 100644 --- a/src/urt/internal/stdc/stdio.d +++ b/src/urt/internal/stdc/stdio.d @@ -1,4 +1,4 @@ -// Minimal C stdio bindings — only what URT actually uses. +// Minimal C stdio bindings - only what URT actually uses. // FILE is opaque; we never dereference its fields. module urt.internal.stdc.stdio; @@ -103,7 +103,7 @@ else version (WASI) } else version (FreeStanding) { - // no stdio streams — io.d uses UART directly + // no stdio streams - io.d uses UART directly } else { diff --git a/src/urt/internal/stdc/stdlib.d b/src/urt/internal/stdc/stdlib.d index 7bc03c9..5760d26 100644 --- a/src/urt/internal/stdc/stdlib.d +++ b/src/urt/internal/stdc/stdlib.d @@ -1,4 +1,4 @@ -// Minimal C stdlib bindings — only what URT actually uses. +// Minimal C stdlib bindings - only what URT actually uses. module urt.internal.stdc.stdlib; diff --git a/src/urt/internal/sys/windows/package.d b/src/urt/internal/sys/windows/package.d index a30b8fa..91631b0 100644 --- a/src/urt/internal/sys/windows/package.d +++ b/src/urt/internal/sys/windows/package.d @@ -1,4 +1,4 @@ -/// Slim umbrella — re-exports only the vendored Windows modules. +/// Slim umbrella - re-exports only the vendored Windows modules. module urt.internal.sys.windows; public import urt.internal.sys.windows.w32api; diff --git a/src/urt/internal/sys/windows/winuser.d b/src/urt/internal/sys/windows/winuser.d index 0f524d5..f542160 100644 --- a/src/urt/internal/sys/windows/winuser.d +++ b/src/urt/internal/sys/windows/winuser.d @@ -1,4 +1,4 @@ -/// Minimal winuser bindings — only virtual key codes. +/// Minimal winuser bindings - only virtual key codes. /// Full winuser.d was trimmed; GUI/display/font APIs removed. module urt.internal.sys.windows.winuser; version (Windows): diff --git a/src/urt/io.d b/src/urt/io.d index bc6833d..4a23337 100644 --- a/src/urt/io.d +++ b/src/urt/io.d @@ -97,7 +97,7 @@ void flush(WriteTarget target = WriteTarget.stdout)() nothrow @nogc } else version (FreeStanding) { - // UART writes are unbuffered — nothing to flush + // UART writes are unbuffered - nothing to flush } else { diff --git a/src/urt/math.d b/src/urt/math.d index 15d1930..d53626f 100644 --- a/src/urt/math.d +++ b/src/urt/math.d @@ -159,7 +159,7 @@ auto pow(B, E)(B base, E exp) @trusted static if (isFloatE) { - // F ^^ F — handle integer-valued exponents (covers 99% of + // F ^^ F - handle integer-valued exponents (covers 99% of // real-world `^^` uses: value^^2, 10.0^^e, etc.) if (exp == 0) return B(1); @@ -171,13 +171,13 @@ auto pow(B, E)(B base, E exp) @trusted } else { - // F ^^ I — binary exponentiation + // F ^^ I - binary exponentiation return _powfi!B(b, long(exp)); } } else { - // I ^^ I — integer power + // I ^^ I - integer power if (exp == 0) return B(1); B result = B(1); From 4e4a5d5c01a47a9c0eab21d7ad8826ba75e3fc7d Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Thu, 23 Apr 2026 01:54:42 +1000 Subject: [PATCH 134/138] coding standard fix --- src/object.d | 2 +- src/urt/driver/windows/ble.d | 14 +++++++------- src/urt/internal/stdc/errno.d | 32 ++++++++++++++++---------------- src/urt/internal/stdc/stdlib.d | 2 +- src/urt/time.d | 2 +- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/object.d b/src/object.d index 0521480..5a0360a 100644 --- a/src/object.d +++ b/src/object.d @@ -39,7 +39,7 @@ alias noreturn = typeof(*null); // needed so druntime's core.stdc.stdio compiles on AArch64 version (AArch64) { - extern (C++, std) struct __va_list + extern(C++, std) struct __va_list { void* __stack; void* __gr_top; diff --git a/src/urt/driver/windows/ble.d b/src/urt/driver/windows/ble.d index d5e4063..1638ae6 100644 --- a/src/urt/driver/windows/ble.d +++ b/src/urt/driver/windows/ble.d @@ -1233,12 +1233,12 @@ nothrow @nogc: import urt.internal.sys.windows.windef : HMODULE; private HMODULE _lib; - extern (Windows) HRESULT function(uint initType) RoInitialize; - extern (Windows) HRESULT function(HSTRING classId, IInspectable* instance) RoActivateInstance; - extern (Windows) HRESULT function(HSTRING classId, const(GUID)* iid, void** factory) RoGetActivationFactory; - extern (Windows) HRESULT function(const(wchar)* str, uint len, HSTRING* out_) WindowsCreateString; - extern (Windows) HRESULT function(HSTRING str) WindowsDeleteString; - extern (Windows) const(wchar)* function(HSTRING str, uint* len) WindowsGetStringRawBuffer; + extern(Windows) HRESULT function(uint initType) RoInitialize; + extern(Windows) HRESULT function(HSTRING classId, IInspectable* instance) RoActivateInstance; + extern(Windows) HRESULT function(HSTRING classId, const(GUID)* iid, void** factory) RoGetActivationFactory; + extern(Windows) HRESULT function(const(wchar)* str, uint len, HSTRING* out_) WindowsCreateString; + extern(Windows) HRESULT function(HSTRING str) WindowsDeleteString; + extern(Windows) const(wchar)* function(HSTRING str, uint* len) WindowsGetStringRawBuffer; bool init() { @@ -1375,7 +1375,7 @@ static immutable IID_TypedEventHandler_Watcher_Stopped = GUID(0x9936A4 // --- COM interfaces --- -extern (Windows): +extern(Windows): interface IUnknown { diff --git a/src/urt/internal/stdc/errno.d b/src/urt/internal/stdc/errno.d index d3d7331..459f9e7 100644 --- a/src/urt/internal/stdc/errno.d +++ b/src/urt/internal/stdc/errno.d @@ -50,7 +50,7 @@ nothrow: version (CRuntime_Microsoft) { - extern (C) + extern(C) { ref int _errno(); alias errno = _errno; @@ -58,7 +58,7 @@ version (CRuntime_Microsoft) } else version (CRuntime_Glibc) { - extern (C) + extern(C) { ref int __errno_location(); alias errno = __errno_location; @@ -66,7 +66,7 @@ else version (CRuntime_Glibc) } else version (CRuntime_Musl) { - extern (C) + extern(C) { ref int __errno_location(); alias errno = __errno_location; @@ -78,18 +78,18 @@ else version (CRuntime_Picolibc) { // On FreeRTOS, picolibc defines errno as _Thread_local which conflicts // with emulated-TLS. Use a C shim to access it indirectly. - extern (C) int* ow_errno_location() nothrow @nogc; + extern(C) int* ow_errno_location() nothrow @nogc; @property ref int errno() nothrow @nogc { return *ow_errno_location(); } } else { // Bare-metal single-threaded: errno is a plain global. - extern (C) extern int errno; + extern(C) extern int errno; } } else version (CRuntime_Newlib) { - extern (C) + extern(C) { ref int __errno(); alias errno = __errno; @@ -98,7 +98,7 @@ else version (CRuntime_Newlib) else version (OpenBSD) { // https://github.com/openbsd/src/blob/master/include/errno.h - extern (C) + extern(C) { ref int __errno(); alias errno = __errno; @@ -107,7 +107,7 @@ else version (OpenBSD) else version (NetBSD) { // https://github.com/NetBSD/src/blob/trunk/include/errno.h - extern (C) + extern(C) { ref int __errno(); alias errno = __errno; @@ -115,7 +115,7 @@ else version (NetBSD) } else version (FreeBSD) { - extern (C) + extern(C) { ref int __error(); alias errno = __error; @@ -123,7 +123,7 @@ else version (FreeBSD) } else version (DragonFlyBSD) { - extern (C) + extern(C) { pragma(mangle, "errno") int __errno; ref int __error() { @@ -134,7 +134,7 @@ else version (DragonFlyBSD) } else version (CRuntime_Bionic) { - extern (C) + extern(C) { ref int __errno(); alias errno = __errno; @@ -142,7 +142,7 @@ else version (CRuntime_Bionic) } else version (CRuntime_UClibc) { - extern (C) + extern(C) { ref int __errno_location(); alias errno = __errno_location; @@ -150,7 +150,7 @@ else version (CRuntime_UClibc) } else version (Darwin) { - extern (C) + extern(C) { ref int __error(); alias errno = __error; @@ -158,7 +158,7 @@ else version (Darwin) } else version (Solaris) { - extern (C) + extern(C) { ref int ___errno(); alias errno = ___errno; @@ -167,7 +167,7 @@ else version (Solaris) else version (Haiku) { // https://github.com/haiku/haiku/blob/master/headers/posix/errno.h - extern (C) + extern(C) { ref int _errnop(); alias errno = _errnop; @@ -181,7 +181,7 @@ else extern(C) pragma(mangle, "setErrno") @property int errno(int n); } -extern (C): +extern(C): version (CRuntime_Microsoft) diff --git a/src/urt/internal/stdc/stdlib.d b/src/urt/internal/stdc/stdlib.d index 5760d26..960d32e 100644 --- a/src/urt/internal/stdc/stdlib.d +++ b/src/urt/internal/stdc/stdlib.d @@ -2,7 +2,7 @@ module urt.internal.stdc.stdlib; -extern (C) nothrow @nogc: +extern(C) nothrow @nogc: noreturn abort() @safe; noreturn exit(int status); diff --git a/src/urt/time.d b/src/urt/time.d index 2111507..e68322c 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -6,7 +6,7 @@ version (Windows) { import urt.internal.sys.windows; - extern (Windows) void GetSystemTimePreciseAsFileTime(FILETIME* lpSystemTimeAsFileTime) nothrow @nogc; + extern(Windows) void GetSystemTimePreciseAsFileTime(FILETIME* lpSystemTimeAsFileTime) nothrow @nogc; } else version (Posix) { From c5603cb23fc02c834916b459259bc344d9625b2e Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sun, 26 Apr 2026 18:11:53 +1000 Subject: [PATCH 135/138] GPIO driver. --- src/urt/driver/bk7231/gpio.d | 112 +++++++++++++++++ src/urt/driver/bk7231/uart.d | 50 +++----- src/urt/driver/bl618/gpio.d | 120 ++++++++++++++++++ src/urt/driver/bl618/package.d | 22 ++-- src/urt/driver/bl808/gpio.d | 221 +++++++++++++++++++++------------ src/urt/driver/esp32/gpio.d | 75 +++++++++++ src/urt/driver/esp32/ow_shim.c | 54 ++++++++ src/urt/driver/esp32/uart.d | 2 +- src/urt/driver/gpio.d | 89 +++++++++++++ src/urt/driver/posix/gpio.d | 133 ++++++++++++++++++++ 10 files changed, 757 insertions(+), 121 deletions(-) create mode 100644 src/urt/driver/bk7231/gpio.d create mode 100644 src/urt/driver/bl618/gpio.d create mode 100644 src/urt/driver/esp32/gpio.d create mode 100644 src/urt/driver/gpio.d create mode 100644 src/urt/driver/posix/gpio.d diff --git a/src/urt/driver/bk7231/gpio.d b/src/urt/driver/bk7231/gpio.d new file mode 100644 index 0000000..4a53a05 --- /dev/null +++ b/src/urt/driver/bk7231/gpio.d @@ -0,0 +1,112 @@ +// BK7231 GPIO. Per-pin config registers at GPIO_BASE + pin*4 (function/ +// input/output/pull). A 2-bit perial-mode field per pin in GPIO_FUNC_CFG +// selects the peripheral when GMODE_FUNC_EN is set; only pins 0..15 have +// a perial-mode field. +// +// Pin numbering: linear 0..31. +// +// Software GPIO primitives are unimplemented (need full per-pin config +// bit definitions from the BK7231 reference manual). gpio_set_function +// works and is sufficient for UART pin mux today. +module urt.driver.bk7231.gpio; + +import core.volatile : volatileLoad, volatileStore; + +import urt.driver.gpio : Pull, DriveMode; + +@nogc nothrow: + + +enum uint num_gpio = 32; +enum bool has_pull_up = true; +enum bool has_pull_down = true; +enum bool has_open_drain = false; +enum bool has_pin_function_muxing = true; + + +uint gpio_count() => num_gpio; + + +void gpio_output_init(uint pin, bool initial = false, DriveMode mode = DriveMode.push_pull) +{ + cast(void) pin; cast(void) initial; cast(void) mode; + assert(false, "bk7231 gpio: gpio_output_init not yet implemented"); +} + +void gpio_input_init(uint pin, Pull pull = Pull.none) +{ + cast(void) pin; cast(void) pull; + assert(false, "bk7231 gpio: gpio_input_init not yet implemented"); +} + +void gpio_output_set(uint pin, bool value) +{ + cast(void) pin; cast(void) value; + assert(false, "bk7231 gpio: gpio_output_set not yet implemented"); +} + +void gpio_output_toggle(uint pin) +{ + cast(void) pin; + assert(false, "bk7231 gpio: gpio_output_toggle not yet implemented"); +} + +bool gpio_input_read(uint pin) +{ + cast(void) pin; + assert(false, "bk7231 gpio: gpio_input_read not yet implemented"); + return false; +} + +void gpio_set_pull(uint pin, Pull pull) +{ + cast(void) pin; cast(void) pull; + assert(false, "bk7231 gpio: gpio_set_pull not yet implemented"); +} + +void gpio_release(uint pin) +{ + cast(void) pin; + assert(false, "bk7231 gpio: gpio_release not yet implemented"); +} + +void gpio_set_function(uint pin, uint function_id, Pull pull = Pull.none, DriveMode mode = DriveMode.push_pull) +{ + assert(pin < 16, "bk7231 gpio: perial-mode field only exists for pins 0..15"); + assert(function_id < 4, "bk7231 gpio: perial mode is 2-bit (0..3)"); + assert(mode == DriveMode.push_pull, "bk7231 gpio: open-drain not supported"); + + uint cfg = GMODE_FUNC_EN | GMODE_OUTPUT_EN; + if (pull == Pull.up) + cfg |= GMODE_PULL_EN | GMODE_PULL_UP; + else if (pull == Pull.down) + cfg |= GMODE_PULL_EN; + reg_write(GPIO_BASE + pin * 4, cfg); + + uint func_cfg = reg_read(GPIO_FUNC_CFG); + func_cfg &= ~(0x3u << (pin * 2)); + func_cfg |= (function_id & 0x3u) << (pin * 2); + reg_write(GPIO_FUNC_CFG, func_cfg); +} + + +private: + +enum uint GPIO_BASE = 0x0080_2800; +enum uint GPIO_FUNC_CFG = GPIO_BASE + 32 * 4; + +// Bit positions inferred from SDK gpio_enable_second_function value 0x78. +enum uint GMODE_FUNC_EN = 1u << 3; +enum uint GMODE_OUTPUT_EN = 1u << 4; +enum uint GMODE_PULL_EN = 1u << 5; +enum uint GMODE_PULL_UP = 1u << 6; + +uint reg_read(uint addr) +{ + return volatileLoad(cast(uint*)(cast(size_t)addr)); +} + +void reg_write(uint addr, uint val) +{ + volatileStore(cast(uint*)(cast(size_t)addr), val); +} diff --git a/src/urt/driver/bk7231/uart.d b/src/urt/driver/bk7231/uart.d index 73e39f8..edfb1f7 100644 --- a/src/urt/driver/bk7231/uart.d +++ b/src/urt/driver/bk7231/uart.d @@ -14,6 +14,7 @@ module urt.driver.bk7231.uart; import core.volatile; import urt.driver.uart : FlowControl, Parity, StopBits, UartConfig; +import urt.driver.gpio : Pull, gpio_set_function; nothrow @nogc: @@ -139,18 +140,10 @@ private enum uint PWD_UART1_CLK = 1 << 0; private enum uint PWD_UART2_CLK = 1 << 1; -// ─── GPIO registers for pin mux ────────────────────────────────────── -// From SDK gpio.h / gpio.c: gpio_enable_second_function() - -private enum uint GPIO_BASE = 0x0080_2800; -private enum uint GPIO_FUNC_CFG = GPIO_BASE + 32 * 4; // 0x0080_2880 - -// Per-pin config register values (from SDK gpio.c gpio_config()) -private enum uint GMODE_SECOND_FUNC_PULL_UP = 0x78; // FUNC_EN | OUTPUT_EN | PULL_EN | PULL_UP - -// Default pin assignments -private enum ubyte[2] default_tx_pins = [11, 0]; // UART1=GPIO11, UART2=GPIO0 -private enum ubyte[2] default_rx_pins = [10, 1]; // UART1=GPIO10, UART2=GPIO1 +// UART pins are fixed by hardware; perial mode 0 is the only valid value. +private enum ubyte[2] default_tx_pins = [11, 0]; // UART1=GPIO11, UART2=GPIO0 +private enum ubyte[2] default_rx_pins = [10, 1]; // UART1=GPIO10, UART2=GPIO1 +private enum uint UART_PERIAL_MODE = 0; // ─── Register access helpers ───────────────────────────────────────── @@ -166,26 +159,17 @@ private void reg_write(uint addr, uint val) } -// ─── GPIO pin mux ──────────────────────────────────────────────────── -// Mirrors SDK gpio_enable_second_function() for UART modes. -// Both GFUNC_MODE_UART1 and GFUNC_MODE_UART2 use PERIAL_MODE_1 (value 0) -// with config_mode = GMODE_SECOND_FUNC_PULL_UP. - -private void gpio_setup_uart_pins(uint id) +private bool gpio_setup_uart_pins(uint id, ref const UartConfig cfg) { - ubyte tx_pin = default_tx_pins[id]; - ubyte rx_pin = default_rx_pins[id]; - - // Set per-pin config to second-function with pull-up - reg_write(GPIO_BASE + tx_pin * 4, GMODE_SECOND_FUNC_PULL_UP); - reg_write(GPIO_BASE + rx_pin * 4, GMODE_SECOND_FUNC_PULL_UP); - - // Set function mux to PERIAL_MODE_1 (value 0) for each pin. - // GPIO_FUNC_CFG has 2 bits per pin for GPIO 0-15. - uint func_cfg = reg_read(GPIO_FUNC_CFG); - func_cfg &= ~(0x3u << (tx_pin * 2)); - func_cfg &= ~(0x3u << (rx_pin * 2)); - reg_write(GPIO_FUNC_CFG, func_cfg); + ubyte tx_pin = cfg.tx_gpio == ubyte.max ? default_tx_pins[id] : cfg.tx_gpio; + ubyte rx_pin = cfg.rx_gpio == ubyte.max ? default_rx_pins[id] : cfg.rx_gpio; + + if (tx_pin != default_tx_pins[id] || rx_pin != default_rx_pins[id]) + return false; + + gpio_set_function(tx_pin, UART_PERIAL_MODE, Pull.up); + gpio_set_function(rx_pin, UART_PERIAL_MODE, Pull.up); + return true; } @@ -220,7 +204,9 @@ bool uart_hw_init(uint id, UartConfig cfg) icu_uart_clock_enable(id); // Step 2: Configure GPIO pins for UART function (SDK: CMD_GPIO_ENABLE_SECOND) - gpio_setup_uart_pins(id); + const pins_ok = gpio_setup_uart_pins(id, cfg); + if (!pins_ok) + return false; // Step 3: Disable TX/RX during configuration reg_write(base + REG_CONFIG, 0); diff --git a/src/urt/driver/bl618/gpio.d b/src/urt/driver/bl618/gpio.d new file mode 100644 index 0000000..c7edbd7 --- /dev/null +++ b/src/urt/driver/bl618/gpio.d @@ -0,0 +1,120 @@ +// BL616/BL618 GPIO (also used by BL808 M0 core, which shares the +// bl618 peripheral set). +// +// GPIO_CFG register at GLB_BASE + 0x8C4 + pin*4: +// bits[4:0] = function (11 = SWGPIO) +// bit[6] = input enable +// bit[11] = output enable +// bit[17] = output value +// bit[18] = input value (read-only) +// bit[24] = pull-up enable +// bit[25] = pull-down enable +// +// Pin numbering: linear 0..34. +module urt.driver.bl618.gpio; + +import core.volatile : volatileLoad, volatileStore; + +import urt.driver.gpio : Pull, DriveMode; + +@nogc nothrow: + + +enum uint num_gpio = 35; +enum bool has_pull_up = true; +enum bool has_pull_down = true; +enum bool has_open_drain = false; +enum bool has_pin_function_muxing = true; + + +uint gpio_count() => num_gpio; + + +void gpio_output_init(uint pin, bool initial = false, DriveMode mode = DriveMode.push_pull) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + assert(mode == DriveMode.push_pull, "bl618 gpio: open-drain not supported"); + uint cfg = GPIO_FUN_SWGPIO | GPIO_OUTPUT_EN; + if (initial) + cfg |= GPIO_OUTPUT_HIGH; + gpio_cfg_write(pin, cfg); +} + +void gpio_input_init(uint pin, Pull pull = Pull.none) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + gpio_cfg_write(pin, GPIO_FUN_SWGPIO | GPIO_INPUT_EN | (uint(pull) << 24)); +} + +void gpio_output_set(uint pin, bool value) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + uint cfg = gpio_cfg_read(pin) & ~GPIO_OUTPUT_HIGH; + if (value) + cfg |= GPIO_OUTPUT_HIGH; + gpio_cfg_write(pin, cfg); +} + +void gpio_output_toggle(uint pin) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + gpio_cfg_write(pin, gpio_cfg_read(pin) ^ GPIO_OUTPUT_HIGH); +} + +bool gpio_input_read(uint pin) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + return (gpio_cfg_read(pin) & GPIO_INPUT_VALUE) != 0; +} + +void gpio_set_pull(uint pin, Pull pull) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + uint cfg = gpio_cfg_read(pin) & ~(GPIO_PULL_UP | GPIO_PULL_DOWN); + gpio_cfg_write(pin, cfg | (uint(pull) << 24)); +} + +void gpio_release(uint pin) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + gpio_cfg_write(pin, GPIO_FUN_SWGPIO); +} + +void gpio_set_function(uint pin, uint function_id, Pull pull = Pull.none, DriveMode mode = DriveMode.push_pull) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + assert(function_id <= GPIO_FUN_MASK, "gpio: function_id out of range (5-bit field)"); + assert(mode == DriveMode.push_pull, "bl618 gpio: open-drain not supported"); + uint cfg = (function_id & GPIO_FUN_MASK) | GPIO_INPUT_EN | (uint(pull) << 24); + gpio_cfg_write(pin, cfg); +} + + +private: + +enum uint GLB_BASE = 0x2000_0000; +enum uint GPIO_CFG_BASE = GLB_BASE + 0x8C4; + +enum uint GPIO_FUN_SWGPIO = 11; +enum uint GPIO_FUN_MASK = 0x1Fu; +enum uint GPIO_INPUT_EN = 1u << 6; +enum uint GPIO_OUTPUT_EN = 1u << 11; +enum uint GPIO_OUTPUT_HIGH = 1u << 17; +enum uint GPIO_INPUT_VALUE = 1u << 18; +enum uint GPIO_PULL_UP = 1u << 24; +enum uint GPIO_PULL_DOWN = 1u << 25; + +// Pull values map directly to GPIO_CFG bits[25:24]: none=0, up=bit24, down=bit25. +static assert(Pull.none == 0 && Pull.up == 1 && Pull.down == 2); + +pragma(inline, true) +uint gpio_cfg_read(uint pin) +{ + return volatileLoad(cast(uint*)(GPIO_CFG_BASE + pin * 4)); +} + +pragma(inline, true) +void gpio_cfg_write(uint pin, uint value) +{ + volatileStore(cast(uint*)(GPIO_CFG_BASE + pin * 4), value); +} diff --git a/src/urt/driver/bl618/package.d b/src/urt/driver/bl618/package.d index d6a7007..5770a0a 100644 --- a/src/urt/driver/bl618/package.d +++ b/src/urt/driver/bl618/package.d @@ -1,7 +1,7 @@ -/// BL618 platform package (T-Head E907 RV32IMAFC) -/// -/// Provides sys_init() as the single entry point for all -/// hardware initialization. Call from main() before anything else. +// BL618 platform package (T-Head E907 RV32IMAFC) +// +// Provides sys_init() as the single entry point for all +// hardware initialization. Call from main() before anything else. module urt.driver.bl618; public import urt.driver.bl618.uart; @@ -14,13 +14,13 @@ private extern(C) void __register_frame_info(const void*, void*); private extern(C) extern const ubyte __eh_frame_start; private ubyte[48] __eh_frame_object; // pre-allocated storage for libgcc -/// Initialize BL618 hardware. -/// Call once at the top of main() before any other OpenWatt code. -/// -/// Order matters: -/// 1. UART - so we have debug output for everything after -/// 2. IRQ table - already done by start.S (_init_interrupts) -/// 3. Timer - periodic tick for main loop +// Initialize BL618 hardware. +// Call once at the top of main() before any other OpenWatt code. +// +// Order matters: +// 1. UART - so we have debug output for everything after +// 2. IRQ table - already done by start.S (_init_interrupts) +// 3. Timer - periodic tick for main loop extern(C) void sys_init() { // Register .eh_frame with libgcc's unwinder so that DWARF exception diff --git a/src/urt/driver/bl808/gpio.d b/src/urt/driver/bl808/gpio.d index 48c1f3c..35bfde9 100644 --- a/src/urt/driver/bl808/gpio.d +++ b/src/urt/driver/bl808/gpio.d @@ -1,51 +1,160 @@ -/// BL808 GPIO and WS2812 LED driver -/// -/// GPIO config registers: GLB_BASE + 0x8C4 + pin*4 -/// bits[4:0] = function (11 = SWGPIO, software-controlled) -/// bit[6] = input enable -/// bit[11] = output enable -/// bit[17] = output value -/// bit[24] = pull-up enable -/// bit[25] = pull-down enable -/// -/// WS2812B on the M1s Dock board: GPIO8 +// GPIO_CFG register at GLB_BASE + 0x8C4 + pin*4: +// bits[4:0] = function (11 = SWGPIO) +// bit[6] = input enable +// bit[11] = output enable +// bit[17] = output value +// bit[18] = input value (read-only) +// bit[24] = pull-up enable +// bit[25] = pull-down enable +// +// Pin numbering: linear 0..46. +// +// Also contains M1s Dock WS2812 LED helpers; will move to a portable +// baremetal/ws2812 driver once the GPIO API is on other SoCs. module urt.driver.bl808.gpio; +import core.volatile : volatileLoad, volatileStore; + +import urt.driver.gpio : Pull, DriveMode; + @nogc nothrow: + +enum uint num_gpio = 47; +enum bool has_pull_up = true; +enum bool has_pull_down = true; +enum bool has_open_drain = false; +enum bool has_pin_function_muxing = true; + + +uint gpio_count() => num_gpio; + + +void gpio_output_init(uint pin, bool initial = false, DriveMode mode = DriveMode.push_pull) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + assert(mode == DriveMode.push_pull, "bl808 gpio: open-drain not supported"); + uint cfg = GPIO_FUN_SWGPIO | GPIO_OUTPUT_EN; + if (initial) + cfg |= GPIO_OUTPUT_HIGH; + gpio_cfg_write(pin, cfg); +} + +void gpio_input_init(uint pin, Pull pull = Pull.none) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + gpio_cfg_write(pin, GPIO_FUN_SWGPIO | GPIO_INPUT_EN | (uint(pull) << 24)); +} + +void gpio_output_set(uint pin, bool value) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + uint cfg = gpio_cfg_read(pin) & ~GPIO_OUTPUT_HIGH; + if (value) + cfg |= GPIO_OUTPUT_HIGH; + gpio_cfg_write(pin, cfg); +} + +void gpio_output_toggle(uint pin) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + gpio_cfg_write(pin, gpio_cfg_read(pin) ^ GPIO_OUTPUT_HIGH); +} + +bool gpio_input_read(uint pin) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + return (gpio_cfg_read(pin) & GPIO_INPUT_VALUE) != 0; +} + +void gpio_set_pull(uint pin, Pull pull) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + uint cfg = gpio_cfg_read(pin) & ~(GPIO_PULL_UP | GPIO_PULL_DOWN); + gpio_cfg_write(pin, cfg | (uint(pull) << 24)); +} + +void gpio_release(uint pin) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + gpio_cfg_write(pin, GPIO_FUN_SWGPIO); +} + +void gpio_set_function(uint pin, uint function_id, Pull pull = Pull.none, DriveMode mode = DriveMode.push_pull) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + assert(function_id <= GPIO_FUN_MASK, "gpio: function_id out of range (5-bit field)"); + assert(mode == DriveMode.push_pull, "bl808 gpio: open-drain not supported"); + uint cfg = (function_id & GPIO_FUN_MASK) | GPIO_INPUT_EN | (uint(pull) << 24); + gpio_cfg_write(pin, cfg); +} + + +void ws2812_send(uint pin, ubyte r, ubyte g, ubyte b) +{ + gpio_output_init(pin); + ws2812_byte(pin, g); // GRB order + ws2812_byte(pin, r); + ws2812_byte(pin, b); + ws2812_raw_set(pin, false); + delay_loops(WS_RESET_US * 200); +} + +void led_set(ubyte r, ubyte g, ubyte b) +{ + ws2812_send(WS2812_PIN, r, g, b); +} + +void led_red() { led_set(32, 0, 0); } +void led_green() { led_set(0, 32, 0); } +void led_blue() { led_set(0, 0, 32); } +void led_white() { led_set(16, 16, 16); } +void led_off() { led_set(0, 0, 0); } + + private: enum uint GLB_BASE = 0x2000_0000; enum uint GPIO_CFG_BASE = GLB_BASE + 0x8C4; -// GPIO_CFG field values -enum uint GPIO_FUN_SWGPIO = 11; -enum uint GPIO_OUTPUT_EN = 1u << 11; -enum uint GPIO_OUTPUT_HIGH = 1u << 17; +enum uint GPIO_FUN_SWGPIO = 11; +enum uint GPIO_FUN_MASK = 0x1Fu; +enum uint GPIO_INPUT_EN = 1u << 6; +enum uint GPIO_OUTPUT_EN = 1u << 11; +enum uint GPIO_OUTPUT_HIGH = 1u << 17; +enum uint GPIO_INPUT_VALUE = 1u << 18; +enum uint GPIO_PULL_UP = 1u << 24; +enum uint GPIO_PULL_DOWN = 1u << 25; -// WS2812 LED pin on M1s Dock -enum uint WS2812_PIN = 8; +// Pull values map directly to GPIO_CFG bits[25:24]: none=0, up=bit24, down=bit25. +static assert(Pull.none == 0 && Pull.up == 1 && Pull.down == 2); -// Timing loop counts - calibrated for ~480MHz D0 core clock. -// WS2812B spec: T0H=400ns, T0L=850ns, T1H=800ns, T1L=450ns (±150ns). -// At 480MHz: 1 cycle ≈ 2.08ns, so T0H ≈ 192 cycles, T1H ≈ 384 cycles. -// Loop body (addi + bnez compressed) ≈ 2 cycles → divide by 2. -enum uint WS_T0H_LOOPS = 80; // ~400ns at 400MHz -enum uint WS_T0L_LOOPS = 170; // ~850ns at 400MHz -enum uint WS_T1H_LOOPS = 160; // ~800ns at 400MHz -enum uint WS_T1L_LOOPS = 90; // ~450ns at 400MHz -enum uint WS_RESET_US = 60; // µs for reset pulse (>50µs required) +pragma(inline, true) +uint gpio_cfg_read(uint pin) +{ + return volatileLoad(cast(uint*)(GPIO_CFG_BASE + pin * 4)); +} pragma(inline, true) void gpio_cfg_write(uint pin, uint value) { - *cast(uint*)(GPIO_CFG_BASE + pin * 4) = value; + volatileStore(cast(uint*)(GPIO_CFG_BASE + pin * 4), value); } + +// WS2812B spec: T0H=400ns, T0L=850ns, T1H=800ns, T1L=450ns (+/- 150ns). +// At 480MHz: 1 cycle ~= 2.08ns. Loop body (addi + bnez compressed) ~= 2 +// cycles, so divide by 2. +enum uint WS2812_PIN = 8; // M1s Dock board +enum uint WS_T0H_LOOPS = 80; +enum uint WS_T0L_LOOPS = 170; +enum uint WS_T1H_LOOPS = 160; +enum uint WS_T1L_LOOPS = 90; +enum uint WS_RESET_US = 60; // >50us required by spec + pragma(inline, true) void delay_loops(ulong n) { - // =r → output (ulong/i64); 0 → input tied to output 0 (same register, same type). import ldc.llvmasm; cast(void) __asm!ulong(` 1: addi $0, $0, -1 @@ -53,7 +162,7 @@ void delay_loops(ulong n) `, "=r,0", n); } -void gpio_set(uint pin, bool high) +void ws2812_raw_set(uint pin, bool high) { uint cfg = GPIO_FUN_SWGPIO | GPIO_OUTPUT_EN; if (high) @@ -61,68 +170,26 @@ void gpio_set(uint pin, bool high) gpio_cfg_write(pin, cfg); } -public: - -/// Configure a pin as software-controlled output, initially low. -void gpio_output_init(uint pin) -{ - gpio_cfg_write(pin, GPIO_FUN_SWGPIO | GPIO_OUTPUT_EN); -} - -/// Set pin output level. -void gpio_output_set(uint pin, bool high) -{ - gpio_set(pin, high); -} - -/// Send one WS2812 bit (0 = short high, 1 = long high) on the given pin. -private void ws2812_bit(uint pin, bool one) +void ws2812_bit(uint pin, bool one) { if (one) { - gpio_set(pin, true); + ws2812_raw_set(pin, true); delay_loops(WS_T1H_LOOPS); - gpio_set(pin, false); + ws2812_raw_set(pin, false); delay_loops(WS_T1L_LOOPS); } else { - gpio_set(pin, true); + ws2812_raw_set(pin, true); delay_loops(WS_T0H_LOOPS); - gpio_set(pin, false); + ws2812_raw_set(pin, false); delay_loops(WS_T0L_LOOPS); } } -/// Send one byte (MSB first) to a WS2812 LED. -private void ws2812_byte(uint pin, ubyte b) +void ws2812_byte(uint pin, ubyte b) { foreach (i; 0 .. 8) ws2812_bit(pin, (b & (0x80 >> i)) != 0); } - -/// Send one GRB pixel to the WS2812 at the given pin. -/// Colours: r/g/b = 0..255. -void ws2812_send(uint pin, ubyte r, ubyte g, ubyte b) -{ - gpio_output_init(pin); - ws2812_byte(pin, g); // WS2812 order: G, R, B - ws2812_byte(pin, r); - ws2812_byte(pin, b); - // Reset: hold low for >50µs - gpio_set(pin, false); - delay_loops(WS_RESET_US * 200); // ~60µs at 400MHz -} - -/// Set the on-board WS2812 LED colour. -void led_set(ubyte r, ubyte g, ubyte b) -{ - ws2812_send(WS2812_PIN, r, g, b); -} - -/// Boot-stage colour helpers -void led_red() { led_set(32, 0, 0); } -void led_green() { led_set(0, 32, 0); } -void led_blue() { led_set(0, 0, 32); } -void led_white() { led_set(16, 16, 16); } -void led_off() { led_set(0, 0, 0); } diff --git a/src/urt/driver/esp32/gpio.d b/src/urt/driver/esp32/gpio.d new file mode 100644 index 0000000..2869195 --- /dev/null +++ b/src/urt/driver/esp32/gpio.d @@ -0,0 +1,75 @@ +// ESP32 software GPIO via ESP-IDF (driver/gpio.h) through ow_shim.c. +// +// Peripheral function routing on ESP32 uses the GPIO matrix per-signal, +// not per-pin function selection, so has_pin_function_muxing = false +// and gpio_set_function is not provided. Peripheral drivers (UART, SPI, +// I2C, etc.) route their own signals via the IDF. +// +// Pin numbering: linear 0..SOC_GPIO_PIN_COUNT-1. The compile-time +// num_gpio is a conservative upper bound; gpio_count() returns the +// actual SOC_GPIO_PIN_COUNT for the active chip variant. +module urt.driver.esp32.gpio; + +import urt.driver.gpio : Pull, DriveMode; + +nothrow @nogc: + + +enum uint num_gpio = 64; +enum bool has_pull_up = true; +enum bool has_pull_down = true; +enum bool has_open_drain = false; +enum bool has_pin_function_muxing = false; + + +uint gpio_count() => ow_gpio_count(); + +void gpio_output_init(uint pin, bool initial = false, DriveMode mode = DriveMode.push_pull) +{ + assert(mode == DriveMode.push_pull, "esp32 gpio: open-drain not exposed via this API"); + ow_gpio_output_init(int(pin), initial ? 1 : 0); +} + +void gpio_input_init(uint pin, Pull pull = Pull.none) +{ + ow_gpio_input_init(int(pin), int(pull)); +} + +void gpio_output_set(uint pin, bool value) +{ + ow_gpio_output_set(int(pin), value ? 1 : 0); +} + +void gpio_output_toggle(uint pin) +{ + ow_gpio_output_set(int(pin), ow_gpio_input_read(int(pin)) ? 0 : 1); +} + +bool gpio_input_read(uint pin) +{ + return ow_gpio_input_read(int(pin)) != 0; +} + +void gpio_set_pull(uint pin, Pull pull) +{ + ow_gpio_set_pull(int(pin), int(pull)); +} + +void gpio_release(uint pin) +{ + ow_gpio_release(int(pin)); +} + + +private: + +extern(C) nothrow @nogc +{ + void ow_gpio_output_init(int pin, int initial); + void ow_gpio_input_init(int pin, int pull); + void ow_gpio_output_set(int pin, int value); + int ow_gpio_input_read(int pin); + void ow_gpio_set_pull(int pin, int pull); + void ow_gpio_release(int pin); + uint ow_gpio_count(); +} diff --git a/src/urt/driver/esp32/ow_shim.c b/src/urt/driver/esp32/ow_shim.c index 6994270..fe901fb 100644 --- a/src/urt/driver/esp32/ow_shim.c +++ b/src/urt/driver/esp32/ow_shim.c @@ -87,6 +87,60 @@ uint32_t ow_task_priority_get(void *handle) return uxTaskPriorityGet((TaskHandle_t)handle); } +// -- GPIO wrappers (software GPIO; peripheral function routing goes +// through ESP-IDF's signal matrix on a per-peripheral basis, not +// through this module). + +#include "driver/gpio.h" +#include "soc/gpio_num.h" + +// pull: 0=none, 1=up, 2=down (matches D Pull enum encoding) +static gpio_pull_mode_t ow_pull_to_idf(int pull) +{ + return (pull == 1) ? GPIO_PULLUP_ONLY : + (pull == 2) ? GPIO_PULLDOWN_ONLY : GPIO_FLOATING; +} + +void ow_gpio_output_init(int pin, int initial) +{ + gpio_reset_pin((gpio_num_t)pin); + gpio_set_direction((gpio_num_t)pin, GPIO_MODE_OUTPUT); + gpio_set_level((gpio_num_t)pin, initial); +} + +void ow_gpio_input_init(int pin, int pull) +{ + gpio_reset_pin((gpio_num_t)pin); + gpio_set_direction((gpio_num_t)pin, GPIO_MODE_INPUT); + gpio_set_pull_mode((gpio_num_t)pin, ow_pull_to_idf(pull)); +} + +void ow_gpio_output_set(int pin, int value) +{ + gpio_set_level((gpio_num_t)pin, value); +} + +int ow_gpio_input_read(int pin) +{ + return gpio_get_level((gpio_num_t)pin); +} + +void ow_gpio_set_pull(int pin, int pull) +{ + gpio_set_pull_mode((gpio_num_t)pin, ow_pull_to_idf(pull)); +} + +void ow_gpio_release(int pin) +{ + gpio_reset_pin((gpio_num_t)pin); +} + +uint32_t ow_gpio_count(void) +{ + return SOC_GPIO_PIN_COUNT; +} + + // -- UART HAL wrappers -- #include "soc/soc_caps.h" diff --git a/src/urt/driver/esp32/uart.d b/src/urt/driver/esp32/uart.d index bf76bab..a752be7 100644 --- a/src/urt/driver/esp32/uart.d +++ b/src/urt/driver/esp32/uart.d @@ -33,7 +33,7 @@ bool uart_hw_open(uint id, ref const UartConfig cfg) if (id >= num_uarts) return false; byte tx = cfg.tx_gpio == ubyte.max ? -1 : cast(byte)cfg.tx_gpio; - byte rx = cfg.tx_gpio == ubyte.max ? -1 : cast(byte)cfg.tx_gpio; + byte rx = cfg.rx_gpio == ubyte.max ? -1 : cast(byte)cfg.rx_gpio; return ow_uart_open(id, cfg.baud_rate, cfg.data_bits, cast(ubyte)cfg.stop_bits, cast(ubyte)cfg.parity, tx, rx) != 0; } diff --git a/src/urt/driver/gpio.d b/src/urt/driver/gpio.d new file mode 100644 index 0000000..4aee2a0 --- /dev/null +++ b/src/urt/driver/gpio.d @@ -0,0 +1,89 @@ +// GPIO driver. Software-GPIO primitives plus per-pin function muxing +// for SoCs that support it (BL6xx, BL8xx, BK7231, RP2350, STM32). On +// targets that route via signal matrix (ESP32) or have no pin concept +// (Windows/POSIX without sysfs), has_pin_function_muxing is false and +// gpio_set_function is not declared. +// +// Pin numbering is a flat uint per SoC. STM32 packs port and pin as +// (port * 16 + pin_in_port) so PA9 = 9, PB3 = 19, PE15 = 79. Other +// SoCs use linear pin numbers from 0. +// +// num_gpio is a compile-time upper bound (exact on SoCs with fixed +// pin counts; on hosted targets like Linux SBC it is a static cap and +// gpio_count() returns the actual runtime number). +// +// Function bodies live in /gpio.d, pulled in by the version +// dispatch below. Each backend exports: +// uint gpio_count(); +// void gpio_output_init(uint pin, bool initial = false, DriveMode = push_pull); +// void gpio_input_init(uint pin, Pull = none); +// void gpio_output_set(uint pin, bool value); +// void gpio_output_toggle(uint pin); +// bool gpio_input_read(uint pin); +// void gpio_set_pull(uint pin, Pull); +// void gpio_release(uint pin); +// void gpio_set_function(uint pin, uint function_id, Pull = none, DriveMode = push_pull); +// +// function_id is opaque per chip; peripheral drivers know the right +// value. The peripheral owns I/O direction once muxed. +module urt.driver.gpio; + +version (BL808_M0) + public import urt.driver.bl618.gpio; +else version (BL808) + public import urt.driver.bl808.gpio; +else version (BL618) + public import urt.driver.bl618.gpio; +else version (Beken) + public import urt.driver.bk7231.gpio; +else version (Espressif) + public import urt.driver.esp32.gpio; +else version (linux) + public import urt.driver.posix.gpio; +else +{ + enum uint num_gpio = 0; + enum bool has_pull_up = false; + enum bool has_pull_down = false; + enum bool has_open_drain = false; + enum bool has_pin_function_muxing = false; + + uint gpio_count() nothrow @nogc => 0; +} + +nothrow @nogc: + +enum Pull : ubyte +{ + none, + up, + down, +} + +enum DriveMode : ubyte +{ + push_pull, + open_drain, +} + + +unittest +{ + static assert(is(typeof(num_gpio) == uint)); + static assert(is(typeof(has_pull_up) == bool)); + static assert(is(typeof(has_pull_down) == bool)); + static assert(is(typeof(has_open_drain) == bool)); + static assert(is(typeof(has_pin_function_muxing) == bool)); + + // Pull encoding is load-bearing: bl808/bl618 backends shift the + // ordinal directly into GPIO_CFG bits[25:24]. + static assert(Pull.none == 0); + static assert(Pull.up == 1); + static assert(Pull.down == 2); + + static assert(DriveMode.push_pull == 0); + static assert(DriveMode.open_drain == 1); + + // gpio_count() is callable on every backend (returns 0 on fallback). + gpio_count(); +} diff --git a/src/urt/driver/posix/gpio.d b/src/urt/driver/posix/gpio.d new file mode 100644 index 0000000..03fca09 --- /dev/null +++ b/src/urt/driver/posix/gpio.d @@ -0,0 +1,133 @@ +// Linux GPIO via legacy /sys/class/gpio sysfs. Targets SBCs (Pi, Orange +// Pi, BeagleBone) where the kernel exposes pins as files. Pin numbering +// is the kernel's flat global GPIO number; each board documents its +// mapping. +// +// Sysfs is deprecated since Linux 4.8 in favour of gpio-cdev +// (/dev/gpiochipN ioctl) but ships in every shipping kernel. cdev +// backend is a future TODO. +// +// Limitations: no pull config (sysfs doesn't expose it; pulls come from +// device tree or external resistors), no peripheral muxing (kernel +// owns it), and one open/close per op (~10us each, fine for management +// rates). +module urt.driver.posix.gpio; + +import urt.driver.gpio : Pull, DriveMode; +import urt.file : File, FileOpenMode, save_file, open, close, read; +import urt.mem.temp : tconcat; + +nothrow @nogc: + + +enum uint num_gpio = 256; +enum bool has_pull_up = false; +enum bool has_pull_down = false; +enum bool has_open_drain = false; +enum bool has_pin_function_muxing = false; + + +// Walks /sys/class/gpio/gpiochip/{base,ngpio} for N in 0..64 (covers +// real SBCs incl. Pi 5 where chips are at 0 and 4) and returns +// max(base+ngpio). 0 means no GPIO chips registered in sysfs. +uint gpio_count() +{ + uint max_pin = 0; + foreach (i; 0 .. 64) + { + uint base, ngpio; + if (!read_uint_file(tconcat("/sys/class/gpio/gpiochip", i, "/base"), base)) + continue; + if (!read_uint_file(tconcat("/sys/class/gpio/gpiochip", i, "/ngpio"), ngpio)) + continue; + uint top = base + ngpio; + if (top > max_pin) + max_pin = top; + } + return max_pin; +} + +void gpio_output_init(uint pin, bool initial = false, DriveMode mode = DriveMode.push_pull) +{ + assert(mode == DriveMode.push_pull, "posix gpio: open-drain not supported via sysfs"); + cast(void) save_file("/sys/class/gpio/export", tconcat(pin)); // EBUSY = already exported + // "low" / "high" atomically set direction=out plus initial value. + cast(void) save_file(tconcat("/sys/class/gpio/gpio", pin, "/direction"), initial ? "high" : "low"); +} + +void gpio_input_init(uint pin, Pull pull = Pull.none) +{ + cast(void) pull; + cast(void) save_file("/sys/class/gpio/export", tconcat(pin)); + cast(void) save_file(tconcat("/sys/class/gpio/gpio", pin, "/direction"), "in"); +} + +void gpio_output_set(uint pin, bool value) +{ + cast(void) save_file(tconcat("/sys/class/gpio/gpio", pin, "/value"), value ? "1" : "0"); +} + +void gpio_output_toggle(uint pin) +{ + auto path = tconcat("/sys/class/gpio/gpio", pin, "/value"); + bool current; + if (!read_bool_file(path, current)) + return; + cast(void) save_file(path, current ? "0" : "1"); +} + +bool gpio_input_read(uint pin) +{ + bool v; + return read_bool_file(tconcat("/sys/class/gpio/gpio", pin, "/value"), v) && v; +} + +void gpio_set_pull(uint pin, Pull pull) +{ + cast(void) pin; cast(void) pull; +} + +void gpio_release(uint pin) +{ + cast(void) save_file("/sys/class/gpio/unexport", tconcat(pin)); +} + + +private: + +bool read_bool_file(const(char)[] path, out bool value) +{ + File f; + if (!f.open(path, FileOpenMode.ReadExisting)) + return false; + ubyte[2] buf; + size_t n; + auto r = f.read(buf, n); + f.close(); + if (!r || n == 0) + return false; + value = buf[0] == '1'; + return true; +} + +bool read_uint_file(const(char)[] path, out uint value) +{ + File f; + if (!f.open(path, FileOpenMode.ReadExisting)) + return false; + ubyte[16] buf; + size_t n; + auto r = f.read(buf, n); + f.close(); + if (!r) + return false; + uint v = 0; + foreach (c; buf[0 .. n]) + { + if (c < '0' || c > '9') + break; + v = v * 10 + (c - '0'); + } + value = v; + return true; +} From 883af2586ea80a113aa70274ae9b1bfeaf9e6175 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Sat, 11 Apr 2026 01:00:06 +1000 Subject: [PATCH 136/138] Add attribute module to standardise attributes for things like code/data placement, sections, etc. --- src/urt/attribute.d | 63 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/urt/attribute.d b/src/urt/attribute.d index 081f11f..5bc2807 100644 --- a/src/urt/attribute.d +++ b/src/urt/attribute.d @@ -4,9 +4,70 @@ version (GNU) public import gcc.attributes; version (LDC) public import ldc.attributes; - version (DigitalMars) { enum restrict; enum weak; } + +// The public imports above already provide the core compiler attributes: +// @section("name") explicit linker section placement +// @weak weak linkage +// @restrict pointer aliasing hint +// @optStrategy(...) optimization strategy override +// @llvmAttr(...) arbitrary LLVM function attribute (LDC) +// @assumeUsed prevent dead-stripping +// +// Aliases for commonly attributes: + +version (LDC) +{ + enum noinline = llvmAttr("noinline"); + enum always_inline = llvmAttr("alwaysinline"); + enum naked = llvmAttr("naked"); // no prologue/epilogue + enum cold = llvmAttr("cold"); // optimizer: rarely executed (layout hint) + enum hot = llvmAttr("hot"); // optimizer: frequently executed (layout hint) + enum used = assumeUsed; // prevent linker dead-stripping +} +else +{ + enum noinline; + enum always_inline; + enum naked; + enum cold; + enum hot; + enum used; +} + + +// ── Memory placement ──────────────────────────────────── + +// @critical — code that must execute from internal SRAM. +// Use for ISRs, code that runs during flash erase/write, and paths +// that need deterministic latency (no cache-miss jitter). +// Default (no attribute) = XIP from flash via instruction cache. +version (Espressif) enum critical = section(".iram1"); +else version (Bouffalo) enum critical = section(".ramfunc"); +else version (STM32) enum critical = section(".ramfunc"); +else version (BK7231) enum critical = section(".ramfunc"); +else version (RP2350) enum critical = section(".ramfunc"); +else enum critical; + +// @persist — data that survives deep sleep / hibernate. +// Not initialized at startup — the whole point is retaining prior values. +// Only available on platforms with RTC or hibernate-capable memory. +version (Espressif) enum persist = section(".rtc_noinit"); +else version (BL808) enum persist = section(".hbn_ram"); +else enum persist; + +// @fast_data — data in the fastest available RAM (TCM/DTCM/SRAM). +// Use sparingly: these regions are small and shared with stack/GOT. +// Only meaningful on platforms with distinct fast/slow data regions. +version (STM32F7) enum fast_data = section(".dtcm_data"); +else version (Bouffalo) enum fast_data = section(".sram_data"); +else enum fast_data; + +// @bulk_data — large data in slow, abundant memory (PSRAM / ext RAM). +// Use for big buffers, caches, lookup tables where latency doesn't matter. +version (Espressif) enum bulk_data = section(".ext_ram.bss"); +else enum bulk_data; From 1656fd7a6d0507a80c3738dea6b4bbd4f802a9c9 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 1 May 2026 01:12:22 +1000 Subject: [PATCH 137/138] Fix internet_checksum, add tests. --- src/urt/hash.d | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/urt/hash.d b/src/urt/hash.d index d519d21..81b3508 100644 --- a/src/urt/hash.d +++ b/src/urt/hash.d @@ -198,7 +198,7 @@ ushort internet_checksum(const void[] data, ushort initial = 0xFFFF) { auto bytes = cast(const(const ubyte)[])data; - uint sum = ~initial; + uint sum = initial ^ 0xFFFF; while (bytes.length > 1) { sum += (bytes.ptr[0] << 8) | bytes.ptr[1]; @@ -212,3 +212,49 @@ ushort internet_checksum(const void[] data, ushort initial = 0xFFFF) return cast(ushort)~sum; } + +unittest +{ + // Empty input: checksum of nothing is 0xFFFF (~0). + assert(internet_checksum(null) == 0xFFFF); + + // RFC 1071 §B vector. + static immutable ubyte[8] rfc1071 = [0x00, 0x01, 0xF2, 0x03, 0xF4, 0xF5, 0xF6, 0xF7]; + assert(internet_checksum(rfc1071[]) == 0x220D); + + // Real IPv4 header (checksum field zeroed). + static immutable ubyte[20] ip_hdr = [ + 0x45, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x06, 0x00, 0x00, + 0xC0, 0xA8, 0x00, 0xC8, 0xC0, 0xA8, 0x03, 0x0C, + ]; + ushort cs = internet_checksum(ip_hdr[]); + assert(cs == 0xF5A7); + + // Patching the computed checksum back in must verify to zero. + ubyte[20] verified = ip_hdr; + verified[10] = cast(ubyte)(cs >> 8); + verified[11] = cast(ubyte)cs; + assert(internet_checksum(verified[]) == 0); + + // Progressive accumulation across even-length chunks must match all-at-once. + ushort first = internet_checksum(ip_hdr[0 .. 10]); + ushort whole = internet_checksum(ip_hdr[10 .. $], first); + assert(whole == cs); + + // Odd-length input: trailing byte is treated as the high byte of a 16-bit word. + static immutable ubyte[3] odd = [0xAA, 0xBB, 0xCC]; + assert(internet_checksum(odd[]) == 0x8943); + + // Chained checksum across two buffers must equal the concatenated buffer's + // checksum, when the prior result is passed directly as `initial`. (This + // is the pseudo-header + segment pattern used by TCP/UDP.) + static immutable ubyte[6] part_a = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC]; + static immutable ubyte[6] part_b = [0xDE, 0xF0, 0x11, 0x22, 0x33, 0x44]; + ubyte[12] joined; + joined[0 .. 6] = part_a; + joined[6 .. 12] = part_b; + ushort partial = internet_checksum(part_a[]); + ushort chained = internet_checksum(part_b[], partial); + assert(chained == internet_checksum(joined[])); +} From a55bbc17a1935fe7307413db0315e915608b31ef Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Fri, 1 May 2026 09:14:57 +1000 Subject: [PATCH 138/138] Hash functions should be pure. --- src/urt/crc.d | 2 +- src/urt/hash.d | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/urt/crc.d b/src/urt/crc.d index 1faac53..e45b6da 100644 --- a/src/urt/crc.d +++ b/src/urt/crc.d @@ -415,7 +415,7 @@ enum crc_params[] param_table = [ ]; // helper function to reflect bits (reverse bit order) -T reflect(T)(T value, ubyte bits) +T reflect(T)(T value, ubyte bits) pure { T result = 0; foreach (i; 0..bits) diff --git a/src/urt/hash.d b/src/urt/hash.d index 81b3508..95e47ed 100644 --- a/src/urt/hash.d +++ b/src/urt/hash.d @@ -19,7 +19,7 @@ template fnv1_initial(T) enum T fnv1_initial = 0xCBF29CE484222325; } -T fnv1(T, bool alternate)(const ubyte[] s, T hash = fnv1_initial!T) pure nothrow @nogc +T fnv1(T, bool alternate)(const ubyte[] s, T hash = fnv1_initial!T) pure if (is(T == ushort) || is(T == uint) || is(T == ulong)) { static if (is(T == ushort)) @@ -59,16 +59,16 @@ unittest version (Espressif) { - private extern(C) uint mz_adler32(uint adler, const ubyte* ptr, size_t buf_len) nothrow @nogc; + private extern(C) uint mz_adler32(uint adler, const ubyte* ptr, size_t buf_len) pure nothrow @nogc; - uint adler32(const void[] data, uint init = 1) + uint adler32(const void[] data, uint init = 1) pure { return mz_adler32(init, cast(const ubyte*)data.ptr, data.length); } } else { - uint adler32(const void[] data, uint init = 1) + uint adler32(const void[] data, uint init = 1) pure { enum A32_BASE = 65521; @@ -194,7 +194,7 @@ unittest // NOTE: progressive accumulation via `initial` works only when prior chunks have even length!!! // odd-length chunks misalign the 16-bit word pairing. fixing this requires carrying a pending byte between calls :/ // maybe there's some way to protect against misuse? -ushort internet_checksum(const void[] data, ushort initial = 0xFFFF) +ushort internet_checksum(const void[] data, ushort initial = 0xFFFF) pure { auto bytes = cast(const(const ubyte)[])data;