diff options
| author | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2026-01-22 11:47:23 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-22 11:47:23 +0100 |
| commit | 3f8a32aeb963e056ac7709c26f05a99fb884ef95 (patch) | |
| tree | 4eed35fa23e9f4c5b7dedc6a355b16f5d3119661 | |
| parent | fb479b3aaec5a9a715bcff7d65498ba18020f4f0 (diff) | |
| parent | 5c09550d383df49a1d29d8f66bc1c45ee3be1136 (diff) | |
Merge branch 'master' into xmlcomment
127 files changed, 15532 insertions, 3169 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6219f370..0d2f3f1ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -156,18 +156,14 @@ jobs: - name: Check benchmarks run: ./odin check tests/benchmark -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point + - name: Odin check examples/all for Linux i386 if: matrix.os == 'ubuntu-latest' run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_i386 - - name: Odin check examples/all for Linux arm64 - if: matrix.os == 'ubuntu-latest' - run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm64 - - name: Odin check examples/all for FreeBSD amd64 - if: matrix.os == 'ubuntu-latest' - run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64 - name: Odin check examples/all for OpenBSD amd64 if: matrix.os == 'ubuntu-latest' run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:openbsd_amd64 + - name: Odin check examples/all for js_wasm32 if: matrix.os == 'ubuntu-latest' run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:js_wasm32 @@ -178,12 +174,6 @@ jobs: - name: Odin check examples/all/sdl3 for Linux i386 if: matrix.os == 'ubuntu-latest' run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:linux_i386 - - name: Odin check examples/all/sdl3 for Linux arm64 - if: matrix.os == 'ubuntu-latest' - run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:linux_arm64 - - name: Odin check examples/all/sdl3 for FreeBSD amd64 - if: matrix.os == 'ubuntu-latest' - run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:freebsd_amd64 - name: Odin check examples/all/sdl3 for OpenBSD amd64 if: matrix.os == 'ubuntu-latest' run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:openbsd_amd64 diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 6fecbaad9..02278a356 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -360,7 +360,7 @@ new_aligned :: proc($T: typeid, alignment: int, allocator := context.allocator, @(builtin, require_results) new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) #optional_allocator_error { - t = (^T)(raw_data(mem_alloc_bytes(size_of(T), align_of(T), allocator, loc) or_return)) + t = (^T)(raw_data(mem_alloc_non_zeroed(size_of(T), align_of(T), allocator, loc) or_return)) if t != nil { t^ = data } @@ -433,7 +433,6 @@ _make_dynamic_array_len_cap :: proc(array: ^Raw_Dynamic_Array, size_of_elem, ali array.data = raw_data(data) array.len = 0 if use_zero else len array.cap = 0 if use_zero else cap - array.allocator = allocator return } diff --git a/core/container/pool/pool.odin b/core/container/pool/pool.odin new file mode 100644 index 000000000..01fb29f2d --- /dev/null +++ b/core/container/pool/pool.odin @@ -0,0 +1,140 @@ +package container_pool + +import "base:intrinsics" +import "base:sanitizer" + +import "core:mem" +import "core:sync" + +_ :: sanitizer + +DEFAULT_BLOCK_SIZE :: _DEFAULT_BLOCK_SIZE + +Pool_Arena :: _Pool_Arena + +/* +A thread-safe (between init and destroy) object pool backed by virtual growing arena returning stable pointers. +The element type requires an intrusive link node. + +Example: + Elem :: struct { + link: ^Elem, + } + + p: pool.Pool(Elem) + pool.init(&p, "link") +*/ +Pool :: struct($T: typeid) { + arena: Pool_Arena, + num_outstanding: int, + num_ready: int, + link_off: uintptr, + free_list: ^T, +} + +@(require_results) +init :: proc(p: ^Pool($T), $link_field: string, block_size: uint = DEFAULT_BLOCK_SIZE) -> (err: mem.Allocator_Error) + where intrinsics.type_has_field(T, link_field), + intrinsics.type_field_type(T, link_field) == ^T { + p.link_off = offset_of_by_string(T, link_field) + return _pool_arena_init(&p.arena, block_size) +} + +destroy :: proc(p: ^Pool($T)) { + elem := sync.atomic_exchange_explicit(&p.free_list, nil, .Acquire) + + sync.atomic_store_explicit(&p.num_ready, 0, .Relaxed) + + when .Address in ODIN_SANITIZER_FLAGS { + for ; elem != nil; elem = _get_next(p, elem) { + _unpoison_elem(p, elem) + } + } else { + _ = elem + } + + _pool_arena_destroy(&p.arena) + p.arena = {} +} + +@(require_results) +get :: proc(p: ^Pool($T)) -> (elem: ^T, err: mem.Allocator_Error) #optional_allocator_error { + defer sync.atomic_add_explicit(&p.num_outstanding, 1, .Relaxed) + + for { + elem = sync.atomic_load_explicit(&p.free_list, .Acquire) + if elem == nil { + // NOTE: pool arena has an internal lock. + return new(T, _pool_arena_allocator(&p.arena)) + } + + if _, ok := sync.atomic_compare_exchange_weak_explicit(&p.free_list, elem, _get_next(p, elem), .Acquire, .Relaxed); ok { + _set_next(p, elem, nil) + _unpoison_elem(p, elem) + sync.atomic_sub_explicit(&p.num_ready, 1, .Relaxed) + return + } + } +} + +put :: proc(p: ^Pool($T), elem: ^T) { + mem.zero_item(elem) + _poison_elem(p, elem) + + defer sync.atomic_sub_explicit(&p.num_outstanding, 1, .Relaxed) + defer sync.atomic_add_explicit(&p.num_ready, 1, .Relaxed) + + for { + head := sync.atomic_load_explicit(&p.free_list, .Relaxed) + _set_next(p, elem, head) + if _, ok := sync.atomic_compare_exchange_weak_explicit(&p.free_list, head, elem, .Release, .Relaxed); ok { + return + } + } +} + +num_outstanding :: proc(p: ^Pool($T)) -> int { + return sync.atomic_load(&p.num_outstanding) +} + +num_ready :: proc(p: ^Pool($T)) -> int { + return sync.atomic_load(&p.num_ready) +} + +cap :: proc(p: ^Pool($T)) -> int { + return sync.atomic_load(&p.num_ready) + sync.atomic_load(&p.num_outstanding) +} + +_get_next :: proc(p: ^Pool($T), elem: ^T) -> ^T { + return (^^T)(uintptr(elem) + p.link_off)^ +} + +_set_next :: proc(p: ^Pool($T), elem: ^T, next: ^T) { + (^^T)(uintptr(elem) + p.link_off)^ = next +} + +@(disabled=.Address not_in ODIN_SANITIZER_FLAGS) +_poison_elem :: proc(p: ^Pool($T), elem: ^T) { + if p.link_off > 0 { + sanitizer.address_poison_rawptr(elem, int(p.link_off)) + } + + len := size_of(T) - p.link_off - size_of(rawptr) + if len > 0 { + ptr := rawptr(uintptr(elem) + p.link_off + size_of(rawptr)) + sanitizer.address_poison_rawptr(ptr, int(len)) + } +} + +@(disabled=.Address not_in ODIN_SANITIZER_FLAGS) +_unpoison_elem :: proc(p: ^Pool($T), elem: ^T) { + if p.link_off > 0 { + sanitizer.address_unpoison_rawptr(elem, int(p.link_off)) + } + + len := size_of(T) - p.link_off - size_of(rawptr) + if len > 0 { + ptr := rawptr(uintptr(elem) + p.link_off + size_of(rawptr)) + sanitizer.address_unpoison_rawptr(ptr, int(len)) + } +} diff --git a/core/container/pool/pool_arena_others.odin b/core/container/pool/pool_arena_others.odin new file mode 100644 index 000000000..3069076f7 --- /dev/null +++ b/core/container/pool/pool_arena_others.odin @@ -0,0 +1,29 @@ +#+build !darwin +#+build !freebsd +#+build !openbsd +#+build !netbsd +#+build !linux +#+build !windows +#+private +package container_pool + +import "base:runtime" + +import "core:mem" + +_Pool_Arena :: runtime.Arena + +_DEFAULT_BLOCK_SIZE :: mem.Megabyte + +_pool_arena_init :: proc(arena: ^Pool_Arena, block_size: uint = DEFAULT_BLOCK_SIZE) -> (err: runtime.Allocator_Error) { + runtime.arena_init(arena, block_size, runtime.default_allocator()) or_return + return +} + +_pool_arena_allocator :: proc(arena: ^Pool_Arena) -> runtime.Allocator { + return runtime.arena_allocator(arena) +} + +_pool_arena_destroy :: proc(arena: ^Pool_Arena) { + runtime.arena_destroy(arena) +} diff --git a/core/container/pool/pool_arena_virtual.odin b/core/container/pool/pool_arena_virtual.odin new file mode 100644 index 000000000..192e60260 --- /dev/null +++ b/core/container/pool/pool_arena_virtual.odin @@ -0,0 +1,24 @@ +#+build darwin, freebsd, openbsd, netbsd, linux, windows +package container_pool + +import "base:runtime" + +import "core:mem" +import "core:mem/virtual" + +_Pool_Arena :: virtual.Arena + +_DEFAULT_BLOCK_SIZE :: mem.Gigabyte + +_pool_arena_init :: proc(arena: ^Pool_Arena, block_size: uint = DEFAULT_BLOCK_SIZE) -> (err: runtime.Allocator_Error) { + virtual.arena_init_growing(arena, block_size) or_return + return +} + +_pool_arena_allocator :: proc(arena: ^Pool_Arena) -> runtime.Allocator { + return virtual.arena_allocator(arena) +} + +_pool_arena_destroy :: proc(arena: ^Pool_Arena) { + virtual.arena_destroy(arena) +} diff --git a/core/container/rbtree/rbtree.odin b/core/container/rbtree/rbtree.odin index 35ce21413..e892188d7 100644 --- a/core/container/rbtree/rbtree.odin +++ b/core/container/rbtree/rbtree.odin @@ -91,7 +91,7 @@ destroy :: proc(t: ^$T/Tree($Key, $Value), call_on_remove: bool = true) { } } -len :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> (node_count: int) { +len :: proc "contextless" (t: $T/Tree($Key, $Value)) -> (node_count: int) { return t._size } @@ -108,7 +108,7 @@ last :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> ^Node(Key, Value) { } // find finds the key in the tree, and returns the corresponding node, or nil iff the value is not present. -find :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) { +find :: proc(t: $T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) { node = t._root for node != nil { switch t._cmp_fn(key, node.key) { @@ -121,7 +121,7 @@ find :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) { } // find_value finds the key in the tree, and returns the corresponding value, or nil iff the value is not present. -find_value :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (value: Value, ok: bool) #optional_ok { +find_value :: proc(t: $T/Tree($Key, $Value), key: Key) -> (value: Value, ok: bool) #optional_ok { if n := find(t, key); n != nil { return n.value, true } @@ -166,7 +166,7 @@ remove :: proc { // removal was successful. While the node's key + value will be left intact, // the node itself will be freed via the tree's node allocator. remove_key :: proc(t: ^$T/Tree($Key, $Value), key: Key, call_on_remove := true) -> bool { - n := find(t, key) + n := find(t^, key) if n == nil { return false // Key not found, nothing to do } diff --git a/core/container/xar/xar.odin b/core/container/xar/xar.odin index 8ed5bc3e4..07fdf5a15 100644 --- a/core/container/xar/xar.odin +++ b/core/container/xar/xar.odin @@ -417,9 +417,10 @@ Create an iterator for traversing the exponential array. Example: - import "lib:xar" + import "core:container/xar" + import "core:fmt" - iteration_example :: proc() { + iterator_example :: proc() { x: xar.Array(int, 4) defer xar.destroy(&x) diff --git a/core/encoding/base64/base64.odin b/core/encoding/base64/base64.odin index 032716bde..0677fdfb7 100644 --- a/core/encoding/base64/base64.odin +++ b/core/encoding/base64/base64.odin @@ -24,6 +24,18 @@ ENC_TABLE := [64]byte { '4', '5', '6', '7', '8', '9', '+', '/', } +// Encoding table for Base64url variant +ENC_URL_TABLE := [64]byte { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '-', '_', +} + PADDING :: '=' DEC_TABLE := [256]u8 { @@ -61,6 +73,43 @@ DEC_TABLE := [256]u8 { 0, 0, 0, 0, 0, 0, 0, 0, } +// Decoding table for Base64url variant +DEC_URL_TABLE := [256]u8 { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +} + + encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> (encoded: string, err: mem.Allocator_Error) #optional_allocator_error { out_length := encoded_len(data) if out_length == 0 { diff --git a/core/encoding/entity/entity.odin b/core/encoding/entity/entity.odin index e112eedf2..a756bdf39 100644 --- a/core/encoding/entity/entity.odin +++ b/core/encoding/entity/entity.odin @@ -21,6 +21,7 @@ package encoding_unicode_entity Jeroen van Rijn: Initial implementation. */ +import "base:runtime" import "core:unicode/utf8" import "core:unicode" import "core:strings" @@ -141,8 +142,10 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := write_string(&builder, entity) } else { if .No_Entity_Decode not_in options { - if decoded, ok := xml_decode_entity(entity); ok { - write_rune(&builder, decoded) + if decoded, count, ok := xml_decode_entity(entity); ok { + for i in 0..<count { + write_rune(&builder, decoded[i]) + } continue } } @@ -212,17 +215,16 @@ advance :: proc(t: ^Tokenizer) -> (err: Error) { } } -xml_decode_entity :: proc(entity: string) -> (decoded: rune, ok: bool) { +xml_decode_entity :: proc(entity: string) -> (decoded: [2]rune, rune_count: int, ok: bool) { entity := entity - if len(entity) == 0 { return -1, false } + if len(entity) == 0 { return } - switch entity[0] { - case '#': + if entity[0] == '#' { base := 10 val := 0 entity = entity[1:] - if len(entity) == 0 { return -1, false } + if len(entity) == 0 { return } if entity[0] == 'x' || entity[0] == 'X' { base = 16 @@ -237,30 +239,275 @@ xml_decode_entity :: proc(entity: string) -> (decoded: rune, ok: bool) { val += int(r - '0') case 'a'..='f': - if base == 10 { return -1, false } + if base == 10 { return } val *= base val += int(r - 'a' + 10) case 'A'..='F': - if base == 10 { return -1, false } + if base == 10 { return } val *= base val += int(r - 'A' + 10) case: - return -1, false + return } - if val > MAX_RUNE_CODEPOINT { return -1, false } + if val > MAX_RUNE_CODEPOINT { return } entity = entity[1:] } - return rune(val), true + return rune(val), 1, true + } + // Named entity. + return named_xml_entity_to_rune(entity) +} + + +// escape_html escapes special characters like '&' to become '&'. +// It escapes only 5 different characters: & ' < > and " +@(require_results) +escape_html :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> (output: string, was_allocation: bool) { + /* + & -> & + ' -> ' // ' is shorter than ' (NOTE: ' was not available until HTML 5) + < -> < + > -> > + " -> " // " is shorter than " + */ + + b := transmute([]byte)s + + extra_bytes_needed := 0 + + for c in b { + switch c { + case '&': extra_bytes_needed += 4 + case '\'': extra_bytes_needed += 4 + case '<': extra_bytes_needed += 3 + case '>': extra_bytes_needed += 3 + case '"': extra_bytes_needed += 4 + } + } + + if extra_bytes_needed == 0 { + return s, false + } + + t, err := make([]byte, len(s) + extra_bytes_needed, allocator, loc) + if err != nil { + return + } + was_allocation = true + + w := 0 + for c in b { + x := "" + switch c { + case '&': x = "&" + case '\'': x = "'" + case '<': x = "<" + case '>': x = ">" + case '"': x = """ + } + if x != "" { + copy(t[w:], x) + w += len(x) + } else { + t[w] = c + w += 1 + } + } + output = string(t[0:w]) + return +} + + +@(require_results) +unescape_html :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> (output: string, was_allocation: bool, err: runtime.Allocator_Error) { + @(require_results) + do_append :: proc(s: string, amp_idx: int, buf: ^[dynamic]byte) -> (n: int) { + s, amp_idx := s, amp_idx + + n += len(s[:amp_idx]) + if buf != nil { append(buf, s[:amp_idx]) } + s = s[amp_idx:] + for len(s) > 0 { + b, w, j := unescape_entity(s) + n += w + if buf != nil { append(buf, ..b[:w]) } + + s = s[j:] + + amp_idx = strings.index_byte(s, '&') + if amp_idx < 0 { + n += len(s) + if buf != nil { append(buf, s) } + break + } + n += amp_idx + if buf != nil { append(buf, s[:amp_idx]) } + s = s[amp_idx:] + } + + return + } - case: - // Named entity. - return named_xml_entity_to_rune(entity) + s := s + amp_idx := strings.index_byte(s, '&') + if amp_idx < 0 { + return s, false, nil } + + // NOTE(bill): this does a two pass in order to minimize the allocations required + bytes_required := do_append(s, amp_idx, nil) + + buf := make([dynamic]byte, 0, bytes_required, allocator, loc) or_return + was_allocation = true + + _ = do_append(s, amp_idx, &buf) + + assert(len(buf) == cap(buf)) + output = string(buf[:]) + + return } +// Returns an unescaped string of an encoded XML/HTML entity. +@(require_results) +unescape_entity :: proc(s: string) -> (b: [8]byte, w: int, j: int) { + s := s + if len(s) < 2 { + return + } + if s[0] != '&' { + return + } + j = 1 + + if s[j] == '#' { // scan numbers + j += 1 + if len(s) <= 3 { // remove `&#.` + return + } + c := s[j] + hex := false + if c == 'x' || c == 'X' { + hex = true + j += 1 + } + + x := rune(0) + scan_number: for j < len(s) { + c = s[j] + j += 1 + if hex { + switch c { + case '0'..='9': x = 16*x + rune(c) - '0'; continue scan_number + case 'a'..='f': x = 16*x + rune(c) - 'a' + 10; continue scan_number + case 'A'..='F': x = 16*x + rune(c) - 'A' + 10; continue scan_number + } + } else { + switch c { + case '0'..='9': x = 10*x + rune(c) - '0'; continue scan_number + } + } + + // Keep the ';' to check for cases which require it and cases which might not + if c != ';' { + j -= 1 + } + break scan_number + } + + + if j <= 3 { // no replacement characters found + return + } + + @(static, rodata) + windows_1252_replacement_table := [0xa0 - 0x80]rune{ // Windows-1252 -> UTF-8 + '\u20ac', '\u0081', '\u201a', '\u0192', + '\u201e', '\u2026', '\u2020', '\u2021', + '\u02c6', '\u2030', '\u0160', '\u2039', + '\u0152', '\u008d', '\u017d', '\u008f', + '\u0090', '\u2018', '\u2019', '\u201c', + '\u201d', '\u2022', '\u2013', '\u2014', + '\u02dc', '\u2122', '\u0161', '\u203a', + '\u0153', '\u009d', '\u017e', '\u0178', + } + + switch x { + case 0x80..<0xa0: + x = windows_1252_replacement_table[x-0x80] + case 0, 0xd800..=0xdfff: + x = utf8.RUNE_ERROR + case: + if x > 0x10ffff { + x = utf8.RUNE_ERROR + } + + } + + b1, w1 := utf8.encode_rune(x) + w += copy(b[:], b1[:w1]) + return + } + + // Lookup by entity names + + scan_ident: for j < len(s) { // scan over letters and digits + c := s[j] + j += 1 + + switch c { + case 'a'..='z', 'A'..='Z', '0'..='9': + continue scan_ident + } + // Keep the ';' to check for cases which require it and cases which might not + if c != ';' { + j -= 1 + } + break scan_ident + } + + entity_name := s[1:j] + if len(entity_name) == 0 { + return + } + + if entity_name[len(entity_name)-1] == ';' { + entity_name = entity_name[:len(entity_name)-1] + } + + if r2, _, ok := named_xml_entity_to_rune(entity_name); ok { + b1, w1 := utf8.encode_rune(r2[0]) + w += copy(b[w:], b1[:w1]) + if r2[1] != 0 { + b2, w2 := utf8.encode_rune(r2[1]) + w += copy(b[w:], b2[:w2]) + } + return + } + + // The longest entities that do not end with a semicolon are <=6 bytes long + LONGEST_ENTITY_WITHOUT_SEMICOLON :: 6 + + n := min(len(entity_name)-1, LONGEST_ENTITY_WITHOUT_SEMICOLON) + for i := n; i > 1; i -= 1 { + if r2, _, ok := named_xml_entity_to_rune(entity_name[:i]); ok { + b1, w1 := utf8.encode_rune(r2[0]) + w += copy(b[w:], b1[:w1]) + if r2[1] != 0 { + b2, w2 := utf8.encode_rune(r2[1]) + w += copy(b[w:], b2[:w2]) + } + return + } + } + + return +} + + // Private XML helper to extract `&<stuff>;` entity. @(private="file") _extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) { diff --git a/core/encoding/entity/generated.odin b/core/encoding/entity/generated.odin index 52027ae03..0096c8199 100644 --- a/core/encoding/entity/generated.odin +++ b/core/encoding/entity/generated.odin @@ -43,5047 +43,5047 @@ XML_NAME_TO_RUNE_MAX_LENGTH :: 31 entity_name - a string, like "copy" that describes a user-encoded Unicode entity as used in XML. Returns: - "decoded" - The decoded rune if found by name, or -1 otherwise. - "ok" - true if found, false if not. + "decoded" - The decoded runes if found by name, or all zero otherwise. + "rune_count" - The number of decoded runes + "ok" - true if found, false if not. IMPORTANT: XML processors (including browsers) treat these names as case-sensitive. So do we. */ -named_xml_entity_to_rune :: proc(name: string) -> (decoded: rune, ok: bool) { +named_xml_entity_to_rune :: proc(name: string) -> (decoded: [2]rune, rune_count: int, ok: bool) { /* Early out if the name is too short or too long. min as a precaution in case the generated table has a bogus value. */ if len(name) < min(1, XML_NAME_TO_RUNE_MIN_LENGTH) || len(name) > XML_NAME_TO_RUNE_MAX_LENGTH { - return -1, false + return } switch rune(name[0]) { - case 'A': switch name { case "AElig": // LATIN CAPITAL LETTER AE - return rune(0xc6), true + return {'Æ', 0}, 1, true case "AMP": // AMPERSAND - return rune(0x26), true + return {'&', 0}, 1, true case "Aacgr": // GREEK CAPITAL LETTER ALPHA WITH TONOS - return rune(0x0386), true + return {'\u0386', 0}, 1, true case "Aacute": // LATIN CAPITAL LETTER A WITH ACUTE - return rune(0xc1), true + return {'Á', 0}, 1, true case "Abreve": // LATIN CAPITAL LETTER A WITH BREVE - return rune(0x0102), true + return {'\u0102', 0}, 1, true case "Acirc": // LATIN CAPITAL LETTER A WITH CIRCUMFLEX - return rune(0xc2), true + return {'Â', 0}, 1, true case "Acy": // CYRILLIC CAPITAL LETTER A - return rune(0x0410), true + return {'\u0410', 0}, 1, true case "Afr": // MATHEMATICAL FRAKTUR CAPITAL A - return rune(0x01d504), true + return {'\U0001d504', 0}, 1, true case "Agr": // GREEK CAPITAL LETTER ALPHA - return rune(0x0391), true + return {'\u0391', 0}, 1, true case "Agrave": // LATIN CAPITAL LETTER A WITH GRAVE - return rune(0xc0), true + return {'À', 0}, 1, true case "Alpha": // GREEK CAPITAL LETTER ALPHA - return rune(0x0391), true + return {'\u0391', 0}, 1, true case "Amacr": // LATIN CAPITAL LETTER A WITH MACRON - return rune(0x0100), true + return {'\u0100', 0}, 1, true case "And": // DOUBLE LOGICAL AND - return rune(0x2a53), true + return {'\u2a53', 0}, 1, true case "Aogon": // LATIN CAPITAL LETTER A WITH OGONEK - return rune(0x0104), true + return {'\u0104', 0}, 1, true case "Aopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL A - return rune(0x01d538), true + return {'\U0001d538', 0}, 1, true case "ApplyFunction": // FUNCTION APPLICATION - return rune(0x2061), true + return {'\u2061', 0}, 1, true case "Aring": // LATIN CAPITAL LETTER A WITH RING ABOVE - return rune(0xc5), true + return {'Å', 0}, 1, true case "Ascr": // MATHEMATICAL SCRIPT CAPITAL A - return rune(0x01d49c), true + return {'\U0001d49c', 0}, 1, true case "Assign": // COLON EQUALS - return rune(0x2254), true + return {'\u2254', 0}, 1, true case "Ast": // TWO ASTERISKS ALIGNED VERTICALLY - return rune(0x2051), true + return {'\u2051', 0}, 1, true case "Atilde": // LATIN CAPITAL LETTER A WITH TILDE - return rune(0xc3), true + return {'Ã', 0}, 1, true case "Auml": // LATIN CAPITAL LETTER A WITH DIAERESIS - return rune(0xc4), true + return {'Ä', 0}, 1, true } case 'B': switch name { case "Backslash": // SET MINUS - return rune(0x2216), true + return {'\u2216', 0}, 1, true case "Barint": // INTEGRAL WITH DOUBLE STROKE - return rune(0x2a0e), true + return {'\u2a0e', 0}, 1, true case "Barv": // SHORT DOWN TACK WITH OVERBAR - return rune(0x2ae7), true + return {'\u2ae7', 0}, 1, true case "Barwed": // PERSPECTIVE - return rune(0x2306), true + return {'\u2306', 0}, 1, true case "Barwedl": // LOGICAL AND WITH DOUBLE OVERBAR - return rune(0x2a5e), true + return {'\u2a5e', 0}, 1, true case "Bcy": // CYRILLIC CAPITAL LETTER BE - return rune(0x0411), true + return {'\u0411', 0}, 1, true case "Because": // BECAUSE - return rune(0x2235), true + return {'\u2235', 0}, 1, true case "Bernoullis": // SCRIPT CAPITAL B - return rune(0x212c), true + return {'\u212c', 0}, 1, true case "Beta": // GREEK CAPITAL LETTER BETA - return rune(0x0392), true + return {'\u0392', 0}, 1, true case "Bfr": // MATHEMATICAL FRAKTUR CAPITAL B - return rune(0x01d505), true + return {'\U0001d505', 0}, 1, true case "Bgr": // GREEK CAPITAL LETTER BETA - return rune(0x0392), true + return {'\u0392', 0}, 1, true case "Bopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL B - return rune(0x01d539), true + return {'\U0001d539', 0}, 1, true case "Breve": // BREVE - return rune(0x02d8), true + return {'\u02d8', 0}, 1, true case "Bscr": // SCRIPT CAPITAL B - return rune(0x212c), true + return {'\u212c', 0}, 1, true case "Bumpeq": // GEOMETRICALLY EQUIVALENT TO - return rune(0x224e), true + return {'\u224e', 0}, 1, true case "Bvert": // BOX DRAWINGS LIGHT TRIPLE DASH VERTICAL - return rune(0x2506), true + return {'\u2506', 0}, 1, true } case 'C': switch name { case "CHcy": // CYRILLIC CAPITAL LETTER CHE - return rune(0x0427), true + return {'\u0427', 0}, 1, true case "COPY": // COPYRIGHT SIGN - return rune(0xa9), true + return {'©', 0}, 1, true case "Cacute": // LATIN CAPITAL LETTER C WITH ACUTE - return rune(0x0106), true + return {'\u0106', 0}, 1, true case "Cap": // DOUBLE INTERSECTION - return rune(0x22d2), true + return {'\u22d2', 0}, 1, true case "CapitalDifferentialD": // DOUBLE-STRUCK ITALIC CAPITAL D - return rune(0x2145), true + return {'\u2145', 0}, 1, true case "Cayleys": // BLACK-LETTER CAPITAL C - return rune(0x212d), true + return {'\u212d', 0}, 1, true case "Ccaron": // LATIN CAPITAL LETTER C WITH CARON - return rune(0x010c), true + return {'\u010c', 0}, 1, true case "Ccedil": // LATIN CAPITAL LETTER C WITH CEDILLA - return rune(0xc7), true + return {'Ç', 0}, 1, true case "Ccirc": // LATIN CAPITAL LETTER C WITH CIRCUMFLEX - return rune(0x0108), true + return {'\u0108', 0}, 1, true case "Cconint": // VOLUME INTEGRAL - return rune(0x2230), true + return {'\u2230', 0}, 1, true case "Cdot": // LATIN CAPITAL LETTER C WITH DOT ABOVE - return rune(0x010a), true + return {'\u010a', 0}, 1, true case "Cedilla": // CEDILLA - return rune(0xb8), true + return {'¸', 0}, 1, true case "CenterDot": // MIDDLE DOT - return rune(0xb7), true + return {'·', 0}, 1, true case "Cfr": // BLACK-LETTER CAPITAL C - return rune(0x212d), true + return {'\u212d', 0}, 1, true case "Chi": // GREEK CAPITAL LETTER CHI - return rune(0x03a7), true + return {'\u03a7', 0}, 1, true case "CircleDot": // CIRCLED DOT OPERATOR - return rune(0x2299), true + return {'\u2299', 0}, 1, true case "CircleMinus": // CIRCLED MINUS - return rune(0x2296), true + return {'\u2296', 0}, 1, true case "CirclePlus": // CIRCLED PLUS - return rune(0x2295), true + return {'\u2295', 0}, 1, true case "CircleTimes": // CIRCLED TIMES - return rune(0x2297), true + return {'\u2297', 0}, 1, true case "ClockwiseContourIntegral": // CLOCKWISE CONTOUR INTEGRAL - return rune(0x2232), true + return {'\u2232', 0}, 1, true case "CloseCurlyDoubleQuote": // RIGHT DOUBLE QUOTATION MARK - return rune(0x201d), true + return {'\u201d', 0}, 1, true case "CloseCurlyQuote": // RIGHT SINGLE QUOTATION MARK - return rune(0x2019), true + return {'\u2019', 0}, 1, true case "Colon": // PROPORTION - return rune(0x2237), true + return {'\u2237', 0}, 1, true case "Colone": // DOUBLE COLON EQUAL - return rune(0x2a74), true + return {'\u2a74', 0}, 1, true case "Congruent": // IDENTICAL TO - return rune(0x2261), true + return {'\u2261', 0}, 1, true case "Conint": // SURFACE INTEGRAL - return rune(0x222f), true + return {'\u222f', 0}, 1, true case "ContourIntegral": // CONTOUR INTEGRAL - return rune(0x222e), true + return {'\u222e', 0}, 1, true case "Copf": // DOUBLE-STRUCK CAPITAL C - return rune(0x2102), true + return {'\u2102', 0}, 1, true case "Coproduct": // N-ARY COPRODUCT - return rune(0x2210), true + return {'\u2210', 0}, 1, true case "CounterClockwiseContourIntegral": // ANTICLOCKWISE CONTOUR INTEGRAL - return rune(0x2233), true + return {'\u2233', 0}, 1, true case "Cross": // VECTOR OR CROSS PRODUCT - return rune(0x2a2f), true + return {'\u2a2f', 0}, 1, true case "Cscr": // MATHEMATICAL SCRIPT CAPITAL C - return rune(0x01d49e), true + return {'\U0001d49e', 0}, 1, true case "Cup": // DOUBLE UNION - return rune(0x22d3), true + return {'\u22d3', 0}, 1, true case "CupCap": // EQUIVALENT TO - return rune(0x224d), true + return {'\u224d', 0}, 1, true } case 'D': switch name { case "DD": // DOUBLE-STRUCK ITALIC CAPITAL D - return rune(0x2145), true + return {'\u2145', 0}, 1, true case "DDotrahd": // RIGHTWARDS ARROW WITH DOTTED STEM - return rune(0x2911), true + return {'\u2911', 0}, 1, true case "DJcy": // CYRILLIC CAPITAL LETTER DJE - return rune(0x0402), true + return {'\u0402', 0}, 1, true case "DScy": // CYRILLIC CAPITAL LETTER DZE - return rune(0x0405), true + return {'\u0405', 0}, 1, true case "DZcy": // CYRILLIC CAPITAL LETTER DZHE - return rune(0x040f), true + return {'\u040f', 0}, 1, true case "Dagger": // DOUBLE DAGGER - return rune(0x2021), true + return {'\u2021', 0}, 1, true case "Darr": // DOWNWARDS TWO HEADED ARROW - return rune(0x21a1), true + return {'\u21a1', 0}, 1, true case "Dashv": // VERTICAL BAR DOUBLE LEFT TURNSTILE - return rune(0x2ae4), true + return {'\u2ae4', 0}, 1, true case "Dcaron": // LATIN CAPITAL LETTER D WITH CARON - return rune(0x010e), true + return {'\u010e', 0}, 1, true case "Dcy": // CYRILLIC CAPITAL LETTER DE - return rune(0x0414), true + return {'\u0414', 0}, 1, true case "Del": // NABLA - return rune(0x2207), true + return {'\u2207', 0}, 1, true case "Delta": // GREEK CAPITAL LETTER DELTA - return rune(0x0394), true + return {'\u0394', 0}, 1, true case "Dfr": // MATHEMATICAL FRAKTUR CAPITAL D - return rune(0x01d507), true + return {'\U0001d507', 0}, 1, true case "Dgr": // GREEK CAPITAL LETTER DELTA - return rune(0x0394), true + return {'\u0394', 0}, 1, true case "DiacriticalAcute": // ACUTE ACCENT - return rune(0xb4), true + return {'´', 0}, 1, true case "DiacriticalDot": // DOT ABOVE - return rune(0x02d9), true + return {'\u02d9', 0}, 1, true case "DiacriticalDoubleAcute": // DOUBLE ACUTE ACCENT - return rune(0x02dd), true + return {'\u02dd', 0}, 1, true case "DiacriticalGrave": // GRAVE ACCENT - return rune(0x60), true + return {'`', 0}, 1, true case "DiacriticalTilde": // SMALL TILDE - return rune(0x02dc), true + return {'\u02dc', 0}, 1, true case "Diamond": // DIAMOND OPERATOR - return rune(0x22c4), true + return {'\u22c4', 0}, 1, true case "DifferentialD": // DOUBLE-STRUCK ITALIC SMALL D - return rune(0x2146), true + return {'\u2146', 0}, 1, true case "Dopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL D - return rune(0x01d53b), true + return {'\U0001d53b', 0}, 1, true case "Dot": // DIAERESIS - return rune(0xa8), true + return {'¨', 0}, 1, true case "DotDot": // COMBINING FOUR DOTS ABOVE - return rune(0x20dc), true + return {'\u20dc', 0}, 1, true case "DotEqual": // APPROACHES THE LIMIT - return rune(0x2250), true + return {'\u2250', 0}, 1, true case "DoubleContourIntegral": // SURFACE INTEGRAL - return rune(0x222f), true + return {'\u222f', 0}, 1, true case "DoubleDot": // DIAERESIS - return rune(0xa8), true + return {'¨', 0}, 1, true case "DoubleDownArrow": // DOWNWARDS DOUBLE ARROW - return rune(0x21d3), true + return {'\u21d3', 0}, 1, true case "DoubleLeftArrow": // LEFTWARDS DOUBLE ARROW - return rune(0x21d0), true + return {'\u21d0', 0}, 1, true case "DoubleLeftRightArrow": // LEFT RIGHT DOUBLE ARROW - return rune(0x21d4), true + return {'\u21d4', 0}, 1, true case "DoubleLeftTee": // VERTICAL BAR DOUBLE LEFT TURNSTILE - return rune(0x2ae4), true + return {'\u2ae4', 0}, 1, true case "DoubleLongLeftArrow": // LONG LEFTWARDS DOUBLE ARROW - return rune(0x27f8), true + return {'\u27f8', 0}, 1, true case "DoubleLongLeftRightArrow": // LONG LEFT RIGHT DOUBLE ARROW - return rune(0x27fa), true + return {'\u27fa', 0}, 1, true case "DoubleLongRightArrow": // LONG RIGHTWARDS DOUBLE ARROW - return rune(0x27f9), true + return {'\u27f9', 0}, 1, true case "DoubleRightArrow": // RIGHTWARDS DOUBLE ARROW - return rune(0x21d2), true + return {'\u21d2', 0}, 1, true case "DoubleRightTee": // TRUE - return rune(0x22a8), true + return {'\u22a8', 0}, 1, true case "DoubleUpArrow": // UPWARDS DOUBLE ARROW - return rune(0x21d1), true + return {'\u21d1', 0}, 1, true case "DoubleUpDownArrow": // UP DOWN DOUBLE ARROW - return rune(0x21d5), true + return {'\u21d5', 0}, 1, true case "DoubleVerticalBar": // PARALLEL TO - return rune(0x2225), true + return {'\u2225', 0}, 1, true case "DownArrow": // DOWNWARDS ARROW - return rune(0x2193), true + return {'\u2193', 0}, 1, true case "DownArrowBar": // DOWNWARDS ARROW TO BAR - return rune(0x2913), true + return {'\u2913', 0}, 1, true case "DownArrowUpArrow": // DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW - return rune(0x21f5), true + return {'\u21f5', 0}, 1, true case "DownBreve": // COMBINING INVERTED BREVE - return rune(0x0311), true + return {'\u0311', 0}, 1, true case "DownLeftRightVector": // LEFT BARB DOWN RIGHT BARB DOWN HARPOON - return rune(0x2950), true + return {'\u2950', 0}, 1, true case "DownLeftTeeVector": // LEFTWARDS HARPOON WITH BARB DOWN FROM BAR - return rune(0x295e), true + return {'\u295e', 0}, 1, true case "DownLeftVector": // LEFTWARDS HARPOON WITH BARB DOWNWARDS - return rune(0x21bd), true + return {'\u21bd', 0}, 1, true case "DownLeftVectorBar": // LEFTWARDS HARPOON WITH BARB DOWN TO BAR - return rune(0x2956), true + return {'\u2956', 0}, 1, true case "DownRightTeeVector": // RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR - return rune(0x295f), true + return {'\u295f', 0}, 1, true case "DownRightVector": // RIGHTWARDS HARPOON WITH BARB DOWNWARDS - return rune(0x21c1), true + return {'\u21c1', 0}, 1, true case "DownRightVectorBar": // RIGHTWARDS HARPOON WITH BARB DOWN TO BAR - return rune(0x2957), true + return {'\u2957', 0}, 1, true case "DownTee": // DOWN TACK - return rune(0x22a4), true + return {'\u22a4', 0}, 1, true case "DownTeeArrow": // DOWNWARDS ARROW FROM BAR - return rune(0x21a7), true + return {'\u21a7', 0}, 1, true case "Downarrow": // DOWNWARDS DOUBLE ARROW - return rune(0x21d3), true + return {'\u21d3', 0}, 1, true case "Dscr": // MATHEMATICAL SCRIPT CAPITAL D - return rune(0x01d49f), true + return {'\U0001d49f', 0}, 1, true case "Dstrok": // LATIN CAPITAL LETTER D WITH STROKE - return rune(0x0110), true + return {'\u0110', 0}, 1, true } case 'E': switch name { case "EEacgr": // GREEK CAPITAL LETTER ETA WITH TONOS - return rune(0x0389), true + return {'\u0389', 0}, 1, true case "EEgr": // GREEK CAPITAL LETTER ETA - return rune(0x0397), true + return {'\u0397', 0}, 1, true case "ENG": // LATIN CAPITAL LETTER ENG - return rune(0x014a), true + return {'\u014a', 0}, 1, true case "ETH": // LATIN CAPITAL LETTER ETH - return rune(0xd0), true + return {'Ð', 0}, 1, true case "Eacgr": // GREEK CAPITAL LETTER EPSILON WITH TONOS - return rune(0x0388), true + return {'\u0388', 0}, 1, true case "Eacute": // LATIN CAPITAL LETTER E WITH ACUTE - return rune(0xc9), true + return {'É', 0}, 1, true case "Ecaron": // LATIN CAPITAL LETTER E WITH CARON - return rune(0x011a), true + return {'\u011a', 0}, 1, true case "Ecirc": // LATIN CAPITAL LETTER E WITH CIRCUMFLEX - return rune(0xca), true + return {'Ê', 0}, 1, true case "Ecy": // CYRILLIC CAPITAL LETTER E - return rune(0x042d), true + return {'\u042d', 0}, 1, true case "Edot": // LATIN CAPITAL LETTER E WITH DOT ABOVE - return rune(0x0116), true + return {'\u0116', 0}, 1, true case "Efr": // MATHEMATICAL FRAKTUR CAPITAL E - return rune(0x01d508), true + return {'\U0001d508', 0}, 1, true case "Egr": // GREEK CAPITAL LETTER EPSILON - return rune(0x0395), true + return {'\u0395', 0}, 1, true case "Egrave": // LATIN CAPITAL LETTER E WITH GRAVE - return rune(0xc8), true + return {'È', 0}, 1, true case "Element": // ELEMENT OF - return rune(0x2208), true + return {'\u2208', 0}, 1, true case "Emacr": // LATIN CAPITAL LETTER E WITH MACRON - return rune(0x0112), true + return {'\u0112', 0}, 1, true case "EmptySmallSquare": // WHITE MEDIUM SQUARE - return rune(0x25fb), true + return {'\u25fb', 0}, 1, true case "EmptyVerySmallSquare": // WHITE SMALL SQUARE - return rune(0x25ab), true + return {'\u25ab', 0}, 1, true case "Eogon": // LATIN CAPITAL LETTER E WITH OGONEK - return rune(0x0118), true + return {'\u0118', 0}, 1, true case "Eopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL E - return rune(0x01d53c), true + return {'\U0001d53c', 0}, 1, true case "Epsilon": // GREEK CAPITAL LETTER EPSILON - return rune(0x0395), true + return {'\u0395', 0}, 1, true case "Equal": // TWO CONSECUTIVE EQUALS SIGNS - return rune(0x2a75), true + return {'\u2a75', 0}, 1, true case "EqualTilde": // MINUS TILDE - return rune(0x2242), true + return {'\u2242', 0}, 1, true case "Equilibrium": // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON - return rune(0x21cc), true + return {'\u21cc', 0}, 1, true case "Escr": // SCRIPT CAPITAL E - return rune(0x2130), true + return {'\u2130', 0}, 1, true case "Esim": // EQUALS SIGN ABOVE TILDE OPERATOR - return rune(0x2a73), true + return {'\u2a73', 0}, 1, true case "Eta": // GREEK CAPITAL LETTER ETA - return rune(0x0397), true + return {'\u0397', 0}, 1, true case "Euml": // LATIN CAPITAL LETTER E WITH DIAERESIS - return rune(0xcb), true + return {'Ë', 0}, 1, true case "Exists": // THERE EXISTS - return rune(0x2203), true + return {'\u2203', 0}, 1, true case "ExponentialE": // DOUBLE-STRUCK ITALIC SMALL E - return rune(0x2147), true + return {'\u2147', 0}, 1, true } case 'F': switch name { case "Fcy": // CYRILLIC CAPITAL LETTER EF - return rune(0x0424), true + return {'\u0424', 0}, 1, true case "Ffr": // MATHEMATICAL FRAKTUR CAPITAL F - return rune(0x01d509), true + return {'\U0001d509', 0}, 1, true case "FilledSmallSquare": // BLACK MEDIUM SQUARE - return rune(0x25fc), true + return {'\u25fc', 0}, 1, true case "FilledVerySmallSquare": // BLACK SMALL SQUARE - return rune(0x25aa), true + return {'\u25aa', 0}, 1, true case "Fopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL F - return rune(0x01d53d), true + return {'\U0001d53d', 0}, 1, true case "ForAll": // FOR ALL - return rune(0x2200), true + return {'\u2200', 0}, 1, true case "Fouriertrf": // SCRIPT CAPITAL F - return rune(0x2131), true + return {'\u2131', 0}, 1, true case "Fscr": // SCRIPT CAPITAL F - return rune(0x2131), true + return {'\u2131', 0}, 1, true } case 'G': switch name { case "GJcy": // CYRILLIC CAPITAL LETTER GJE - return rune(0x0403), true + return {'\u0403', 0}, 1, true case "GT": // GREATER-THAN SIGN - return rune(0x3e), true + return {'>', 0}, 1, true case "Game": // TURNED SANS-SERIF CAPITAL G - return rune(0x2141), true + return {'\u2141', 0}, 1, true case "Gamma": // GREEK CAPITAL LETTER GAMMA - return rune(0x0393), true + return {'\u0393', 0}, 1, true case "Gammad": // GREEK LETTER DIGAMMA - return rune(0x03dc), true + return {'\u03dc', 0}, 1, true case "Gbreve": // LATIN CAPITAL LETTER G WITH BREVE - return rune(0x011e), true + return {'\u011e', 0}, 1, true case "Gcedil": // LATIN CAPITAL LETTER G WITH CEDILLA - return rune(0x0122), true + return {'\u0122', 0}, 1, true case "Gcirc": // LATIN CAPITAL LETTER G WITH CIRCUMFLEX - return rune(0x011c), true + return {'\u011c', 0}, 1, true case "Gcy": // CYRILLIC CAPITAL LETTER GHE - return rune(0x0413), true + return {'\u0413', 0}, 1, true case "Gdot": // LATIN CAPITAL LETTER G WITH DOT ABOVE - return rune(0x0120), true + return {'\u0120', 0}, 1, true case "Gfr": // MATHEMATICAL FRAKTUR CAPITAL G - return rune(0x01d50a), true + return {'\U0001d50a', 0}, 1, true case "Gg": // VERY MUCH GREATER-THAN - return rune(0x22d9), true + return {'\u22d9', 0}, 1, true case "Ggr": // GREEK CAPITAL LETTER GAMMA - return rune(0x0393), true + return {'\u0393', 0}, 1, true case "Gopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL G - return rune(0x01d53e), true + return {'\U0001d53e', 0}, 1, true case "GreaterEqual": // GREATER-THAN OR EQUAL TO - return rune(0x2265), true + return {'\u2265', 0}, 1, true case "GreaterEqualLess": // GREATER-THAN EQUAL TO OR LESS-THAN - return rune(0x22db), true + return {'\u22db', 0}, 1, true case "GreaterFullEqual": // GREATER-THAN OVER EQUAL TO - return rune(0x2267), true + return {'\u2267', 0}, 1, true case "GreaterGreater": // DOUBLE NESTED GREATER-THAN - return rune(0x2aa2), true + return {'\u2aa2', 0}, 1, true case "GreaterLess": // GREATER-THAN OR LESS-THAN - return rune(0x2277), true + return {'\u2277', 0}, 1, true case "GreaterSlantEqual": // GREATER-THAN OR SLANTED EQUAL TO - return rune(0x2a7e), true + return {'\u2a7e', 0}, 1, true case "GreaterTilde": // GREATER-THAN OR EQUIVALENT TO - return rune(0x2273), true + return {'\u2273', 0}, 1, true case "Gscr": // MATHEMATICAL SCRIPT CAPITAL G - return rune(0x01d4a2), true + return {'\U0001d4a2', 0}, 1, true case "Gt": // MUCH GREATER-THAN - return rune(0x226b), true + return {'\u226b', 0}, 1, true } case 'H': switch name { case "HARDcy": // CYRILLIC CAPITAL LETTER HARD SIGN - return rune(0x042a), true + return {'\u042a', 0}, 1, true case "Hacek": // CARON - return rune(0x02c7), true + return {'\u02c7', 0}, 1, true case "Hat": // CIRCUMFLEX ACCENT - return rune(0x5e), true + return {'^', 0}, 1, true case "Hcirc": // LATIN CAPITAL LETTER H WITH CIRCUMFLEX - return rune(0x0124), true + return {'\u0124', 0}, 1, true case "Hfr": // BLACK-LETTER CAPITAL H - return rune(0x210c), true + return {'\u210c', 0}, 1, true case "HilbertSpace": // SCRIPT CAPITAL H - return rune(0x210b), true + return {'\u210b', 0}, 1, true case "Hopf": // DOUBLE-STRUCK CAPITAL H - return rune(0x210d), true + return {'\u210d', 0}, 1, true case "HorizontalLine": // BOX DRAWINGS LIGHT HORIZONTAL - return rune(0x2500), true + return {'\u2500', 0}, 1, true case "Hscr": // SCRIPT CAPITAL H - return rune(0x210b), true + return {'\u210b', 0}, 1, true case "Hstrok": // LATIN CAPITAL LETTER H WITH STROKE - return rune(0x0126), true + return {'\u0126', 0}, 1, true case "HumpDownHump": // GEOMETRICALLY EQUIVALENT TO - return rune(0x224e), true + return {'\u224e', 0}, 1, true case "HumpEqual": // DIFFERENCE BETWEEN - return rune(0x224f), true + return {'\u224f', 0}, 1, true } case 'I': switch name { case "IEcy": // CYRILLIC CAPITAL LETTER IE - return rune(0x0415), true + return {'\u0415', 0}, 1, true case "IJlig": // LATIN CAPITAL LIGATURE IJ - return rune(0x0132), true + return {'\u0132', 0}, 1, true case "IOcy": // CYRILLIC CAPITAL LETTER IO - return rune(0x0401), true + return {'\u0401', 0}, 1, true case "Iacgr": // GREEK CAPITAL LETTER IOTA WITH TONOS - return rune(0x038a), true + return {'\u038a', 0}, 1, true case "Iacute": // LATIN CAPITAL LETTER I WITH ACUTE - return rune(0xcd), true + return {'Í', 0}, 1, true case "Icirc": // LATIN CAPITAL LETTER I WITH CIRCUMFLEX - return rune(0xce), true + return {'Î', 0}, 1, true case "Icy": // CYRILLIC CAPITAL LETTER I - return rune(0x0418), true + return {'\u0418', 0}, 1, true case "Idigr": // GREEK CAPITAL LETTER IOTA WITH DIALYTIKA - return rune(0x03aa), true + return {'\u03aa', 0}, 1, true case "Idot": // LATIN CAPITAL LETTER I WITH DOT ABOVE - return rune(0x0130), true + return {'\u0130', 0}, 1, true case "Ifr": // BLACK-LETTER CAPITAL I - return rune(0x2111), true + return {'\u2111', 0}, 1, true case "Igr": // GREEK CAPITAL LETTER IOTA - return rune(0x0399), true + return {'\u0399', 0}, 1, true case "Igrave": // LATIN CAPITAL LETTER I WITH GRAVE - return rune(0xcc), true + return {'Ì', 0}, 1, true case "Im": // BLACK-LETTER CAPITAL I - return rune(0x2111), true + return {'\u2111', 0}, 1, true case "Imacr": // LATIN CAPITAL LETTER I WITH MACRON - return rune(0x012a), true + return {'\u012a', 0}, 1, true case "ImaginaryI": // DOUBLE-STRUCK ITALIC SMALL I - return rune(0x2148), true + return {'\u2148', 0}, 1, true case "Implies": // RIGHTWARDS DOUBLE ARROW - return rune(0x21d2), true + return {'\u21d2', 0}, 1, true case "Int": // DOUBLE INTEGRAL - return rune(0x222c), true + return {'\u222c', 0}, 1, true case "Integral": // INTEGRAL - return rune(0x222b), true + return {'\u222b', 0}, 1, true case "Intersection": // N-ARY INTERSECTION - return rune(0x22c2), true + return {'\u22c2', 0}, 1, true case "InvisibleComma": // INVISIBLE SEPARATOR - return rune(0x2063), true + return {'\u2063', 0}, 1, true case "InvisibleTimes": // INVISIBLE TIMES - return rune(0x2062), true + return {'\u2062', 0}, 1, true case "Iogon": // LATIN CAPITAL LETTER I WITH OGONEK - return rune(0x012e), true + return {'\u012e', 0}, 1, true case "Iopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL I - return rune(0x01d540), true + return {'\U0001d540', 0}, 1, true case "Iota": // GREEK CAPITAL LETTER IOTA - return rune(0x0399), true + return {'\u0399', 0}, 1, true case "Iscr": // SCRIPT CAPITAL I - return rune(0x2110), true + return {'\u2110', 0}, 1, true case "Itilde": // LATIN CAPITAL LETTER I WITH TILDE - return rune(0x0128), true + return {'\u0128', 0}, 1, true case "Iukcy": // CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I - return rune(0x0406), true + return {'\u0406', 0}, 1, true case "Iuml": // LATIN CAPITAL LETTER I WITH DIAERESIS - return rune(0xcf), true + return {'Ï', 0}, 1, true } case 'J': switch name { case "Jcirc": // LATIN CAPITAL LETTER J WITH CIRCUMFLEX - return rune(0x0134), true + return {'\u0134', 0}, 1, true case "Jcy": // CYRILLIC CAPITAL LETTER SHORT I - return rune(0x0419), true + return {'\u0419', 0}, 1, true case "Jfr": // MATHEMATICAL FRAKTUR CAPITAL J - return rune(0x01d50d), true + return {'\U0001d50d', 0}, 1, true case "Jopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL J - return rune(0x01d541), true + return {'\U0001d541', 0}, 1, true case "Jscr": // MATHEMATICAL SCRIPT CAPITAL J - return rune(0x01d4a5), true + return {'\U0001d4a5', 0}, 1, true case "Jsercy": // CYRILLIC CAPITAL LETTER JE - return rune(0x0408), true + return {'\u0408', 0}, 1, true case "Jukcy": // CYRILLIC CAPITAL LETTER UKRAINIAN IE - return rune(0x0404), true + return {'\u0404', 0}, 1, true } case 'K': switch name { case "KHcy": // CYRILLIC CAPITAL LETTER HA - return rune(0x0425), true + return {'\u0425', 0}, 1, true case "KHgr": // GREEK CAPITAL LETTER CHI - return rune(0x03a7), true + return {'\u03a7', 0}, 1, true case "KJcy": // CYRILLIC CAPITAL LETTER KJE - return rune(0x040c), true + return {'\u040c', 0}, 1, true case "Kappa": // GREEK CAPITAL LETTER KAPPA - return rune(0x039a), true + return {'\u039a', 0}, 1, true case "Kcedil": // LATIN CAPITAL LETTER K WITH CEDILLA - return rune(0x0136), true + return {'\u0136', 0}, 1, true case "Kcy": // CYRILLIC CAPITAL LETTER KA - return rune(0x041a), true + return {'\u041a', 0}, 1, true case "Kfr": // MATHEMATICAL FRAKTUR CAPITAL K - return rune(0x01d50e), true + return {'\U0001d50e', 0}, 1, true case "Kgr": // GREEK CAPITAL LETTER KAPPA - return rune(0x039a), true + return {'\u039a', 0}, 1, true case "Kopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL K - return rune(0x01d542), true + return {'\U0001d542', 0}, 1, true case "Kscr": // MATHEMATICAL SCRIPT CAPITAL K - return rune(0x01d4a6), true + return {'\U0001d4a6', 0}, 1, true } case 'L': switch name { case "LJcy": // CYRILLIC CAPITAL LETTER LJE - return rune(0x0409), true + return {'\u0409', 0}, 1, true case "LT": // LESS-THAN SIGN - return rune(0x3c), true + return {'<', 0}, 1, true case "Lacute": // LATIN CAPITAL LETTER L WITH ACUTE - return rune(0x0139), true + return {'\u0139', 0}, 1, true case "Lambda": // GREEK CAPITAL LETTER LAMDA - return rune(0x039b), true + return {'\u039b', 0}, 1, true case "Lang": // MATHEMATICAL LEFT DOUBLE ANGLE BRACKET - return rune(0x27ea), true + return {'\u27ea', 0}, 1, true case "Laplacetrf": // SCRIPT CAPITAL L - return rune(0x2112), true + return {'\u2112', 0}, 1, true case "Larr": // LEFTWARDS TWO HEADED ARROW - return rune(0x219e), true + return {'\u219e', 0}, 1, true case "Lcaron": // LATIN CAPITAL LETTER L WITH CARON - return rune(0x013d), true + return {'\u013d', 0}, 1, true case "Lcedil": // LATIN CAPITAL LETTER L WITH CEDILLA - return rune(0x013b), true + return {'\u013b', 0}, 1, true case "Lcy": // CYRILLIC CAPITAL LETTER EL - return rune(0x041b), true + return {'\u041b', 0}, 1, true case "LeftAngleBracket": // MATHEMATICAL LEFT ANGLE BRACKET - return rune(0x27e8), true + return {'\u27e8', 0}, 1, true case "LeftArrow": // LEFTWARDS ARROW - return rune(0x2190), true + return {'\u2190', 0}, 1, true case "LeftArrowBar": // LEFTWARDS ARROW TO BAR - return rune(0x21e4), true + return {'\u21e4', 0}, 1, true case "LeftArrowRightArrow": // LEFTWARDS ARROW OVER RIGHTWARDS ARROW - return rune(0x21c6), true + return {'\u21c6', 0}, 1, true case "LeftCeiling": // LEFT CEILING - return rune(0x2308), true + return {'\u2308', 0}, 1, true case "LeftDoubleBracket": // MATHEMATICAL LEFT WHITE SQUARE BRACKET - return rune(0x27e6), true + return {'\u27e6', 0}, 1, true case "LeftDownTeeVector": // DOWNWARDS HARPOON WITH BARB LEFT FROM BAR - return rune(0x2961), true + return {'\u2961', 0}, 1, true case "LeftDownVector": // DOWNWARDS HARPOON WITH BARB LEFTWARDS - return rune(0x21c3), true + return {'\u21c3', 0}, 1, true case "LeftDownVectorBar": // DOWNWARDS HARPOON WITH BARB LEFT TO BAR - return rune(0x2959), true + return {'\u2959', 0}, 1, true case "LeftFloor": // LEFT FLOOR - return rune(0x230a), true + return {'\u230a', 0}, 1, true case "LeftRightArrow": // LEFT RIGHT ARROW - return rune(0x2194), true + return {'\u2194', 0}, 1, true case "LeftRightVector": // LEFT BARB UP RIGHT BARB UP HARPOON - return rune(0x294e), true + return {'\u294e', 0}, 1, true case "LeftTee": // LEFT TACK - return rune(0x22a3), true + return {'\u22a3', 0}, 1, true case "LeftTeeArrow": // LEFTWARDS ARROW FROM BAR - return rune(0x21a4), true + return {'\u21a4', 0}, 1, true case "LeftTeeVector": // LEFTWARDS HARPOON WITH BARB UP FROM BAR - return rune(0x295a), true + return {'\u295a', 0}, 1, true case "LeftTriangle": // NORMAL SUBGROUP OF - return rune(0x22b2), true + return {'\u22b2', 0}, 1, true case "LeftTriangleBar": // LEFT TRIANGLE BESIDE VERTICAL BAR - return rune(0x29cf), true + return {'\u29cf', 0}, 1, true case "LeftTriangleEqual": // NORMAL SUBGROUP OF OR EQUAL TO - return rune(0x22b4), true + return {'\u22b4', 0}, 1, true case "LeftUpDownVector": // UP BARB LEFT DOWN BARB LEFT HARPOON - return rune(0x2951), true + return {'\u2951', 0}, 1, true case "LeftUpTeeVector": // UPWARDS HARPOON WITH BARB LEFT FROM BAR - return rune(0x2960), true + return {'\u2960', 0}, 1, true case "LeftUpVector": // UPWARDS HARPOON WITH BARB LEFTWARDS - return rune(0x21bf), true + return {'\u21bf', 0}, 1, true case "LeftUpVectorBar": // UPWARDS HARPOON WITH BARB LEFT TO BAR - return rune(0x2958), true + return {'\u2958', 0}, 1, true case "LeftVector": // LEFTWARDS HARPOON WITH BARB UPWARDS - return rune(0x21bc), true + return {'\u21bc', 0}, 1, true case "LeftVectorBar": // LEFTWARDS HARPOON WITH BARB UP TO BAR - return rune(0x2952), true + return {'\u2952', 0}, 1, true case "Leftarrow": // LEFTWARDS DOUBLE ARROW - return rune(0x21d0), true + return {'\u21d0', 0}, 1, true case "Leftrightarrow": // LEFT RIGHT DOUBLE ARROW - return rune(0x21d4), true + return {'\u21d4', 0}, 1, true case "LessEqualGreater": // LESS-THAN EQUAL TO OR GREATER-THAN - return rune(0x22da), true + return {'\u22da', 0}, 1, true case "LessFullEqual": // LESS-THAN OVER EQUAL TO - return rune(0x2266), true + return {'\u2266', 0}, 1, true case "LessGreater": // LESS-THAN OR GREATER-THAN - return rune(0x2276), true + return {'\u2276', 0}, 1, true case "LessLess": // DOUBLE NESTED LESS-THAN - return rune(0x2aa1), true + return {'\u2aa1', 0}, 1, true case "LessSlantEqual": // LESS-THAN OR SLANTED EQUAL TO - return rune(0x2a7d), true + return {'\u2a7d', 0}, 1, true case "LessTilde": // LESS-THAN OR EQUIVALENT TO - return rune(0x2272), true + return {'\u2272', 0}, 1, true case "Lfr": // MATHEMATICAL FRAKTUR CAPITAL L - return rune(0x01d50f), true + return {'\U0001d50f', 0}, 1, true case "Lgr": // GREEK CAPITAL LETTER LAMDA - return rune(0x039b), true + return {'\u039b', 0}, 1, true case "Ll": // VERY MUCH LESS-THAN - return rune(0x22d8), true + return {'\u22d8', 0}, 1, true case "Lleftarrow": // LEFTWARDS TRIPLE ARROW - return rune(0x21da), true + return {'\u21da', 0}, 1, true case "Lmidot": // LATIN CAPITAL LETTER L WITH MIDDLE DOT - return rune(0x013f), true + return {'\u013f', 0}, 1, true case "LongLeftArrow": // LONG LEFTWARDS ARROW - return rune(0x27f5), true + return {'\u27f5', 0}, 1, true case "LongLeftRightArrow": // LONG LEFT RIGHT ARROW - return rune(0x27f7), true + return {'\u27f7', 0}, 1, true case "LongRightArrow": // LONG RIGHTWARDS ARROW - return rune(0x27f6), true + return {'\u27f6', 0}, 1, true case "Longleftarrow": // LONG LEFTWARDS DOUBLE ARROW - return rune(0x27f8), true + return {'\u27f8', 0}, 1, true case "Longleftrightarrow": // LONG LEFT RIGHT DOUBLE ARROW - return rune(0x27fa), true + return {'\u27fa', 0}, 1, true case "Longrightarrow": // LONG RIGHTWARDS DOUBLE ARROW - return rune(0x27f9), true + return {'\u27f9', 0}, 1, true case "Lopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL L - return rune(0x01d543), true + return {'\U0001d543', 0}, 1, true case "LowerLeftArrow": // SOUTH WEST ARROW - return rune(0x2199), true + return {'\u2199', 0}, 1, true case "LowerRightArrow": // SOUTH EAST ARROW - return rune(0x2198), true + return {'\u2198', 0}, 1, true case "Lscr": // SCRIPT CAPITAL L - return rune(0x2112), true + return {'\u2112', 0}, 1, true case "Lsh": // UPWARDS ARROW WITH TIP LEFTWARDS - return rune(0x21b0), true + return {'\u21b0', 0}, 1, true case "Lstrok": // LATIN CAPITAL LETTER L WITH STROKE - return rune(0x0141), true + return {'\u0141', 0}, 1, true case "Lt": // MUCH LESS-THAN - return rune(0x226a), true + return {'\u226a', 0}, 1, true case "Ltbar": // DOUBLE NESTED LESS-THAN WITH UNDERBAR - return rune(0x2aa3), true + return {'\u2aa3', 0}, 1, true } case 'M': switch name { case "Map": // RIGHTWARDS TWO-HEADED ARROW FROM BAR - return rune(0x2905), true + return {'\u2905', 0}, 1, true case "Mapfrom": // LEFTWARDS DOUBLE ARROW FROM BAR - return rune(0x2906), true + return {'\u2906', 0}, 1, true case "Mapto": // RIGHTWARDS DOUBLE ARROW FROM BAR - return rune(0x2907), true + return {'\u2907', 0}, 1, true case "Mcy": // CYRILLIC CAPITAL LETTER EM - return rune(0x041c), true + return {'\u041c', 0}, 1, true case "MediumSpace": // MEDIUM MATHEMATICAL SPACE - return rune(0x205f), true + return {'\u205f', 0}, 1, true case "Mellintrf": // SCRIPT CAPITAL M - return rune(0x2133), true + return {'\u2133', 0}, 1, true case "Mfr": // MATHEMATICAL FRAKTUR CAPITAL M - return rune(0x01d510), true + return {'\U0001d510', 0}, 1, true case "Mgr": // GREEK CAPITAL LETTER MU - return rune(0x039c), true + return {'\u039c', 0}, 1, true case "MinusPlus": // MINUS-OR-PLUS SIGN - return rune(0x2213), true + return {'\u2213', 0}, 1, true case "Mopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL M - return rune(0x01d544), true + return {'\U0001d544', 0}, 1, true case "Mscr": // SCRIPT CAPITAL M - return rune(0x2133), true + return {'\u2133', 0}, 1, true case "Mu": // GREEK CAPITAL LETTER MU - return rune(0x039c), true + return {'\u039c', 0}, 1, true } case 'N': switch name { case "NJcy": // CYRILLIC CAPITAL LETTER NJE - return rune(0x040a), true + return {'\u040a', 0}, 1, true case "Nacute": // LATIN CAPITAL LETTER N WITH ACUTE - return rune(0x0143), true + return {'\u0143', 0}, 1, true case "Ncaron": // LATIN CAPITAL LETTER N WITH CARON - return rune(0x0147), true + return {'\u0147', 0}, 1, true case "Ncedil": // LATIN CAPITAL LETTER N WITH CEDILLA - return rune(0x0145), true + return {'\u0145', 0}, 1, true case "Ncy": // CYRILLIC CAPITAL LETTER EN - return rune(0x041d), true + return {'\u041d', 0}, 1, true case "NegativeMediumSpace": // ZERO WIDTH SPACE - return rune(0x200b), true + return {'\u200b', 0}, 1, true case "NegativeThickSpace": // ZERO WIDTH SPACE - return rune(0x200b), true + return {'\u200b', 0}, 1, true case "NegativeThinSpace": // ZERO WIDTH SPACE - return rune(0x200b), true + return {'\u200b', 0}, 1, true case "NegativeVeryThinSpace": // ZERO WIDTH SPACE - return rune(0x200b), true + return {'\u200b', 0}, 1, true case "NestedGreaterGreater": // MUCH GREATER-THAN - return rune(0x226b), true + return {'\u226b', 0}, 1, true case "NestedLessLess": // MUCH LESS-THAN - return rune(0x226a), true + return {'\u226a', 0}, 1, true case "NewLine": // LINE FEED (LF) - return rune(0x0a), true + return {'\n', 0}, 1, true case "Nfr": // MATHEMATICAL FRAKTUR CAPITAL N - return rune(0x01d511), true + return {'\U0001d511', 0}, 1, true case "Ngr": // GREEK CAPITAL LETTER NU - return rune(0x039d), true + return {'\u039d', 0}, 1, true case "NoBreak": // WORD JOINER - return rune(0x2060), true + return {'\u2060', 0}, 1, true case "NonBreakingSpace": // NO-BREAK SPACE - return rune(0xa0), true + return {'\u00a0', 0}, 1, true case "Nopf": // DOUBLE-STRUCK CAPITAL N - return rune(0x2115), true + return {'\u2115', 0}, 1, true case "Not": // DOUBLE STROKE NOT SIGN - return rune(0x2aec), true + return {'\u2aec', 0}, 1, true case "NotCongruent": // NOT IDENTICAL TO - return rune(0x2262), true + return {'\u2262', 0}, 1, true case "NotCupCap": // NOT EQUIVALENT TO - return rune(0x226d), true + return {'\u226d', 0}, 1, true case "NotDoubleVerticalBar": // NOT PARALLEL TO - return rune(0x2226), true + return {'\u2226', 0}, 1, true case "NotElement": // NOT AN ELEMENT OF - return rune(0x2209), true + return {'\u2209', 0}, 1, true case "NotEqual": // NOT EQUAL TO - return rune(0x2260), true + return {'\u2260', 0}, 1, true case "NotEqualTilde": // MINUS TILDE with slash - return rune(0x2242), true + return {'\u2242', '\u0338'}, 2, true case "NotExists": // THERE DOES NOT EXIST - return rune(0x2204), true + return {'\u2204', 0}, 1, true case "NotGreater": // NOT GREATER-THAN - return rune(0x226f), true + return {'\u226f', 0}, 1, true case "NotGreaterEqual": // NEITHER GREATER-THAN NOR EQUAL TO - return rune(0x2271), true + return {'\u2271', 0}, 1, true case "NotGreaterFullEqual": // GREATER-THAN OVER EQUAL TO with slash - return rune(0x2267), true + return {'\u2267', '\u0338'}, 2, true case "NotGreaterGreater": // MUCH GREATER THAN with slash - return rune(0x226b), true + return {'\u226b', '\u0338'}, 2, true case "NotGreaterLess": // NEITHER GREATER-THAN NOR LESS-THAN - return rune(0x2279), true + return {'\u2279', 0}, 1, true case "NotGreaterSlantEqual": // GREATER-THAN OR SLANTED EQUAL TO with slash - return rune(0x2a7e), true + return {'\u2a7e', '\u0338'}, 2, true case "NotGreaterTilde": // NEITHER GREATER-THAN NOR EQUIVALENT TO - return rune(0x2275), true + return {'\u2275', 0}, 1, true case "NotHumpDownHump": // GEOMETRICALLY EQUIVALENT TO with slash - return rune(0x224e), true + return {'\u224e', '\u0338'}, 2, true case "NotHumpEqual": // DIFFERENCE BETWEEN with slash - return rune(0x224f), true + return {'\u224f', '\u0338'}, 2, true case "NotLeftTriangle": // NOT NORMAL SUBGROUP OF - return rune(0x22ea), true + return {'\u22ea', 0}, 1, true case "NotLeftTriangleBar": // LEFT TRIANGLE BESIDE VERTICAL BAR with slash - return rune(0x29cf), true + return {'\u29cf', '\u0338'}, 2, true case "NotLeftTriangleEqual": // NOT NORMAL SUBGROUP OF OR EQUAL TO - return rune(0x22ec), true + return {'\u22ec', 0}, 1, true case "NotLess": // NOT LESS-THAN - return rune(0x226e), true + return {'\u226e', 0}, 1, true case "NotLessEqual": // NEITHER LESS-THAN NOR EQUAL TO - return rune(0x2270), true + return {'\u2270', 0}, 1, true case "NotLessGreater": // NEITHER LESS-THAN NOR GREATER-THAN - return rune(0x2278), true + return {'\u2278', 0}, 1, true case "NotLessLess": // MUCH LESS THAN with slash - return rune(0x226a), true + return {'\u226a', '\u0338'}, 2, true case "NotLessSlantEqual": // LESS-THAN OR SLANTED EQUAL TO with slash - return rune(0x2a7d), true + return {'\u2a7d', '\u0338'}, 2, true case "NotLessTilde": // NEITHER LESS-THAN NOR EQUIVALENT TO - return rune(0x2274), true + return {'\u2274', 0}, 1, true case "NotNestedGreaterGreater": // DOUBLE NESTED GREATER-THAN with slash - return rune(0x2aa2), true + return {'\u2aa2', '\u0338'}, 2, true case "NotNestedLessLess": // DOUBLE NESTED LESS-THAN with slash - return rune(0x2aa1), true + return {'\u2aa1', '\u0338'}, 2, true case "NotPrecedes": // DOES NOT PRECEDE - return rune(0x2280), true + return {'\u2280', 0}, 1, true case "NotPrecedesEqual": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash - return rune(0x2aaf), true + return {'\u2aaf', '\u0338'}, 2, true case "NotPrecedesSlantEqual": // DOES NOT PRECEDE OR EQUAL - return rune(0x22e0), true + return {'\u22e0', 0}, 1, true case "NotReverseElement": // DOES NOT CONTAIN AS MEMBER - return rune(0x220c), true + return {'\u220c', 0}, 1, true case "NotRightTriangle": // DOES NOT CONTAIN AS NORMAL SUBGROUP - return rune(0x22eb), true + return {'\u22eb', 0}, 1, true case "NotRightTriangleBar": // VERTICAL BAR BESIDE RIGHT TRIANGLE with slash - return rune(0x29d0), true + return {'\u29d0', '\u0338'}, 2, true case "NotRightTriangleEqual": // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL - return rune(0x22ed), true + return {'\u22ed', 0}, 1, true case "NotSquareSubset": // SQUARE IMAGE OF with slash - return rune(0x228f), true + return {'\u228f', '\u0338'}, 2, true case "NotSquareSubsetEqual": // NOT SQUARE IMAGE OF OR EQUAL TO - return rune(0x22e2), true + return {'\u22e2', 0}, 1, true case "NotSquareSuperset": // SQUARE ORIGINAL OF with slash - return rune(0x2290), true + return {'\u2290', '\u0338'}, 2, true case "NotSquareSupersetEqual": // NOT SQUARE ORIGINAL OF OR EQUAL TO - return rune(0x22e3), true + return {'\u22e3', 0}, 1, true case "NotSubset": // SUBSET OF with vertical line - return rune(0x2282), true + return {'\u2282', '\u20d2'}, 2, true case "NotSubsetEqual": // NEITHER A SUBSET OF NOR EQUAL TO - return rune(0x2288), true + return {'\u2288', 0}, 1, true case "NotSucceeds": // DOES NOT SUCCEED - return rune(0x2281), true + return {'\u2281', 0}, 1, true case "NotSucceedsEqual": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash - return rune(0x2ab0), true + return {'\u2ab0', '\u0338'}, 2, true case "NotSucceedsSlantEqual": // DOES NOT SUCCEED OR EQUAL - return rune(0x22e1), true + return {'\u22e1', 0}, 1, true case "NotSucceedsTilde": // SUCCEEDS OR EQUIVALENT TO with slash - return rune(0x227f), true + return {'\u227f', '\u0338'}, 2, true case "NotSuperset": // SUPERSET OF with vertical line - return rune(0x2283), true + return {'\u2283', '\u20d2'}, 2, true case "NotSupersetEqual": // NEITHER A SUPERSET OF NOR EQUAL TO - return rune(0x2289), true + return {'\u2289', 0}, 1, true case "NotTilde": // NOT TILDE - return rune(0x2241), true + return {'\u2241', 0}, 1, true case "NotTildeEqual": // NOT ASYMPTOTICALLY EQUAL TO - return rune(0x2244), true + return {'\u2244', 0}, 1, true case "NotTildeFullEqual": // NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO - return rune(0x2247), true + return {'\u2247', 0}, 1, true case "NotTildeTilde": // NOT ALMOST EQUAL TO - return rune(0x2249), true + return {'\u2249', 0}, 1, true case "NotVerticalBar": // DOES NOT DIVIDE - return rune(0x2224), true + return {'\u2224', 0}, 1, true case "Nscr": // MATHEMATICAL SCRIPT CAPITAL N - return rune(0x01d4a9), true + return {'\U0001d4a9', 0}, 1, true case "Ntilde": // LATIN CAPITAL LETTER N WITH TILDE - return rune(0xd1), true + return {'Ñ', 0}, 1, true case "Nu": // GREEK CAPITAL LETTER NU - return rune(0x039d), true + return {'\u039d', 0}, 1, true } case 'O': switch name { case "OElig": // LATIN CAPITAL LIGATURE OE - return rune(0x0152), true + return {'\u0152', 0}, 1, true case "OHacgr": // GREEK CAPITAL LETTER OMEGA WITH TONOS - return rune(0x038f), true + return {'\u038f', 0}, 1, true case "OHgr": // GREEK CAPITAL LETTER OMEGA - return rune(0x03a9), true + return {'\u03a9', 0}, 1, true case "Oacgr": // GREEK CAPITAL LETTER OMICRON WITH TONOS - return rune(0x038c), true + return {'\u038c', 0}, 1, true case "Oacute": // LATIN CAPITAL LETTER O WITH ACUTE - return rune(0xd3), true + return {'Ó', 0}, 1, true case "Ocirc": // LATIN CAPITAL LETTER O WITH CIRCUMFLEX - return rune(0xd4), true + return {'Ô', 0}, 1, true case "Ocy": // CYRILLIC CAPITAL LETTER O - return rune(0x041e), true + return {'\u041e', 0}, 1, true case "Odblac": // LATIN CAPITAL LETTER O WITH DOUBLE ACUTE - return rune(0x0150), true + return {'\u0150', 0}, 1, true case "Ofr": // MATHEMATICAL FRAKTUR CAPITAL O - return rune(0x01d512), true + return {'\U0001d512', 0}, 1, true case "Ogr": // GREEK CAPITAL LETTER OMICRON - return rune(0x039f), true + return {'\u039f', 0}, 1, true case "Ograve": // LATIN CAPITAL LETTER O WITH GRAVE - return rune(0xd2), true + return {'Ò', 0}, 1, true case "Omacr": // LATIN CAPITAL LETTER O WITH MACRON - return rune(0x014c), true + return {'\u014c', 0}, 1, true case "Omega": // GREEK CAPITAL LETTER OMEGA - return rune(0x03a9), true + return {'\u03a9', 0}, 1, true case "Omicron": // GREEK CAPITAL LETTER OMICRON - return rune(0x039f), true + return {'\u039f', 0}, 1, true case "Oopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL O - return rune(0x01d546), true + return {'\U0001d546', 0}, 1, true case "OpenCurlyDoubleQuote": // LEFT DOUBLE QUOTATION MARK - return rune(0x201c), true + return {'\u201c', 0}, 1, true case "OpenCurlyQuote": // LEFT SINGLE QUOTATION MARK - return rune(0x2018), true + return {'\u2018', 0}, 1, true case "Or": // DOUBLE LOGICAL OR - return rune(0x2a54), true + return {'\u2a54', 0}, 1, true case "Oscr": // MATHEMATICAL SCRIPT CAPITAL O - return rune(0x01d4aa), true + return {'\U0001d4aa', 0}, 1, true case "Oslash": // LATIN CAPITAL LETTER O WITH STROKE - return rune(0xd8), true + return {'Ø', 0}, 1, true case "Otilde": // LATIN CAPITAL LETTER O WITH TILDE - return rune(0xd5), true + return {'Õ', 0}, 1, true case "Otimes": // MULTIPLICATION SIGN IN DOUBLE CIRCLE - return rune(0x2a37), true + return {'\u2a37', 0}, 1, true case "Ouml": // LATIN CAPITAL LETTER O WITH DIAERESIS - return rune(0xd6), true + return {'Ö', 0}, 1, true case "OverBar": // OVERLINE - return rune(0x203e), true + return {'\u203e', 0}, 1, true case "OverBrace": // TOP CURLY BRACKET - return rune(0x23de), true + return {'\u23de', 0}, 1, true case "OverBracket": // TOP SQUARE BRACKET - return rune(0x23b4), true + return {'\u23b4', 0}, 1, true case "OverParenthesis": // TOP PARENTHESIS - return rune(0x23dc), true + return {'\u23dc', 0}, 1, true } case 'P': switch name { case "PHgr": // GREEK CAPITAL LETTER PHI - return rune(0x03a6), true + return {'\u03a6', 0}, 1, true case "PSgr": // GREEK CAPITAL LETTER PSI - return rune(0x03a8), true + return {'\u03a8', 0}, 1, true case "PartialD": // PARTIAL DIFFERENTIAL - return rune(0x2202), true + return {'\u2202', 0}, 1, true case "Pcy": // CYRILLIC CAPITAL LETTER PE - return rune(0x041f), true + return {'\u041f', 0}, 1, true case "Pfr": // MATHEMATICAL FRAKTUR CAPITAL P - return rune(0x01d513), true + return {'\U0001d513', 0}, 1, true case "Pgr": // GREEK CAPITAL LETTER PI - return rune(0x03a0), true + return {'\u03a0', 0}, 1, true case "Phi": // GREEK CAPITAL LETTER PHI - return rune(0x03a6), true + return {'\u03a6', 0}, 1, true case "Pi": // GREEK CAPITAL LETTER PI - return rune(0x03a0), true + return {'\u03a0', 0}, 1, true case "PlusMinus": // PLUS-MINUS SIGN - return rune(0xb1), true + return {'±', 0}, 1, true case "Poincareplane": // BLACK-LETTER CAPITAL H - return rune(0x210c), true + return {'\u210c', 0}, 1, true case "Popf": // DOUBLE-STRUCK CAPITAL P - return rune(0x2119), true + return {'\u2119', 0}, 1, true case "Pr": // DOUBLE PRECEDES - return rune(0x2abb), true + return {'\u2abb', 0}, 1, true case "Precedes": // PRECEDES - return rune(0x227a), true + return {'\u227a', 0}, 1, true case "PrecedesEqual": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN - return rune(0x2aaf), true + return {'\u2aaf', 0}, 1, true case "PrecedesSlantEqual": // PRECEDES OR EQUAL TO - return rune(0x227c), true + return {'\u227c', 0}, 1, true case "PrecedesTilde": // PRECEDES OR EQUIVALENT TO - return rune(0x227e), true + return {'\u227e', 0}, 1, true case "Prime": // DOUBLE PRIME - return rune(0x2033), true + return {'\u2033', 0}, 1, true case "Product": // N-ARY PRODUCT - return rune(0x220f), true + return {'\u220f', 0}, 1, true case "Proportion": // PROPORTION - return rune(0x2237), true + return {'\u2237', 0}, 1, true case "Proportional": // PROPORTIONAL TO - return rune(0x221d), true + return {'\u221d', 0}, 1, true case "Pscr": // MATHEMATICAL SCRIPT CAPITAL P - return rune(0x01d4ab), true + return {'\U0001d4ab', 0}, 1, true case "Psi": // GREEK CAPITAL LETTER PSI - return rune(0x03a8), true + return {'\u03a8', 0}, 1, true } case 'Q': switch name { case "QUOT": // QUOTATION MARK - return rune(0x22), true + return {'"', 0}, 1, true case "Qfr": // MATHEMATICAL FRAKTUR CAPITAL Q - return rune(0x01d514), true + return {'\U0001d514', 0}, 1, true case "Qopf": // DOUBLE-STRUCK CAPITAL Q - return rune(0x211a), true + return {'\u211a', 0}, 1, true case "Qscr": // MATHEMATICAL SCRIPT CAPITAL Q - return rune(0x01d4ac), true + return {'\U0001d4ac', 0}, 1, true } case 'R': switch name { case "RBarr": // RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW - return rune(0x2910), true + return {'\u2910', 0}, 1, true case "REG": // REGISTERED SIGN - return rune(0xae), true + return {'®', 0}, 1, true case "Racute": // LATIN CAPITAL LETTER R WITH ACUTE - return rune(0x0154), true + return {'\u0154', 0}, 1, true case "Rang": // MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET - return rune(0x27eb), true + return {'\u27eb', 0}, 1, true case "Rarr": // RIGHTWARDS TWO HEADED ARROW - return rune(0x21a0), true + return {'\u21a0', 0}, 1, true case "Rarrtl": // RIGHTWARDS TWO-HEADED ARROW WITH TAIL - return rune(0x2916), true + return {'\u2916', 0}, 1, true case "Rcaron": // LATIN CAPITAL LETTER R WITH CARON - return rune(0x0158), true + return {'\u0158', 0}, 1, true case "Rcedil": // LATIN CAPITAL LETTER R WITH CEDILLA - return rune(0x0156), true + return {'\u0156', 0}, 1, true case "Rcy": // CYRILLIC CAPITAL LETTER ER - return rune(0x0420), true + return {'\u0420', 0}, 1, true case "Re": // BLACK-LETTER CAPITAL R - return rune(0x211c), true + return {'\u211c', 0}, 1, true case "ReverseElement": // CONTAINS AS MEMBER - return rune(0x220b), true + return {'\u220b', 0}, 1, true case "ReverseEquilibrium": // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON - return rune(0x21cb), true + return {'\u21cb', 0}, 1, true case "ReverseUpEquilibrium": // DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT - return rune(0x296f), true + return {'\u296f', 0}, 1, true case "Rfr": // BLACK-LETTER CAPITAL R - return rune(0x211c), true + return {'\u211c', 0}, 1, true case "Rgr": // GREEK CAPITAL LETTER RHO - return rune(0x03a1), true + return {'\u03a1', 0}, 1, true case "Rho": // GREEK CAPITAL LETTER RHO - return rune(0x03a1), true + return {'\u03a1', 0}, 1, true case "RightAngleBracket": // MATHEMATICAL RIGHT ANGLE BRACKET - return rune(0x27e9), true + return {'\u27e9', 0}, 1, true case "RightArrow": // RIGHTWARDS ARROW - return rune(0x2192), true + return {'\u2192', 0}, 1, true case "RightArrowBar": // RIGHTWARDS ARROW TO BAR - return rune(0x21e5), true + return {'\u21e5', 0}, 1, true case "RightArrowLeftArrow": // RIGHTWARDS ARROW OVER LEFTWARDS ARROW - return rune(0x21c4), true + return {'\u21c4', 0}, 1, true case "RightCeiling": // RIGHT CEILING - return rune(0x2309), true + return {'\u2309', 0}, 1, true case "RightDoubleBracket": // MATHEMATICAL RIGHT WHITE SQUARE BRACKET - return rune(0x27e7), true + return {'\u27e7', 0}, 1, true case "RightDownTeeVector": // DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR - return rune(0x295d), true + return {'\u295d', 0}, 1, true case "RightDownVector": // DOWNWARDS HARPOON WITH BARB RIGHTWARDS - return rune(0x21c2), true + return {'\u21c2', 0}, 1, true case "RightDownVectorBar": // DOWNWARDS HARPOON WITH BARB RIGHT TO BAR - return rune(0x2955), true + return {'\u2955', 0}, 1, true case "RightFloor": // RIGHT FLOOR - return rune(0x230b), true + return {'\u230b', 0}, 1, true case "RightTee": // RIGHT TACK - return rune(0x22a2), true + return {'\u22a2', 0}, 1, true case "RightTeeArrow": // RIGHTWARDS ARROW FROM BAR - return rune(0x21a6), true + return {'\u21a6', 0}, 1, true case "RightTeeVector": // RIGHTWARDS HARPOON WITH BARB UP FROM BAR - return rune(0x295b), true + return {'\u295b', 0}, 1, true case "RightTriangle": // CONTAINS AS NORMAL SUBGROUP - return rune(0x22b3), true + return {'\u22b3', 0}, 1, true case "RightTriangleBar": // VERTICAL BAR BESIDE RIGHT TRIANGLE - return rune(0x29d0), true + return {'\u29d0', 0}, 1, true case "RightTriangleEqual": // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO - return rune(0x22b5), true + return {'\u22b5', 0}, 1, true case "RightUpDownVector": // UP BARB RIGHT DOWN BARB RIGHT HARPOON - return rune(0x294f), true + return {'\u294f', 0}, 1, true case "RightUpTeeVector": // UPWARDS HARPOON WITH BARB RIGHT FROM BAR - return rune(0x295c), true + return {'\u295c', 0}, 1, true case "RightUpVector": // UPWARDS HARPOON WITH BARB RIGHTWARDS - return rune(0x21be), true + return {'\u21be', 0}, 1, true case "RightUpVectorBar": // UPWARDS HARPOON WITH BARB RIGHT TO BAR - return rune(0x2954), true + return {'\u2954', 0}, 1, true case "RightVector": // RIGHTWARDS HARPOON WITH BARB UPWARDS - return rune(0x21c0), true + return {'\u21c0', 0}, 1, true case "RightVectorBar": // RIGHTWARDS HARPOON WITH BARB UP TO BAR - return rune(0x2953), true + return {'\u2953', 0}, 1, true case "Rightarrow": // RIGHTWARDS DOUBLE ARROW - return rune(0x21d2), true + return {'\u21d2', 0}, 1, true case "Ropf": // DOUBLE-STRUCK CAPITAL R - return rune(0x211d), true + return {'\u211d', 0}, 1, true case "RoundImplies": // RIGHT DOUBLE ARROW WITH ROUNDED HEAD - return rune(0x2970), true + return {'\u2970', 0}, 1, true case "Rrightarrow": // RIGHTWARDS TRIPLE ARROW - return rune(0x21db), true + return {'\u21db', 0}, 1, true case "Rscr": // SCRIPT CAPITAL R - return rune(0x211b), true + return {'\u211b', 0}, 1, true case "Rsh": // UPWARDS ARROW WITH TIP RIGHTWARDS - return rune(0x21b1), true + return {'\u21b1', 0}, 1, true case "RuleDelayed": // RULE-DELAYED - return rune(0x29f4), true + return {'\u29f4', 0}, 1, true } case 'S': switch name { case "SHCHcy": // CYRILLIC CAPITAL LETTER SHCHA - return rune(0x0429), true + return {'\u0429', 0}, 1, true case "SHcy": // CYRILLIC CAPITAL LETTER SHA - return rune(0x0428), true + return {'\u0428', 0}, 1, true case "SOFTcy": // CYRILLIC CAPITAL LETTER SOFT SIGN - return rune(0x042c), true + return {'\u042c', 0}, 1, true case "Sacute": // LATIN CAPITAL LETTER S WITH ACUTE - return rune(0x015a), true + return {'\u015a', 0}, 1, true case "Sc": // DOUBLE SUCCEEDS - return rune(0x2abc), true + return {'\u2abc', 0}, 1, true case "Scaron": // LATIN CAPITAL LETTER S WITH CARON - return rune(0x0160), true + return {'\u0160', 0}, 1, true case "Scedil": // LATIN CAPITAL LETTER S WITH CEDILLA - return rune(0x015e), true + return {'\u015e', 0}, 1, true case "Scirc": // LATIN CAPITAL LETTER S WITH CIRCUMFLEX - return rune(0x015c), true + return {'\u015c', 0}, 1, true case "Scy": // CYRILLIC CAPITAL LETTER ES - return rune(0x0421), true + return {'\u0421', 0}, 1, true case "Sfr": // MATHEMATICAL FRAKTUR CAPITAL S - return rune(0x01d516), true + return {'\U0001d516', 0}, 1, true case "Sgr": // GREEK CAPITAL LETTER SIGMA - return rune(0x03a3), true + return {'\u03a3', 0}, 1, true case "ShortDownArrow": // DOWNWARDS ARROW - return rune(0x2193), true + return {'\u2193', 0}, 1, true case "ShortLeftArrow": // LEFTWARDS ARROW - return rune(0x2190), true + return {'\u2190', 0}, 1, true case "ShortRightArrow": // RIGHTWARDS ARROW - return rune(0x2192), true + return {'\u2192', 0}, 1, true case "ShortUpArrow": // UPWARDS ARROW - return rune(0x2191), true + return {'\u2191', 0}, 1, true case "Sigma": // GREEK CAPITAL LETTER SIGMA - return rune(0x03a3), true + return {'\u03a3', 0}, 1, true case "SmallCircle": // RING OPERATOR - return rune(0x2218), true + return {'\u2218', 0}, 1, true case "Sopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL S - return rune(0x01d54a), true + return {'\U0001d54a', 0}, 1, true case "Sqrt": // SQUARE ROOT - return rune(0x221a), true + return {'\u221a', 0}, 1, true case "Square": // WHITE SQUARE - return rune(0x25a1), true + return {'\u25a1', 0}, 1, true case "SquareIntersection": // SQUARE CAP - return rune(0x2293), true + return {'\u2293', 0}, 1, true case "SquareSubset": // SQUARE IMAGE OF - return rune(0x228f), true + return {'\u228f', 0}, 1, true case "SquareSubsetEqual": // SQUARE IMAGE OF OR EQUAL TO - return rune(0x2291), true + return {'\u2291', 0}, 1, true case "SquareSuperset": // SQUARE ORIGINAL OF - return rune(0x2290), true + return {'\u2290', 0}, 1, true case "SquareSupersetEqual": // SQUARE ORIGINAL OF OR EQUAL TO - return rune(0x2292), true + return {'\u2292', 0}, 1, true case "SquareUnion": // SQUARE CUP - return rune(0x2294), true + return {'\u2294', 0}, 1, true case "Sscr": // MATHEMATICAL SCRIPT CAPITAL S - return rune(0x01d4ae), true + return {'\U0001d4ae', 0}, 1, true case "Star": // STAR OPERATOR - return rune(0x22c6), true + return {'\u22c6', 0}, 1, true case "Sub": // DOUBLE SUBSET - return rune(0x22d0), true + return {'\u22d0', 0}, 1, true case "Subset": // DOUBLE SUBSET - return rune(0x22d0), true + return {'\u22d0', 0}, 1, true case "SubsetEqual": // SUBSET OF OR EQUAL TO - return rune(0x2286), true + return {'\u2286', 0}, 1, true case "Succeeds": // SUCCEEDS - return rune(0x227b), true + return {'\u227b', 0}, 1, true case "SucceedsEqual": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN - return rune(0x2ab0), true + return {'\u2ab0', 0}, 1, true case "SucceedsSlantEqual": // SUCCEEDS OR EQUAL TO - return rune(0x227d), true + return {'\u227d', 0}, 1, true case "SucceedsTilde": // SUCCEEDS OR EQUIVALENT TO - return rune(0x227f), true + return {'\u227f', 0}, 1, true case "SuchThat": // CONTAINS AS MEMBER - return rune(0x220b), true + return {'\u220b', 0}, 1, true case "Sum": // N-ARY SUMMATION - return rune(0x2211), true + return {'\u2211', 0}, 1, true case "Sup": // DOUBLE SUPERSET - return rune(0x22d1), true + return {'\u22d1', 0}, 1, true case "Superset": // SUPERSET OF - return rune(0x2283), true + return {'\u2283', 0}, 1, true case "SupersetEqual": // SUPERSET OF OR EQUAL TO - return rune(0x2287), true + return {'\u2287', 0}, 1, true case "Supset": // DOUBLE SUPERSET - return rune(0x22d1), true + return {'\u22d1', 0}, 1, true } case 'T': switch name { case "THORN": // LATIN CAPITAL LETTER THORN - return rune(0xde), true + return {'Þ', 0}, 1, true case "THgr": // GREEK CAPITAL LETTER THETA - return rune(0x0398), true + return {'\u0398', 0}, 1, true case "TRADE": // TRADE MARK SIGN - return rune(0x2122), true + return {'\u2122', 0}, 1, true case "TSHcy": // CYRILLIC CAPITAL LETTER TSHE - return rune(0x040b), true + return {'\u040b', 0}, 1, true case "TScy": // CYRILLIC CAPITAL LETTER TSE - return rune(0x0426), true + return {'\u0426', 0}, 1, true case "Tab": // CHARACTER TABULATION - return rune(0x09), true + return {'\t', 0}, 1, true case "Tau": // GREEK CAPITAL LETTER TAU - return rune(0x03a4), true + return {'\u03a4', 0}, 1, true case "Tcaron": // LATIN CAPITAL LETTER T WITH CARON - return rune(0x0164), true + return {'\u0164', 0}, 1, true case "Tcedil": // LATIN CAPITAL LETTER T WITH CEDILLA - return rune(0x0162), true + return {'\u0162', 0}, 1, true case "Tcy": // CYRILLIC CAPITAL LETTER TE - return rune(0x0422), true + return {'\u0422', 0}, 1, true case "Tfr": // MATHEMATICAL FRAKTUR CAPITAL T - return rune(0x01d517), true + return {'\U0001d517', 0}, 1, true case "Tgr": // GREEK CAPITAL LETTER TAU - return rune(0x03a4), true + return {'\u03a4', 0}, 1, true case "Therefore": // THEREFORE - return rune(0x2234), true + return {'\u2234', 0}, 1, true case "Theta": // GREEK CAPITAL LETTER THETA - return rune(0x0398), true + return {'\u0398', 0}, 1, true case "Thetav": // GREEK CAPITAL THETA SYMBOL - return rune(0x03f4), true + return {'\u03f4', 0}, 1, true case "ThickSpace": // space of width 5/18 em - return rune(0x205f), true + return {'\u205f', '\u200a'}, 2, true case "ThinSpace": // THIN SPACE - return rune(0x2009), true + return {'\u2009', 0}, 1, true case "Tilde": // TILDE OPERATOR - return rune(0x223c), true + return {'\u223c', 0}, 1, true case "TildeEqual": // ASYMPTOTICALLY EQUAL TO - return rune(0x2243), true + return {'\u2243', 0}, 1, true case "TildeFullEqual": // APPROXIMATELY EQUAL TO - return rune(0x2245), true + return {'\u2245', 0}, 1, true case "TildeTilde": // ALMOST EQUAL TO - return rune(0x2248), true + return {'\u2248', 0}, 1, true case "Topf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL T - return rune(0x01d54b), true + return {'\U0001d54b', 0}, 1, true case "TripleDot": // COMBINING THREE DOTS ABOVE - return rune(0x20db), true + return {'\u20db', 0}, 1, true case "Tscr": // MATHEMATICAL SCRIPT CAPITAL T - return rune(0x01d4af), true + return {'\U0001d4af', 0}, 1, true case "Tstrok": // LATIN CAPITAL LETTER T WITH STROKE - return rune(0x0166), true + return {'\u0166', 0}, 1, true } case 'U': switch name { case "Uacgr": // GREEK CAPITAL LETTER UPSILON WITH TONOS - return rune(0x038e), true + return {'\u038e', 0}, 1, true case "Uacute": // LATIN CAPITAL LETTER U WITH ACUTE - return rune(0xda), true + return {'Ú', 0}, 1, true case "Uarr": // UPWARDS TWO HEADED ARROW - return rune(0x219f), true + return {'\u219f', 0}, 1, true case "Uarrocir": // UPWARDS TWO-HEADED ARROW FROM SMALL CIRCLE - return rune(0x2949), true + return {'\u2949', 0}, 1, true case "Ubrcy": // CYRILLIC CAPITAL LETTER SHORT U - return rune(0x040e), true + return {'\u040e', 0}, 1, true case "Ubreve": // LATIN CAPITAL LETTER U WITH BREVE - return rune(0x016c), true + return {'\u016c', 0}, 1, true case "Ucirc": // LATIN CAPITAL LETTER U WITH CIRCUMFLEX - return rune(0xdb), true + return {'Û', 0}, 1, true case "Ucy": // CYRILLIC CAPITAL LETTER U - return rune(0x0423), true + return {'\u0423', 0}, 1, true case "Udblac": // LATIN CAPITAL LETTER U WITH DOUBLE ACUTE - return rune(0x0170), true + return {'\u0170', 0}, 1, true case "Udigr": // GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA - return rune(0x03ab), true + return {'\u03ab', 0}, 1, true case "Ufr": // MATHEMATICAL FRAKTUR CAPITAL U - return rune(0x01d518), true + return {'\U0001d518', 0}, 1, true case "Ugr": // GREEK CAPITAL LETTER UPSILON - return rune(0x03a5), true + return {'\u03a5', 0}, 1, true case "Ugrave": // LATIN CAPITAL LETTER U WITH GRAVE - return rune(0xd9), true + return {'Ù', 0}, 1, true case "Umacr": // LATIN CAPITAL LETTER U WITH MACRON - return rune(0x016a), true + return {'\u016a', 0}, 1, true case "UnderBar": // LOW LINE - return rune(0x5f), true + return {'_', 0}, 1, true case "UnderBrace": // BOTTOM CURLY BRACKET - return rune(0x23df), true + return {'\u23df', 0}, 1, true case "UnderBracket": // BOTTOM SQUARE BRACKET - return rune(0x23b5), true + return {'\u23b5', 0}, 1, true case "UnderParenthesis": // BOTTOM PARENTHESIS - return rune(0x23dd), true + return {'\u23dd', 0}, 1, true case "Union": // N-ARY UNION - return rune(0x22c3), true + return {'\u22c3', 0}, 1, true case "UnionPlus": // MULTISET UNION - return rune(0x228e), true + return {'\u228e', 0}, 1, true case "Uogon": // LATIN CAPITAL LETTER U WITH OGONEK - return rune(0x0172), true + return {'\u0172', 0}, 1, true case "Uopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL U - return rune(0x01d54c), true + return {'\U0001d54c', 0}, 1, true case "UpArrow": // UPWARDS ARROW - return rune(0x2191), true + return {'\u2191', 0}, 1, true case "UpArrowBar": // UPWARDS ARROW TO BAR - return rune(0x2912), true + return {'\u2912', 0}, 1, true case "UpArrowDownArrow": // UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW - return rune(0x21c5), true + return {'\u21c5', 0}, 1, true case "UpDownArrow": // UP DOWN ARROW - return rune(0x2195), true + return {'\u2195', 0}, 1, true case "UpEquilibrium": // UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT - return rune(0x296e), true + return {'\u296e', 0}, 1, true case "UpTee": // UP TACK - return rune(0x22a5), true + return {'\u22a5', 0}, 1, true case "UpTeeArrow": // UPWARDS ARROW FROM BAR - return rune(0x21a5), true + return {'\u21a5', 0}, 1, true case "Uparrow": // UPWARDS DOUBLE ARROW - return rune(0x21d1), true + return {'\u21d1', 0}, 1, true case "Updownarrow": // UP DOWN DOUBLE ARROW - return rune(0x21d5), true + return {'\u21d5', 0}, 1, true case "UpperLeftArrow": // NORTH WEST ARROW - return rune(0x2196), true + return {'\u2196', 0}, 1, true case "UpperRightArrow": // NORTH EAST ARROW - return rune(0x2197), true + return {'\u2197', 0}, 1, true case "Upsi": // GREEK UPSILON WITH HOOK SYMBOL - return rune(0x03d2), true + return {'\u03d2', 0}, 1, true case "Upsilon": // GREEK CAPITAL LETTER UPSILON - return rune(0x03a5), true + return {'\u03a5', 0}, 1, true case "Uring": // LATIN CAPITAL LETTER U WITH RING ABOVE - return rune(0x016e), true + return {'\u016e', 0}, 1, true case "Uscr": // MATHEMATICAL SCRIPT CAPITAL U - return rune(0x01d4b0), true + return {'\U0001d4b0', 0}, 1, true case "Utilde": // LATIN CAPITAL LETTER U WITH TILDE - return rune(0x0168), true + return {'\u0168', 0}, 1, true case "Uuml": // LATIN CAPITAL LETTER U WITH DIAERESIS - return rune(0xdc), true + return {'Ü', 0}, 1, true } case 'V': switch name { case "VDash": // DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE - return rune(0x22ab), true + return {'\u22ab', 0}, 1, true case "Vbar": // DOUBLE UP TACK - return rune(0x2aeb), true + return {'\u2aeb', 0}, 1, true case "Vcy": // CYRILLIC CAPITAL LETTER VE - return rune(0x0412), true + return {'\u0412', 0}, 1, true case "Vdash": // FORCES - return rune(0x22a9), true + return {'\u22a9', 0}, 1, true case "Vdashl": // LONG DASH FROM LEFT MEMBER OF DOUBLE VERTICAL - return rune(0x2ae6), true + return {'\u2ae6', 0}, 1, true case "Vee": // N-ARY LOGICAL OR - return rune(0x22c1), true + return {'\u22c1', 0}, 1, true case "Verbar": // DOUBLE VERTICAL LINE - return rune(0x2016), true + return {'\u2016', 0}, 1, true case "Vert": // DOUBLE VERTICAL LINE - return rune(0x2016), true + return {'\u2016', 0}, 1, true case "VerticalBar": // DIVIDES - return rune(0x2223), true + return {'\u2223', 0}, 1, true case "VerticalLine": // VERTICAL LINE - return rune(0x7c), true + return {'|', 0}, 1, true case "VerticalSeparator": // LIGHT VERTICAL BAR - return rune(0x2758), true + return {'\u2758', 0}, 1, true case "VerticalTilde": // WREATH PRODUCT - return rune(0x2240), true + return {'\u2240', 0}, 1, true case "VeryThinSpace": // HAIR SPACE - return rune(0x200a), true + return {'\u200a', 0}, 1, true case "Vfr": // MATHEMATICAL FRAKTUR CAPITAL V - return rune(0x01d519), true + return {'\U0001d519', 0}, 1, true case "Vopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL V - return rune(0x01d54d), true + return {'\U0001d54d', 0}, 1, true case "Vscr": // MATHEMATICAL SCRIPT CAPITAL V - return rune(0x01d4b1), true + return {'\U0001d4b1', 0}, 1, true case "Vvdash": // TRIPLE VERTICAL BAR RIGHT TURNSTILE - return rune(0x22aa), true + return {'\u22aa', 0}, 1, true } case 'W': switch name { case "Wcirc": // LATIN CAPITAL LETTER W WITH CIRCUMFLEX - return rune(0x0174), true + return {'\u0174', 0}, 1, true case "Wedge": // N-ARY LOGICAL AND - return rune(0x22c0), true + return {'\u22c0', 0}, 1, true case "Wfr": // MATHEMATICAL FRAKTUR CAPITAL W - return rune(0x01d51a), true + return {'\U0001d51a', 0}, 1, true case "Wopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL W - return rune(0x01d54e), true + return {'\U0001d54e', 0}, 1, true case "Wscr": // MATHEMATICAL SCRIPT CAPITAL W - return rune(0x01d4b2), true + return {'\U0001d4b2', 0}, 1, true } case 'X': switch name { case "Xfr": // MATHEMATICAL FRAKTUR CAPITAL X - return rune(0x01d51b), true + return {'\U0001d51b', 0}, 1, true case "Xgr": // GREEK CAPITAL LETTER XI - return rune(0x039e), true + return {'\u039e', 0}, 1, true case "Xi": // GREEK CAPITAL LETTER XI - return rune(0x039e), true + return {'\u039e', 0}, 1, true case "Xopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL X - return rune(0x01d54f), true + return {'\U0001d54f', 0}, 1, true case "Xscr": // MATHEMATICAL SCRIPT CAPITAL X - return rune(0x01d4b3), true + return {'\U0001d4b3', 0}, 1, true } case 'Y': switch name { case "YAcy": // CYRILLIC CAPITAL LETTER YA - return rune(0x042f), true + return {'\u042f', 0}, 1, true case "YIcy": // CYRILLIC CAPITAL LETTER YI - return rune(0x0407), true + return {'\u0407', 0}, 1, true case "YUcy": // CYRILLIC CAPITAL LETTER YU - return rune(0x042e), true + return {'\u042e', 0}, 1, true case "Yacute": // LATIN CAPITAL LETTER Y WITH ACUTE - return rune(0xdd), true + return {'Ý', 0}, 1, true case "Ycirc": // LATIN CAPITAL LETTER Y WITH CIRCUMFLEX - return rune(0x0176), true + return {'\u0176', 0}, 1, true case "Ycy": // CYRILLIC CAPITAL LETTER YERU - return rune(0x042b), true + return {'\u042b', 0}, 1, true case "Yfr": // MATHEMATICAL FRAKTUR CAPITAL Y - return rune(0x01d51c), true + return {'\U0001d51c', 0}, 1, true case "Yopf": // MATHEMATICAL DOUBLE-STRUCK CAPITAL Y - return rune(0x01d550), true + return {'\U0001d550', 0}, 1, true case "Yscr": // MATHEMATICAL SCRIPT CAPITAL Y - return rune(0x01d4b4), true + return {'\U0001d4b4', 0}, 1, true case "Yuml": // LATIN CAPITAL LETTER Y WITH DIAERESIS - return rune(0x0178), true + return {'\u0178', 0}, 1, true } case 'Z': switch name { case "ZHcy": // CYRILLIC CAPITAL LETTER ZHE - return rune(0x0416), true + return {'\u0416', 0}, 1, true case "Zacute": // LATIN CAPITAL LETTER Z WITH ACUTE - return rune(0x0179), true + return {'\u0179', 0}, 1, true case "Zcaron": // LATIN CAPITAL LETTER Z WITH CARON - return rune(0x017d), true + return {'\u017d', 0}, 1, true case "Zcy": // CYRILLIC CAPITAL LETTER ZE - return rune(0x0417), true + return {'\u0417', 0}, 1, true case "Zdot": // LATIN CAPITAL LETTER Z WITH DOT ABOVE - return rune(0x017b), true + return {'\u017b', 0}, 1, true case "ZeroWidthSpace": // ZERO WIDTH SPACE - return rune(0x200b), true + return {'\u200b', 0}, 1, true case "Zeta": // GREEK CAPITAL LETTER ZETA - return rune(0x0396), true + return {'\u0396', 0}, 1, true case "Zfr": // BLACK-LETTER CAPITAL Z - return rune(0x2128), true + return {'\u2128', 0}, 1, true case "Zgr": // GREEK CAPITAL LETTER ZETA - return rune(0x0396), true + return {'\u0396', 0}, 1, true case "Zopf": // DOUBLE-STRUCK CAPITAL Z - return rune(0x2124), true + return {'\u2124', 0}, 1, true case "Zscr": // MATHEMATICAL SCRIPT CAPITAL Z - return rune(0x01d4b5), true + return {'\U0001d4b5', 0}, 1, true } case 'a': switch name { case "aacgr": // GREEK SMALL LETTER ALPHA WITH TONOS - return rune(0x03ac), true + return {'\u03ac', 0}, 1, true case "aacute": // LATIN SMALL LETTER A WITH ACUTE - return rune(0xe1), true + return {'á', 0}, 1, true case "abreve": // LATIN SMALL LETTER A WITH BREVE - return rune(0x0103), true + return {'\u0103', 0}, 1, true case "ac": // INVERTED LAZY S - return rune(0x223e), true + return {'\u223e', 0}, 1, true case "acE": // INVERTED LAZY S with double underline - return rune(0x223e), true + return {'\u223e', '\u0333'}, 2, true case "acd": // SINE WAVE - return rune(0x223f), true + return {'\u223f', 0}, 1, true case "acirc": // LATIN SMALL LETTER A WITH CIRCUMFLEX - return rune(0xe2), true + return {'â', 0}, 1, true case "actuary": // COMBINING ANNUITY SYMBOL - return rune(0x20e7), true + return {'\u20e7', 0}, 1, true case "acute": // ACUTE ACCENT - return rune(0xb4), true + return {'´', 0}, 1, true case "acy": // CYRILLIC SMALL LETTER A - return rune(0x0430), true + return {'\u0430', 0}, 1, true case "aelig": // LATIN SMALL LETTER AE - return rune(0xe6), true + return {'æ', 0}, 1, true case "af": // FUNCTION APPLICATION - return rune(0x2061), true + return {'\u2061', 0}, 1, true case "afr": // MATHEMATICAL FRAKTUR SMALL A - return rune(0x01d51e), true + return {'\U0001d51e', 0}, 1, true case "agr": // GREEK SMALL LETTER ALPHA - return rune(0x03b1), true + return {'\u03b1', 0}, 1, true case "agrave": // LATIN SMALL LETTER A WITH GRAVE - return rune(0xe0), true + return {'à', 0}, 1, true case "alefsym": // ALEF SYMBOL - return rune(0x2135), true + return {'\u2135', 0}, 1, true case "aleph": // ALEF SYMBOL - return rune(0x2135), true + return {'\u2135', 0}, 1, true case "alpha": // GREEK SMALL LETTER ALPHA - return rune(0x03b1), true + return {'\u03b1', 0}, 1, true case "amacr": // LATIN SMALL LETTER A WITH MACRON - return rune(0x0101), true + return {'\u0101', 0}, 1, true case "amalg": // AMALGAMATION OR COPRODUCT - return rune(0x2a3f), true + return {'\u2a3f', 0}, 1, true case "amp": // AMPERSAND - return rune(0x26), true + return {'&', 0}, 1, true case "and": // LOGICAL AND - return rune(0x2227), true + return {'\u2227', 0}, 1, true case "andand": // TWO INTERSECTING LOGICAL AND - return rune(0x2a55), true + return {'\u2a55', 0}, 1, true case "andd": // LOGICAL AND WITH HORIZONTAL DASH - return rune(0x2a5c), true + return {'\u2a5c', 0}, 1, true case "andslope": // SLOPING LARGE AND - return rune(0x2a58), true + return {'\u2a58', 0}, 1, true case "andv": // LOGICAL AND WITH MIDDLE STEM - return rune(0x2a5a), true + return {'\u2a5a', 0}, 1, true case "ang": // ANGLE - return rune(0x2220), true + return {'\u2220', 0}, 1, true case "ang90": // RIGHT ANGLE - return rune(0x221f), true + return {'\u221f', 0}, 1, true case "angdnl": // TURNED ANGLE - return rune(0x29a2), true + return {'\u29a2', 0}, 1, true case "angdnr": // ACUTE ANGLE - return rune(0x299f), true + return {'\u299f', 0}, 1, true case "ange": // ANGLE WITH UNDERBAR - return rune(0x29a4), true + return {'\u29a4', 0}, 1, true case "angle": // ANGLE - return rune(0x2220), true + return {'\u2220', 0}, 1, true case "angles": // ANGLE WITH S INSIDE - return rune(0x299e), true + return {'\u299e', 0}, 1, true case "angmsd": // MEASURED ANGLE - return rune(0x2221), true + return {'\u2221', 0}, 1, true case "angmsdaa": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND RIGHT - return rune(0x29a8), true + return {'\u29a8', 0}, 1, true case "angmsdab": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND LEFT - return rune(0x29a9), true + return {'\u29a9', 0}, 1, true case "angmsdac": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND RIGHT - return rune(0x29aa), true + return {'\u29aa', 0}, 1, true case "angmsdad": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND LEFT - return rune(0x29ab), true + return {'\u29ab', 0}, 1, true case "angmsdae": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND UP - return rune(0x29ac), true + return {'\u29ac', 0}, 1, true case "angmsdaf": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND UP - return rune(0x29ad), true + return {'\u29ad', 0}, 1, true case "angmsdag": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND DOWN - return rune(0x29ae), true + return {'\u29ae', 0}, 1, true case "angmsdah": // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND DOWN - return rune(0x29af), true + return {'\u29af', 0}, 1, true case "angrt": // RIGHT ANGLE - return rune(0x221f), true + return {'\u221f', 0}, 1, true case "angrtvb": // RIGHT ANGLE WITH ARC - return rune(0x22be), true + return {'\u22be', 0}, 1, true case "angrtvbd": // MEASURED RIGHT ANGLE WITH DOT - return rune(0x299d), true + return {'\u299d', 0}, 1, true case "angsph": // SPHERICAL ANGLE - return rune(0x2222), true + return {'\u2222', 0}, 1, true case "angst": // LATIN CAPITAL LETTER A WITH RING ABOVE - return rune(0xc5), true + return {'Å', 0}, 1, true case "angupl": // REVERSED ANGLE - return rune(0x29a3), true + return {'\u29a3', 0}, 1, true case "angzarr": // RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW - return rune(0x237c), true + return {'\u237c', 0}, 1, true case "aogon": // LATIN SMALL LETTER A WITH OGONEK - return rune(0x0105), true + return {'\u0105', 0}, 1, true case "aopf": // MATHEMATICAL DOUBLE-STRUCK SMALL A - return rune(0x01d552), true + return {'\U0001d552', 0}, 1, true case "ap": // ALMOST EQUAL TO - return rune(0x2248), true + return {'\u2248', 0}, 1, true case "apE": // APPROXIMATELY EQUAL OR EQUAL TO - return rune(0x2a70), true + return {'\u2a70', 0}, 1, true case "apacir": // ALMOST EQUAL TO WITH CIRCUMFLEX ACCENT - return rune(0x2a6f), true + return {'\u2a6f', 0}, 1, true case "ape": // ALMOST EQUAL OR EQUAL TO - return rune(0x224a), true + return {'\u224a', 0}, 1, true case "apid": // TRIPLE TILDE - return rune(0x224b), true + return {'\u224b', 0}, 1, true case "apos": // APOSTROPHE - return rune(0x27), true + return {'\'', 0}, 1, true case "approx": // ALMOST EQUAL TO - return rune(0x2248), true + return {'\u2248', 0}, 1, true case "approxeq": // ALMOST EQUAL OR EQUAL TO - return rune(0x224a), true + return {'\u224a', 0}, 1, true case "aring": // LATIN SMALL LETTER A WITH RING ABOVE - return rune(0xe5), true + return {'å', 0}, 1, true case "arrllsr": // LEFTWARDS ARROW ABOVE SHORT RIGHTWARDS ARROW - return rune(0x2943), true + return {'\u2943', 0}, 1, true case "arrlrsl": // RIGHTWARDS ARROW ABOVE SHORT LEFTWARDS ARROW - return rune(0x2942), true + return {'\u2942', 0}, 1, true case "arrsrll": // SHORT RIGHTWARDS ARROW ABOVE LEFTWARDS ARROW - return rune(0x2944), true + return {'\u2944', 0}, 1, true case "ascr": // MATHEMATICAL SCRIPT SMALL A - return rune(0x01d4b6), true + return {'\U0001d4b6', 0}, 1, true case "ast": // ASTERISK - return rune(0x2a), true + return {'*', 0}, 1, true case "astb": // SQUARED ASTERISK - return rune(0x29c6), true + return {'\u29c6', 0}, 1, true case "asymp": // ALMOST EQUAL TO - return rune(0x2248), true + return {'\u2248', 0}, 1, true case "asympeq": // EQUIVALENT TO - return rune(0x224d), true + return {'\u224d', 0}, 1, true case "atilde": // LATIN SMALL LETTER A WITH TILDE - return rune(0xe3), true + return {'ã', 0}, 1, true case "auml": // LATIN SMALL LETTER A WITH DIAERESIS - return rune(0xe4), true + return {'ä', 0}, 1, true case "awconint": // ANTICLOCKWISE CONTOUR INTEGRAL - return rune(0x2233), true + return {'\u2233', 0}, 1, true case "awint": // ANTICLOCKWISE INTEGRATION - return rune(0x2a11), true + return {'\u2a11', 0}, 1, true } case 'b': switch name { case "b.Delta": // MATHEMATICAL BOLD CAPITAL DELTA - return rune(0x01d6ab), true + return {'\U0001d6ab', 0}, 1, true case "b.Gamma": // MATHEMATICAL BOLD CAPITAL GAMMA - return rune(0x01d6aa), true + return {'\U0001d6aa', 0}, 1, true case "b.Gammad": // MATHEMATICAL BOLD CAPITAL DIGAMMA - return rune(0x01d7ca), true + return {'\U0001d7ca', 0}, 1, true case "b.Lambda": // MATHEMATICAL BOLD CAPITAL LAMDA - return rune(0x01d6b2), true + return {'\U0001d6b2', 0}, 1, true case "b.Omega": // MATHEMATICAL BOLD CAPITAL OMEGA - return rune(0x01d6c0), true + return {'\U0001d6c0', 0}, 1, true case "b.Phi": // MATHEMATICAL BOLD CAPITAL PHI - return rune(0x01d6bd), true + return {'\U0001d6bd', 0}, 1, true case "b.Pi": // MATHEMATICAL BOLD CAPITAL PI - return rune(0x01d6b7), true + return {'\U0001d6b7', 0}, 1, true case "b.Psi": // MATHEMATICAL BOLD CAPITAL PSI - return rune(0x01d6bf), true + return {'\U0001d6bf', 0}, 1, true case "b.Sigma": // MATHEMATICAL BOLD CAPITAL SIGMA - return rune(0x01d6ba), true + return {'\U0001d6ba', 0}, 1, true case "b.Theta": // MATHEMATICAL BOLD CAPITAL THETA - return rune(0x01d6af), true + return {'\U0001d6af', 0}, 1, true case "b.Upsi": // MATHEMATICAL BOLD CAPITAL UPSILON - return rune(0x01d6bc), true + return {'\U0001d6bc', 0}, 1, true case "b.Xi": // MATHEMATICAL BOLD CAPITAL XI - return rune(0x01d6b5), true + return {'\U0001d6b5', 0}, 1, true case "b.alpha": // MATHEMATICAL BOLD SMALL ALPHA - return rune(0x01d6c2), true + return {'\U0001d6c2', 0}, 1, true case "b.beta": // MATHEMATICAL BOLD SMALL BETA - return rune(0x01d6c3), true + return {'\U0001d6c3', 0}, 1, true case "b.chi": // MATHEMATICAL BOLD SMALL CHI - return rune(0x01d6d8), true + return {'\U0001d6d8', 0}, 1, true case "b.delta": // MATHEMATICAL BOLD SMALL DELTA - return rune(0x01d6c5), true + return {'\U0001d6c5', 0}, 1, true case "b.epsi": // MATHEMATICAL BOLD SMALL EPSILON - return rune(0x01d6c6), true + return {'\U0001d6c6', 0}, 1, true case "b.epsiv": // MATHEMATICAL BOLD EPSILON SYMBOL - return rune(0x01d6dc), true + return {'\U0001d6dc', 0}, 1, true case "b.eta": // MATHEMATICAL BOLD SMALL ETA - return rune(0x01d6c8), true + return {'\U0001d6c8', 0}, 1, true case "b.gamma": // MATHEMATICAL BOLD SMALL GAMMA - return rune(0x01d6c4), true + return {'\U0001d6c4', 0}, 1, true case "b.gammad": // MATHEMATICAL BOLD SMALL DIGAMMA - return rune(0x01d7cb), true + return {'\U0001d7cb', 0}, 1, true case "b.iota": // MATHEMATICAL BOLD SMALL IOTA - return rune(0x01d6ca), true + return {'\U0001d6ca', 0}, 1, true case "b.kappa": // MATHEMATICAL BOLD SMALL KAPPA - return rune(0x01d6cb), true + return {'\U0001d6cb', 0}, 1, true case "b.kappav": // MATHEMATICAL BOLD KAPPA SYMBOL - return rune(0x01d6de), true + return {'\U0001d6de', 0}, 1, true case "b.lambda": // MATHEMATICAL BOLD SMALL LAMDA - return rune(0x01d6cc), true + return {'\U0001d6cc', 0}, 1, true case "b.mu": // MATHEMATICAL BOLD SMALL MU - return rune(0x01d6cd), true + return {'\U0001d6cd', 0}, 1, true case "b.nu": // MATHEMATICAL BOLD SMALL NU - return rune(0x01d6ce), true + return {'\U0001d6ce', 0}, 1, true case "b.omega": // MATHEMATICAL BOLD SMALL OMEGA - return rune(0x01d6da), true + return {'\U0001d6da', 0}, 1, true case "b.phi": // MATHEMATICAL BOLD SMALL PHI - return rune(0x01d6d7), true + return {'\U0001d6d7', 0}, 1, true case "b.phiv": // MATHEMATICAL BOLD PHI SYMBOL - return rune(0x01d6df), true + return {'\U0001d6df', 0}, 1, true case "b.pi": // MATHEMATICAL BOLD SMALL PI - return rune(0x01d6d1), true + return {'\U0001d6d1', 0}, 1, true case "b.piv": // MATHEMATICAL BOLD PI SYMBOL - return rune(0x01d6e1), true + return {'\U0001d6e1', 0}, 1, true case "b.psi": // MATHEMATICAL BOLD SMALL PSI - return rune(0x01d6d9), true + return {'\U0001d6d9', 0}, 1, true case "b.rho": // MATHEMATICAL BOLD SMALL RHO - return rune(0x01d6d2), true + return {'\U0001d6d2', 0}, 1, true case "b.rhov": // MATHEMATICAL BOLD RHO SYMBOL - return rune(0x01d6e0), true + return {'\U0001d6e0', 0}, 1, true case "b.sigma": // MATHEMATICAL BOLD SMALL SIGMA - return rune(0x01d6d4), true + return {'\U0001d6d4', 0}, 1, true case "b.sigmav": // MATHEMATICAL BOLD SMALL FINAL SIGMA - return rune(0x01d6d3), true + return {'\U0001d6d3', 0}, 1, true case "b.tau": // MATHEMATICAL BOLD SMALL TAU - return rune(0x01d6d5), true + return {'\U0001d6d5', 0}, 1, true case "b.thetas": // MATHEMATICAL BOLD SMALL THETA - return rune(0x01d6c9), true + return {'\U0001d6c9', 0}, 1, true case "b.thetav": // MATHEMATICAL BOLD THETA SYMBOL - return rune(0x01d6dd), true + return {'\U0001d6dd', 0}, 1, true case "b.upsi": // MATHEMATICAL BOLD SMALL UPSILON - return rune(0x01d6d6), true + return {'\U0001d6d6', 0}, 1, true case "b.xi": // MATHEMATICAL BOLD SMALL XI - return rune(0x01d6cf), true + return {'\U0001d6cf', 0}, 1, true case "b.zeta": // MATHEMATICAL BOLD SMALL ZETA - return rune(0x01d6c7), true + return {'\U0001d6c7', 0}, 1, true case "bNot": // REVERSED DOUBLE STROKE NOT SIGN - return rune(0x2aed), true + return {'\u2aed', 0}, 1, true case "backcong": // ALL EQUAL TO - return rune(0x224c), true + return {'\u224c', 0}, 1, true case "backepsilon": // GREEK REVERSED LUNATE EPSILON SYMBOL - return rune(0x03f6), true + return {'\u03f6', 0}, 1, true case "backprime": // REVERSED PRIME - return rune(0x2035), true + return {'\u2035', 0}, 1, true case "backsim": // REVERSED TILDE - return rune(0x223d), true + return {'\u223d', 0}, 1, true case "backsimeq": // REVERSED TILDE EQUALS - return rune(0x22cd), true + return {'\u22cd', 0}, 1, true case "barV": // DOUBLE DOWN TACK - return rune(0x2aea), true + return {'\u2aea', 0}, 1, true case "barvee": // NOR - return rune(0x22bd), true + return {'\u22bd', 0}, 1, true case "barwed": // PROJECTIVE - return rune(0x2305), true + return {'\u2305', 0}, 1, true case "barwedge": // PROJECTIVE - return rune(0x2305), true + return {'\u2305', 0}, 1, true case "bbrk": // BOTTOM SQUARE BRACKET - return rune(0x23b5), true + return {'\u23b5', 0}, 1, true case "bbrktbrk": // BOTTOM SQUARE BRACKET OVER TOP SQUARE BRACKET - return rune(0x23b6), true + return {'\u23b6', 0}, 1, true case "bcong": // ALL EQUAL TO - return rune(0x224c), true + return {'\u224c', 0}, 1, true case "bcy": // CYRILLIC SMALL LETTER BE - return rune(0x0431), true + return {'\u0431', 0}, 1, true case "bdlhar": // DOWNWARDS HARPOON WITH BARB LEFT FROM BAR - return rune(0x2961), true + return {'\u2961', 0}, 1, true case "bdquo": // DOUBLE LOW-9 QUOTATION MARK - return rune(0x201e), true + return {'\u201e', 0}, 1, true case "bdrhar": // DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR - return rune(0x295d), true + return {'\u295d', 0}, 1, true case "becaus": // BECAUSE - return rune(0x2235), true + return {'\u2235', 0}, 1, true case "because": // BECAUSE - return rune(0x2235), true + return {'\u2235', 0}, 1, true case "bemptyv": // REVERSED EMPTY SET - return rune(0x29b0), true + return {'\u29b0', 0}, 1, true case "bepsi": // GREEK REVERSED LUNATE EPSILON SYMBOL - return rune(0x03f6), true + return {'\u03f6', 0}, 1, true case "bernou": // SCRIPT CAPITAL B - return rune(0x212c), true + return {'\u212c', 0}, 1, true case "beta": // GREEK SMALL LETTER BETA - return rune(0x03b2), true + return {'\u03b2', 0}, 1, true case "beth": // BET SYMBOL - return rune(0x2136), true + return {'\u2136', 0}, 1, true case "between": // BETWEEN - return rune(0x226c), true + return {'\u226c', 0}, 1, true case "bfr": // MATHEMATICAL FRAKTUR SMALL B - return rune(0x01d51f), true + return {'\U0001d51f', 0}, 1, true case "bgr": // GREEK SMALL LETTER BETA - return rune(0x03b2), true + return {'\u03b2', 0}, 1, true case "bigcap": // N-ARY INTERSECTION - return rune(0x22c2), true + return {'\u22c2', 0}, 1, true case "bigcirc": // LARGE CIRCLE - return rune(0x25ef), true + return {'\u25ef', 0}, 1, true case "bigcup": // N-ARY UNION - return rune(0x22c3), true + return {'\u22c3', 0}, 1, true case "bigodot": // N-ARY CIRCLED DOT OPERATOR - return rune(0x2a00), true + return {'\u2a00', 0}, 1, true case "bigoplus": // N-ARY CIRCLED PLUS OPERATOR - return rune(0x2a01), true + return {'\u2a01', 0}, 1, true case "bigotimes": // N-ARY CIRCLED TIMES OPERATOR - return rune(0x2a02), true + return {'\u2a02', 0}, 1, true case "bigsqcup": // N-ARY SQUARE UNION OPERATOR - return rune(0x2a06), true + return {'\u2a06', 0}, 1, true case "bigstar": // BLACK STAR - return rune(0x2605), true + return {'\u2605', 0}, 1, true case "bigtriangledown": // WHITE DOWN-POINTING TRIANGLE - return rune(0x25bd), true + return {'\u25bd', 0}, 1, true case "bigtriangleup": // WHITE UP-POINTING TRIANGLE - return rune(0x25b3), true + return {'\u25b3', 0}, 1, true case "biguplus": // N-ARY UNION OPERATOR WITH PLUS - return rune(0x2a04), true + return {'\u2a04', 0}, 1, true case "bigvee": // N-ARY LOGICAL OR - return rune(0x22c1), true + return {'\u22c1', 0}, 1, true case "bigwedge": // N-ARY LOGICAL AND - return rune(0x22c0), true + return {'\u22c0', 0}, 1, true case "bkarow": // RIGHTWARDS DOUBLE DASH ARROW - return rune(0x290d), true + return {'\u290d', 0}, 1, true case "blacklozenge": // BLACK LOZENGE - return rune(0x29eb), true + return {'\u29eb', 0}, 1, true case "blacksquare": // BLACK SMALL SQUARE - return rune(0x25aa), true + return {'\u25aa', 0}, 1, true case "blacktriangle": // BLACK UP-POINTING SMALL TRIANGLE - return rune(0x25b4), true + return {'\u25b4', 0}, 1, true case "blacktriangledown": // BLACK DOWN-POINTING SMALL TRIANGLE - return rune(0x25be), true + return {'\u25be', 0}, 1, true case "blacktriangleleft": // BLACK LEFT-POINTING SMALL TRIANGLE - return rune(0x25c2), true + return {'\u25c2', 0}, 1, true case "blacktriangleright": // BLACK RIGHT-POINTING SMALL TRIANGLE - return rune(0x25b8), true + return {'\u25b8', 0}, 1, true case "blank": // BLANK SYMBOL - return rune(0x2422), true + return {'\u2422', 0}, 1, true case "bldhar": // LEFTWARDS HARPOON WITH BARB DOWN FROM BAR - return rune(0x295e), true + return {'\u295e', 0}, 1, true case "blk12": // MEDIUM SHADE - return rune(0x2592), true + return {'\u2592', 0}, 1, true case "blk14": // LIGHT SHADE - return rune(0x2591), true + return {'\u2591', 0}, 1, true case "blk34": // DARK SHADE - return rune(0x2593), true + return {'\u2593', 0}, 1, true case "block": // FULL BLOCK - return rune(0x2588), true + return {'\u2588', 0}, 1, true case "bluhar": // LEFTWARDS HARPOON WITH BARB UP FROM BAR - return rune(0x295a), true + return {'\u295a', 0}, 1, true case "bne": // EQUALS SIGN with reverse slash - return rune(0x3d), true + return {'=', '\u20e5'}, 2, true case "bnequiv": // IDENTICAL TO with reverse slash - return rune(0x2261), true + return {'\u2261', '\u20e5'}, 2, true case "bnot": // REVERSED NOT SIGN - return rune(0x2310), true + return {'\u2310', 0}, 1, true case "bopf": // MATHEMATICAL DOUBLE-STRUCK SMALL B - return rune(0x01d553), true + return {'\U0001d553', 0}, 1, true case "bot": // UP TACK - return rune(0x22a5), true + return {'\u22a5', 0}, 1, true case "bottom": // UP TACK - return rune(0x22a5), true + return {'\u22a5', 0}, 1, true case "bowtie": // BOWTIE - return rune(0x22c8), true + return {'\u22c8', 0}, 1, true case "boxDL": // BOX DRAWINGS DOUBLE DOWN AND LEFT - return rune(0x2557), true + return {'\u2557', 0}, 1, true case "boxDR": // BOX DRAWINGS DOUBLE DOWN AND RIGHT - return rune(0x2554), true + return {'\u2554', 0}, 1, true case "boxDl": // BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE - return rune(0x2556), true + return {'\u2556', 0}, 1, true case "boxDr": // BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE - return rune(0x2553), true + return {'\u2553', 0}, 1, true case "boxH": // BOX DRAWINGS DOUBLE HORIZONTAL - return rune(0x2550), true + return {'\u2550', 0}, 1, true case "boxHD": // BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL - return rune(0x2566), true + return {'\u2566', 0}, 1, true case "boxHU": // BOX DRAWINGS DOUBLE UP AND HORIZONTAL - return rune(0x2569), true + return {'\u2569', 0}, 1, true case "boxHd": // BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE - return rune(0x2564), true + return {'\u2564', 0}, 1, true case "boxHu": // BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE - return rune(0x2567), true + return {'\u2567', 0}, 1, true case "boxUL": // BOX DRAWINGS DOUBLE UP AND LEFT - return rune(0x255d), true + return {'\u255d', 0}, 1, true case "boxUR": // BOX DRAWINGS DOUBLE UP AND RIGHT - return rune(0x255a), true + return {'\u255a', 0}, 1, true case "boxUl": // BOX DRAWINGS UP DOUBLE AND LEFT SINGLE - return rune(0x255c), true + return {'\u255c', 0}, 1, true case "boxUr": // BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE - return rune(0x2559), true + return {'\u2559', 0}, 1, true case "boxV": // BOX DRAWINGS DOUBLE VERTICAL - return rune(0x2551), true + return {'\u2551', 0}, 1, true case "boxVH": // BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL - return rune(0x256c), true + return {'\u256c', 0}, 1, true case "boxVL": // BOX DRAWINGS DOUBLE VERTICAL AND LEFT - return rune(0x2563), true + return {'\u2563', 0}, 1, true case "boxVR": // BOX DRAWINGS DOUBLE VERTICAL AND RIGHT - return rune(0x2560), true + return {'\u2560', 0}, 1, true case "boxVh": // BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE - return rune(0x256b), true + return {'\u256b', 0}, 1, true case "boxVl": // BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE - return rune(0x2562), true + return {'\u2562', 0}, 1, true case "boxVr": // BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE - return rune(0x255f), true + return {'\u255f', 0}, 1, true case "boxbox": // TWO JOINED SQUARES - return rune(0x29c9), true + return {'\u29c9', 0}, 1, true case "boxdL": // BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE - return rune(0x2555), true + return {'\u2555', 0}, 1, true case "boxdR": // BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE - return rune(0x2552), true + return {'\u2552', 0}, 1, true case "boxdl": // BOX DRAWINGS LIGHT DOWN AND LEFT - return rune(0x2510), true + return {'\u2510', 0}, 1, true case "boxdr": // BOX DRAWINGS LIGHT DOWN AND RIGHT - return rune(0x250c), true + return {'\u250c', 0}, 1, true case "boxh": // BOX DRAWINGS LIGHT HORIZONTAL - return rune(0x2500), true + return {'\u2500', 0}, 1, true case "boxhD": // BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE - return rune(0x2565), true + return {'\u2565', 0}, 1, true case "boxhU": // BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE - return rune(0x2568), true + return {'\u2568', 0}, 1, true case "boxhd": // BOX DRAWINGS LIGHT DOWN AND HORIZONTAL - return rune(0x252c), true + return {'\u252c', 0}, 1, true case "boxhu": // BOX DRAWINGS LIGHT UP AND HORIZONTAL - return rune(0x2534), true + return {'\u2534', 0}, 1, true case "boxminus": // SQUARED MINUS - return rune(0x229f), true + return {'\u229f', 0}, 1, true case "boxplus": // SQUARED PLUS - return rune(0x229e), true + return {'\u229e', 0}, 1, true case "boxtimes": // SQUARED TIMES - return rune(0x22a0), true + return {'\u22a0', 0}, 1, true case "boxuL": // BOX DRAWINGS UP SINGLE AND LEFT DOUBLE - return rune(0x255b), true + return {'\u255b', 0}, 1, true case "boxuR": // BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE - return rune(0x2558), true + return {'\u2558', 0}, 1, true case "boxul": // BOX DRAWINGS LIGHT UP AND LEFT - return rune(0x2518), true + return {'\u2518', 0}, 1, true case "boxur": // BOX DRAWINGS LIGHT UP AND RIGHT - return rune(0x2514), true + return {'\u2514', 0}, 1, true case "boxv": // BOX DRAWINGS LIGHT VERTICAL - return rune(0x2502), true + return {'\u2502', 0}, 1, true case "boxvH": // BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE - return rune(0x256a), true + return {'\u256a', 0}, 1, true case "boxvL": // BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE - return rune(0x2561), true + return {'\u2561', 0}, 1, true case "boxvR": // BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE - return rune(0x255e), true + return {'\u255e', 0}, 1, true case "boxvh": // BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL - return rune(0x253c), true + return {'\u253c', 0}, 1, true case "boxvl": // BOX DRAWINGS LIGHT VERTICAL AND LEFT - return rune(0x2524), true + return {'\u2524', 0}, 1, true case "boxvr": // BOX DRAWINGS LIGHT VERTICAL AND RIGHT - return rune(0x251c), true + return {'\u251c', 0}, 1, true case "bprime": // REVERSED PRIME - return rune(0x2035), true + return {'\u2035', 0}, 1, true case "brdhar": // RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR - return rune(0x295f), true + return {'\u295f', 0}, 1, true case "breve": // BREVE - return rune(0x02d8), true + return {'\u02d8', 0}, 1, true case "bruhar": // RIGHTWARDS HARPOON WITH BARB UP FROM BAR - return rune(0x295b), true + return {'\u295b', 0}, 1, true case "brvbar": // BROKEN BAR - return rune(0xa6), true + return {'¦', 0}, 1, true case "bscr": // MATHEMATICAL SCRIPT SMALL B - return rune(0x01d4b7), true + return {'\U0001d4b7', 0}, 1, true case "bsemi": // REVERSED SEMICOLON - return rune(0x204f), true + return {'\u204f', 0}, 1, true case "bsim": // REVERSED TILDE - return rune(0x223d), true + return {'\u223d', 0}, 1, true case "bsime": // REVERSED TILDE EQUALS - return rune(0x22cd), true + return {'\u22cd', 0}, 1, true case "bsol": // REVERSE SOLIDUS - return rune(0x5c), true + return {'\\', 0}, 1, true case "bsolb": // SQUARED FALLING DIAGONAL SLASH - return rune(0x29c5), true + return {'\u29c5', 0}, 1, true case "bsolhsub": // REVERSE SOLIDUS PRECEDING SUBSET - return rune(0x27c8), true + return {'\u27c8', 0}, 1, true case "btimes": // SEMIDIRECT PRODUCT WITH BOTTOM CLOSED - return rune(0x2a32), true + return {'\u2a32', 0}, 1, true case "bulhar": // UPWARDS HARPOON WITH BARB LEFT FROM BAR - return rune(0x2960), true + return {'\u2960', 0}, 1, true case "bull": // BULLET - return rune(0x2022), true + return {'\u2022', 0}, 1, true case "bullet": // BULLET - return rune(0x2022), true + return {'\u2022', 0}, 1, true case "bump": // GEOMETRICALLY EQUIVALENT TO - return rune(0x224e), true + return {'\u224e', 0}, 1, true case "bumpE": // EQUALS SIGN WITH BUMPY ABOVE - return rune(0x2aae), true + return {'\u2aae', 0}, 1, true case "bumpe": // DIFFERENCE BETWEEN - return rune(0x224f), true + return {'\u224f', 0}, 1, true case "bumpeq": // DIFFERENCE BETWEEN - return rune(0x224f), true + return {'\u224f', 0}, 1, true case "burhar": // UPWARDS HARPOON WITH BARB RIGHT FROM BAR - return rune(0x295c), true + return {'\u295c', 0}, 1, true } case 'c': switch name { case "cacute": // LATIN SMALL LETTER C WITH ACUTE - return rune(0x0107), true + return {'\u0107', 0}, 1, true case "cap": // INTERSECTION - return rune(0x2229), true + return {'\u2229', 0}, 1, true case "capand": // INTERSECTION WITH LOGICAL AND - return rune(0x2a44), true + return {'\u2a44', 0}, 1, true case "capbrcup": // INTERSECTION ABOVE BAR ABOVE UNION - return rune(0x2a49), true + return {'\u2a49', 0}, 1, true case "capcap": // INTERSECTION BESIDE AND JOINED WITH INTERSECTION - return rune(0x2a4b), true + return {'\u2a4b', 0}, 1, true case "capcup": // INTERSECTION ABOVE UNION - return rune(0x2a47), true + return {'\u2a47', 0}, 1, true case "capdot": // INTERSECTION WITH DOT - return rune(0x2a40), true + return {'\u2a40', 0}, 1, true case "capint": // INTEGRAL WITH INTERSECTION - return rune(0x2a19), true + return {'\u2a19', 0}, 1, true case "caps": // INTERSECTION with serifs - return rune(0x2229), true + return {'\u2229', '\ufe00'}, 2, true case "caret": // CARET INSERTION POINT - return rune(0x2041), true + return {'\u2041', 0}, 1, true case "caron": // CARON - return rune(0x02c7), true + return {'\u02c7', 0}, 1, true case "ccaps": // CLOSED INTERSECTION WITH SERIFS - return rune(0x2a4d), true + return {'\u2a4d', 0}, 1, true case "ccaron": // LATIN SMALL LETTER C WITH CARON - return rune(0x010d), true + return {'\u010d', 0}, 1, true case "ccedil": // LATIN SMALL LETTER C WITH CEDILLA - return rune(0xe7), true + return {'ç', 0}, 1, true case "ccirc": // LATIN SMALL LETTER C WITH CIRCUMFLEX - return rune(0x0109), true + return {'\u0109', 0}, 1, true case "ccups": // CLOSED UNION WITH SERIFS - return rune(0x2a4c), true + return {'\u2a4c', 0}, 1, true case "ccupssm": // CLOSED UNION WITH SERIFS AND SMASH PRODUCT - return rune(0x2a50), true + return {'\u2a50', 0}, 1, true case "cdot": // LATIN SMALL LETTER C WITH DOT ABOVE - return rune(0x010b), true + return {'\u010b', 0}, 1, true case "cedil": // CEDILLA - return rune(0xb8), true + return {'¸', 0}, 1, true case "cemptyv": // EMPTY SET WITH SMALL CIRCLE ABOVE - return rune(0x29b2), true + return {'\u29b2', 0}, 1, true case "cent": // CENT SIGN - return rune(0xa2), true + return {'¢', 0}, 1, true case "centerdot": // MIDDLE DOT - return rune(0xb7), true + return {'·', 0}, 1, true case "cfr": // MATHEMATICAL FRAKTUR SMALL C - return rune(0x01d520), true + return {'\U0001d520', 0}, 1, true case "chcy": // CYRILLIC SMALL LETTER CHE - return rune(0x0447), true + return {'\u0447', 0}, 1, true case "check": // CHECK MARK - return rune(0x2713), true + return {'\u2713', 0}, 1, true case "checkmark": // CHECK MARK - return rune(0x2713), true + return {'\u2713', 0}, 1, true case "chi": // GREEK SMALL LETTER CHI - return rune(0x03c7), true + return {'\u03c7', 0}, 1, true case "cir": // WHITE CIRCLE - return rune(0x25cb), true + return {'\u25cb', 0}, 1, true case "cirE": // CIRCLE WITH TWO HORIZONTAL STROKES TO THE RIGHT - return rune(0x29c3), true + return {'\u29c3', 0}, 1, true case "cirb": // SQUARED SMALL CIRCLE - return rune(0x29c7), true + return {'\u29c7', 0}, 1, true case "circ": // MODIFIER LETTER CIRCUMFLEX ACCENT - return rune(0x02c6), true + return {'\u02c6', 0}, 1, true case "circeq": // RING EQUAL TO - return rune(0x2257), true + return {'\u2257', 0}, 1, true case "circlearrowleft": // ANTICLOCKWISE OPEN CIRCLE ARROW - return rune(0x21ba), true + return {'\u21ba', 0}, 1, true case "circlearrowright": // CLOCKWISE OPEN CIRCLE ARROW - return rune(0x21bb), true + return {'\u21bb', 0}, 1, true case "circledR": // REGISTERED SIGN - return rune(0xae), true + return {'®', 0}, 1, true case "circledS": // CIRCLED LATIN CAPITAL LETTER S - return rune(0x24c8), true + return {'\u24c8', 0}, 1, true case "circledast": // CIRCLED ASTERISK OPERATOR - return rune(0x229b), true + return {'\u229b', 0}, 1, true case "circledcirc": // CIRCLED RING OPERATOR - return rune(0x229a), true + return {'\u229a', 0}, 1, true case "circleddash": // CIRCLED DASH - return rune(0x229d), true + return {'\u229d', 0}, 1, true case "cirdarr": // WHITE CIRCLE WITH DOWN ARROW - return rune(0x29ec), true + return {'\u29ec', 0}, 1, true case "cire": // RING EQUAL TO - return rune(0x2257), true + return {'\u2257', 0}, 1, true case "cirerr": // ERROR-BARRED WHITE CIRCLE - return rune(0x29f2), true + return {'\u29f2', 0}, 1, true case "cirfdarr": // BLACK CIRCLE WITH DOWN ARROW - return rune(0x29ed), true + return {'\u29ed', 0}, 1, true case "cirferr": // ERROR-BARRED BLACK CIRCLE - return rune(0x29f3), true + return {'\u29f3', 0}, 1, true case "cirfnint": // CIRCULATION FUNCTION - return rune(0x2a10), true + return {'\u2a10', 0}, 1, true case "cirmid": // VERTICAL LINE WITH CIRCLE ABOVE - return rune(0x2aef), true + return {'\u2aef', 0}, 1, true case "cirscir": // CIRCLE WITH SMALL CIRCLE TO THE RIGHT - return rune(0x29c2), true + return {'\u29c2', 0}, 1, true case "closur": // CLOSE UP - return rune(0x2050), true + return {'\u2050', 0}, 1, true case "clubs": // BLACK CLUB SUIT - return rune(0x2663), true + return {'\u2663', 0}, 1, true case "clubsuit": // BLACK CLUB SUIT - return rune(0x2663), true + return {'\u2663', 0}, 1, true case "colon": // COLON - return rune(0x3a), true + return {':', 0}, 1, true case "colone": // COLON EQUALS - return rune(0x2254), true + return {'\u2254', 0}, 1, true case "coloneq": // COLON EQUALS - return rune(0x2254), true + return {'\u2254', 0}, 1, true case "comma": // COMMA - return rune(0x2c), true + return {',', 0}, 1, true case "commat": // COMMERCIAL AT - return rune(0x40), true + return {'@', 0}, 1, true case "comp": // COMPLEMENT - return rune(0x2201), true + return {'\u2201', 0}, 1, true case "compfn": // RING OPERATOR - return rune(0x2218), true + return {'\u2218', 0}, 1, true case "complement": // COMPLEMENT - return rune(0x2201), true + return {'\u2201', 0}, 1, true case "complexes": // DOUBLE-STRUCK CAPITAL C - return rune(0x2102), true + return {'\u2102', 0}, 1, true case "cong": // APPROXIMATELY EQUAL TO - return rune(0x2245), true + return {'\u2245', 0}, 1, true case "congdot": // CONGRUENT WITH DOT ABOVE - return rune(0x2a6d), true + return {'\u2a6d', 0}, 1, true case "conint": // CONTOUR INTEGRAL - return rune(0x222e), true + return {'\u222e', 0}, 1, true case "copf": // MATHEMATICAL DOUBLE-STRUCK SMALL C - return rune(0x01d554), true + return {'\U0001d554', 0}, 1, true case "coprod": // N-ARY COPRODUCT - return rune(0x2210), true + return {'\u2210', 0}, 1, true case "copy": // COPYRIGHT SIGN - return rune(0xa9), true + return {'©', 0}, 1, true case "copysr": // SOUND RECORDING COPYRIGHT - return rune(0x2117), true + return {'\u2117', 0}, 1, true case "crarr": // DOWNWARDS ARROW WITH CORNER LEFTWARDS - return rune(0x21b5), true + return {'\u21b5', 0}, 1, true case "cross": // BALLOT X - return rune(0x2717), true + return {'\u2717', 0}, 1, true case "cscr": // MATHEMATICAL SCRIPT SMALL C - return rune(0x01d4b8), true + return {'\U0001d4b8', 0}, 1, true case "csub": // CLOSED SUBSET - return rune(0x2acf), true + return {'\u2acf', 0}, 1, true case "csube": // CLOSED SUBSET OR EQUAL TO - return rune(0x2ad1), true + return {'\u2ad1', 0}, 1, true case "csup": // CLOSED SUPERSET - return rune(0x2ad0), true + return {'\u2ad0', 0}, 1, true case "csupe": // CLOSED SUPERSET OR EQUAL TO - return rune(0x2ad2), true + return {'\u2ad2', 0}, 1, true case "ctdot": // MIDLINE HORIZONTAL ELLIPSIS - return rune(0x22ef), true + return {'\u22ef', 0}, 1, true case "cudarrl": // RIGHT-SIDE ARC CLOCKWISE ARROW - return rune(0x2938), true + return {'\u2938', 0}, 1, true case "cudarrr": // ARROW POINTING RIGHTWARDS THEN CURVING DOWNWARDS - return rune(0x2935), true + return {'\u2935', 0}, 1, true case "cuepr": // EQUAL TO OR PRECEDES - return rune(0x22de), true + return {'\u22de', 0}, 1, true case "cuesc": // EQUAL TO OR SUCCEEDS - return rune(0x22df), true + return {'\u22df', 0}, 1, true case "cularr": // ANTICLOCKWISE TOP SEMICIRCLE ARROW - return rune(0x21b6), true + return {'\u21b6', 0}, 1, true case "cularrp": // TOP ARC ANTICLOCKWISE ARROW WITH PLUS - return rune(0x293d), true + return {'\u293d', 0}, 1, true case "cup": // UNION - return rune(0x222a), true + return {'\u222a', 0}, 1, true case "cupbrcap": // UNION ABOVE BAR ABOVE INTERSECTION - return rune(0x2a48), true + return {'\u2a48', 0}, 1, true case "cupcap": // UNION ABOVE INTERSECTION - return rune(0x2a46), true + return {'\u2a46', 0}, 1, true case "cupcup": // UNION BESIDE AND JOINED WITH UNION - return rune(0x2a4a), true + return {'\u2a4a', 0}, 1, true case "cupdot": // MULTISET MULTIPLICATION - return rune(0x228d), true + return {'\u228d', 0}, 1, true case "cupint": // INTEGRAL WITH UNION - return rune(0x2a1a), true + return {'\u2a1a', 0}, 1, true case "cupor": // UNION WITH LOGICAL OR - return rune(0x2a45), true + return {'\u2a45', 0}, 1, true case "cupre": // PRECEDES OR EQUAL TO - return rune(0x227c), true + return {'\u227c', 0}, 1, true case "cups": // UNION with serifs - return rune(0x222a), true + return {'\u222a', '\ufe00'}, 2, true case "curarr": // CLOCKWISE TOP SEMICIRCLE ARROW - return rune(0x21b7), true + return {'\u21b7', 0}, 1, true case "curarrm": // TOP ARC CLOCKWISE ARROW WITH MINUS - return rune(0x293c), true + return {'\u293c', 0}, 1, true case "curlyeqprec": // EQUAL TO OR PRECEDES - return rune(0x22de), true + return {'\u22de', 0}, 1, true case "curlyeqsucc": // EQUAL TO OR SUCCEEDS - return rune(0x22df), true + return {'\u22df', 0}, 1, true case "curlyvee": // CURLY LOGICAL OR - return rune(0x22ce), true + return {'\u22ce', 0}, 1, true case "curlywedge": // CURLY LOGICAL AND - return rune(0x22cf), true + return {'\u22cf', 0}, 1, true case "curren": // CURRENCY SIGN - return rune(0xa4), true + return {'¤', 0}, 1, true case "curvearrowleft": // ANTICLOCKWISE TOP SEMICIRCLE ARROW - return rune(0x21b6), true + return {'\u21b6', 0}, 1, true case "curvearrowright": // CLOCKWISE TOP SEMICIRCLE ARROW - return rune(0x21b7), true + return {'\u21b7', 0}, 1, true case "cuvee": // CURLY LOGICAL OR - return rune(0x22ce), true + return {'\u22ce', 0}, 1, true case "cuwed": // CURLY LOGICAL AND - return rune(0x22cf), true + return {'\u22cf', 0}, 1, true case "cwconint": // CLOCKWISE CONTOUR INTEGRAL - return rune(0x2232), true + return {'\u2232', 0}, 1, true case "cwint": // CLOCKWISE INTEGRAL - return rune(0x2231), true + return {'\u2231', 0}, 1, true case "cylcty": // CYLINDRICITY - return rune(0x232d), true + return {'\u232d', 0}, 1, true } case 'd': switch name { case "dAarr": // DOWNWARDS TRIPLE ARROW - return rune(0x290b), true + return {'\u290b', 0}, 1, true case "dArr": // DOWNWARDS DOUBLE ARROW - return rune(0x21d3), true + return {'\u21d3', 0}, 1, true case "dHar": // DOWNWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT - return rune(0x2965), true + return {'\u2965', 0}, 1, true case "dagger": // DAGGER - return rune(0x2020), true + return {'\u2020', 0}, 1, true case "dalembrt": // SQUARE WITH CONTOURED OUTLINE - return rune(0x29e0), true + return {'\u29e0', 0}, 1, true case "daleth": // DALET SYMBOL - return rune(0x2138), true + return {'\u2138', 0}, 1, true case "darr": // DOWNWARDS ARROW - return rune(0x2193), true + return {'\u2193', 0}, 1, true case "darr2": // DOWNWARDS PAIRED ARROWS - return rune(0x21ca), true + return {'\u21ca', 0}, 1, true case "darrb": // DOWNWARDS ARROW TO BAR - return rune(0x2913), true + return {'\u2913', 0}, 1, true case "darrln": // DOWNWARDS ARROW WITH HORIZONTAL STROKE - return rune(0x2908), true + return {'\u2908', 0}, 1, true case "dash": // HYPHEN - return rune(0x2010), true + return {'\u2010', 0}, 1, true case "dashV": // DOUBLE VERTICAL BAR LEFT TURNSTILE - return rune(0x2ae3), true + return {'\u2ae3', 0}, 1, true case "dashv": // LEFT TACK - return rune(0x22a3), true + return {'\u22a3', 0}, 1, true case "dbkarow": // RIGHTWARDS TRIPLE DASH ARROW - return rune(0x290f), true + return {'\u290f', 0}, 1, true case "dblac": // DOUBLE ACUTE ACCENT - return rune(0x02dd), true + return {'\u02dd', 0}, 1, true case "dcaron": // LATIN SMALL LETTER D WITH CARON - return rune(0x010f), true + return {'\u010f', 0}, 1, true case "dcy": // CYRILLIC SMALL LETTER DE - return rune(0x0434), true + return {'\u0434', 0}, 1, true case "dd": // DOUBLE-STRUCK ITALIC SMALL D - return rune(0x2146), true + return {'\u2146', 0}, 1, true case "ddagger": // DOUBLE DAGGER - return rune(0x2021), true + return {'\u2021', 0}, 1, true case "ddarr": // DOWNWARDS PAIRED ARROWS - return rune(0x21ca), true + return {'\u21ca', 0}, 1, true case "ddotseq": // EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW - return rune(0x2a77), true + return {'\u2a77', 0}, 1, true case "deg": // DEGREE SIGN - return rune(0xb0), true + return {'°', 0}, 1, true case "delta": // GREEK SMALL LETTER DELTA - return rune(0x03b4), true + return {'\u03b4', 0}, 1, true case "demptyv": // EMPTY SET WITH OVERBAR - return rune(0x29b1), true + return {'\u29b1', 0}, 1, true case "dfisht": // DOWN FISH TAIL - return rune(0x297f), true + return {'\u297f', 0}, 1, true case "dfr": // MATHEMATICAL FRAKTUR SMALL D - return rune(0x01d521), true + return {'\U0001d521', 0}, 1, true case "dgr": // GREEK SMALL LETTER DELTA - return rune(0x03b4), true + return {'\u03b4', 0}, 1, true case "dharl": // DOWNWARDS HARPOON WITH BARB LEFTWARDS - return rune(0x21c3), true + return {'\u21c3', 0}, 1, true case "dharr": // DOWNWARDS HARPOON WITH BARB RIGHTWARDS - return rune(0x21c2), true + return {'\u21c2', 0}, 1, true case "diam": // DIAMOND OPERATOR - return rune(0x22c4), true + return {'\u22c4', 0}, 1, true case "diamdarr": // BLACK DIAMOND WITH DOWN ARROW - return rune(0x29ea), true + return {'\u29ea', 0}, 1, true case "diamerr": // ERROR-BARRED WHITE DIAMOND - return rune(0x29f0), true + return {'\u29f0', 0}, 1, true case "diamerrf": // ERROR-BARRED BLACK DIAMOND - return rune(0x29f1), true + return {'\u29f1', 0}, 1, true case "diamond": // DIAMOND OPERATOR - return rune(0x22c4), true + return {'\u22c4', 0}, 1, true case "diamondsuit": // BLACK DIAMOND SUIT - return rune(0x2666), true + return {'\u2666', 0}, 1, true case "diams": // BLACK DIAMOND SUIT - return rune(0x2666), true + return {'\u2666', 0}, 1, true case "die": // DIAERESIS - return rune(0xa8), true + return {'¨', 0}, 1, true case "digamma": // GREEK SMALL LETTER DIGAMMA - return rune(0x03dd), true + return {'\u03dd', 0}, 1, true case "disin": // ELEMENT OF WITH LONG HORIZONTAL STROKE - return rune(0x22f2), true + return {'\u22f2', 0}, 1, true case "div": // DIVISION SIGN - return rune(0xf7), true + return {'÷', 0}, 1, true case "divide": // DIVISION SIGN - return rune(0xf7), true + return {'÷', 0}, 1, true case "divideontimes": // DIVISION TIMES - return rune(0x22c7), true + return {'\u22c7', 0}, 1, true case "divonx": // DIVISION TIMES - return rune(0x22c7), true + return {'\u22c7', 0}, 1, true case "djcy": // CYRILLIC SMALL LETTER DJE - return rune(0x0452), true + return {'\u0452', 0}, 1, true case "dlarr": // SOUTH WEST ARROW - return rune(0x2199), true + return {'\u2199', 0}, 1, true case "dlcorn": // BOTTOM LEFT CORNER - return rune(0x231e), true + return {'\u231e', 0}, 1, true case "dlcrop": // BOTTOM LEFT CROP - return rune(0x230d), true + return {'\u230d', 0}, 1, true case "dlharb": // DOWNWARDS HARPOON WITH BARB LEFT TO BAR - return rune(0x2959), true + return {'\u2959', 0}, 1, true case "dollar": // DOLLAR SIGN - return rune(0x24), true + return {'$', 0}, 1, true case "dopf": // MATHEMATICAL DOUBLE-STRUCK SMALL D - return rune(0x01d555), true + return {'\U0001d555', 0}, 1, true case "dot": // DOT ABOVE - return rune(0x02d9), true + return {'\u02d9', 0}, 1, true case "doteq": // APPROACHES THE LIMIT - return rune(0x2250), true + return {'\u2250', 0}, 1, true case "doteqdot": // GEOMETRICALLY EQUAL TO - return rune(0x2251), true + return {'\u2251', 0}, 1, true case "dotminus": // DOT MINUS - return rune(0x2238), true + return {'\u2238', 0}, 1, true case "dotplus": // DOT PLUS - return rune(0x2214), true + return {'\u2214', 0}, 1, true case "dotsquare": // SQUARED DOT OPERATOR - return rune(0x22a1), true + return {'\u22a1', 0}, 1, true case "doublebarwedge": // PERSPECTIVE - return rune(0x2306), true + return {'\u2306', 0}, 1, true case "downarrow": // DOWNWARDS ARROW - return rune(0x2193), true + return {'\u2193', 0}, 1, true case "downdownarrows": // DOWNWARDS PAIRED ARROWS - return rune(0x21ca), true + return {'\u21ca', 0}, 1, true case "downharpoonleft": // DOWNWARDS HARPOON WITH BARB LEFTWARDS - return rune(0x21c3), true + return {'\u21c3', 0}, 1, true case "downharpoonright": // DOWNWARDS HARPOON WITH BARB RIGHTWARDS - return rune(0x21c2), true + return {'\u21c2', 0}, 1, true case "drarr": // SOUTH EAST ARROW - return rune(0x2198), true + return {'\u2198', 0}, 1, true case "drbkarow": // RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW - return rune(0x2910), true + return {'\u2910', 0}, 1, true case "drcorn": // BOTTOM RIGHT CORNER - return rune(0x231f), true + return {'\u231f', 0}, 1, true case "drcrop": // BOTTOM RIGHT CROP - return rune(0x230c), true + return {'\u230c', 0}, 1, true case "drharb": // DOWNWARDS HARPOON WITH BARB RIGHT TO BAR - return rune(0x2955), true + return {'\u2955', 0}, 1, true case "dscr": // MATHEMATICAL SCRIPT SMALL D - return rune(0x01d4b9), true + return {'\U0001d4b9', 0}, 1, true case "dscy": // CYRILLIC SMALL LETTER DZE - return rune(0x0455), true + return {'\u0455', 0}, 1, true case "dsol": // SOLIDUS WITH OVERBAR - return rune(0x29f6), true + return {'\u29f6', 0}, 1, true case "dstrok": // LATIN SMALL LETTER D WITH STROKE - return rune(0x0111), true + return {'\u0111', 0}, 1, true case "dtdot": // DOWN RIGHT DIAGONAL ELLIPSIS - return rune(0x22f1), true + return {'\u22f1', 0}, 1, true case "dtri": // WHITE DOWN-POINTING SMALL TRIANGLE - return rune(0x25bf), true + return {'\u25bf', 0}, 1, true case "dtrif": // BLACK DOWN-POINTING SMALL TRIANGLE - return rune(0x25be), true + return {'\u25be', 0}, 1, true case "dtrilf": // DOWN-POINTING TRIANGLE WITH LEFT HALF BLACK - return rune(0x29e8), true + return {'\u29e8', 0}, 1, true case "dtrirf": // DOWN-POINTING TRIANGLE WITH RIGHT HALF BLACK - return rune(0x29e9), true + return {'\u29e9', 0}, 1, true case "duarr": // DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW - return rune(0x21f5), true + return {'\u21f5', 0}, 1, true case "duhar": // DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT - return rune(0x296f), true + return {'\u296f', 0}, 1, true case "dumap": // DOUBLE-ENDED MULTIMAP - return rune(0x29df), true + return {'\u29df', 0}, 1, true case "dwangle": // OBLIQUE ANGLE OPENING UP - return rune(0x29a6), true + return {'\u29a6', 0}, 1, true case "dzcy": // CYRILLIC SMALL LETTER DZHE - return rune(0x045f), true + return {'\u045f', 0}, 1, true case "dzigrarr": // LONG RIGHTWARDS SQUIGGLE ARROW - return rune(0x27ff), true + return {'\u27ff', 0}, 1, true } case 'e': switch name { case "eDDot": // EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW - return rune(0x2a77), true + return {'\u2a77', 0}, 1, true case "eDot": // GEOMETRICALLY EQUAL TO - return rune(0x2251), true + return {'\u2251', 0}, 1, true case "eacgr": // GREEK SMALL LETTER EPSILON WITH TONOS - return rune(0x03ad), true + return {'\u03ad', 0}, 1, true case "eacute": // LATIN SMALL LETTER E WITH ACUTE - return rune(0xe9), true + return {'é', 0}, 1, true case "easter": // EQUALS WITH ASTERISK - return rune(0x2a6e), true + return {'\u2a6e', 0}, 1, true case "ecaron": // LATIN SMALL LETTER E WITH CARON - return rune(0x011b), true + return {'\u011b', 0}, 1, true case "ecir": // RING IN EQUAL TO - return rune(0x2256), true + return {'\u2256', 0}, 1, true case "ecirc": // LATIN SMALL LETTER E WITH CIRCUMFLEX - return rune(0xea), true + return {'ê', 0}, 1, true case "ecolon": // EQUALS COLON - return rune(0x2255), true + return {'\u2255', 0}, 1, true case "ecy": // CYRILLIC SMALL LETTER E - return rune(0x044d), true + return {'\u044d', 0}, 1, true case "edot": // LATIN SMALL LETTER E WITH DOT ABOVE - return rune(0x0117), true + return {'\u0117', 0}, 1, true case "ee": // DOUBLE-STRUCK ITALIC SMALL E - return rune(0x2147), true + return {'\u2147', 0}, 1, true case "eeacgr": // GREEK SMALL LETTER ETA WITH TONOS - return rune(0x03ae), true + return {'\u03ae', 0}, 1, true case "eegr": // GREEK SMALL LETTER ETA - return rune(0x03b7), true + return {'\u03b7', 0}, 1, true case "efDot": // APPROXIMATELY EQUAL TO OR THE IMAGE OF - return rune(0x2252), true + return {'\u2252', 0}, 1, true case "efr": // MATHEMATICAL FRAKTUR SMALL E - return rune(0x01d522), true + return {'\U0001d522', 0}, 1, true case "eg": // DOUBLE-LINE EQUAL TO OR GREATER-THAN - return rune(0x2a9a), true + return {'\u2a9a', 0}, 1, true case "egr": // GREEK SMALL LETTER EPSILON - return rune(0x03b5), true + return {'\u03b5', 0}, 1, true case "egrave": // LATIN SMALL LETTER E WITH GRAVE - return rune(0xe8), true + return {'è', 0}, 1, true case "egs": // SLANTED EQUAL TO OR GREATER-THAN - return rune(0x2a96), true + return {'\u2a96', 0}, 1, true case "egsdot": // SLANTED EQUAL TO OR GREATER-THAN WITH DOT INSIDE - return rune(0x2a98), true + return {'\u2a98', 0}, 1, true case "el": // DOUBLE-LINE EQUAL TO OR LESS-THAN - return rune(0x2a99), true + return {'\u2a99', 0}, 1, true case "elinters": // ELECTRICAL INTERSECTION - return rune(0x23e7), true + return {'\u23e7', 0}, 1, true case "ell": // SCRIPT SMALL L - return rune(0x2113), true + return {'\u2113', 0}, 1, true case "els": // SLANTED EQUAL TO OR LESS-THAN - return rune(0x2a95), true + return {'\u2a95', 0}, 1, true case "elsdot": // SLANTED EQUAL TO OR LESS-THAN WITH DOT INSIDE - return rune(0x2a97), true + return {'\u2a97', 0}, 1, true case "emacr": // LATIN SMALL LETTER E WITH MACRON - return rune(0x0113), true + return {'\u0113', 0}, 1, true case "empty": // EMPTY SET - return rune(0x2205), true + return {'\u2205', 0}, 1, true case "emptyset": // EMPTY SET - return rune(0x2205), true + return {'\u2205', 0}, 1, true case "emptyv": // EMPTY SET - return rune(0x2205), true + return {'\u2205', 0}, 1, true case "emsp": // EM SPACE - return rune(0x2003), true + return {'\u2003', 0}, 1, true case "emsp13": // THREE-PER-EM SPACE - return rune(0x2004), true + return {'\u2004', 0}, 1, true case "emsp14": // FOUR-PER-EM SPACE - return rune(0x2005), true + return {'\u2005', 0}, 1, true case "eng": // LATIN SMALL LETTER ENG - return rune(0x014b), true + return {'\u014b', 0}, 1, true case "ensp": // EN SPACE - return rune(0x2002), true + return {'\u2002', 0}, 1, true case "eogon": // LATIN SMALL LETTER E WITH OGONEK - return rune(0x0119), true + return {'\u0119', 0}, 1, true case "eopf": // MATHEMATICAL DOUBLE-STRUCK SMALL E - return rune(0x01d556), true + return {'\U0001d556', 0}, 1, true case "epar": // EQUAL AND PARALLEL TO - return rune(0x22d5), true + return {'\u22d5', 0}, 1, true case "eparsl": // EQUALS SIGN AND SLANTED PARALLEL - return rune(0x29e3), true + return {'\u29e3', 0}, 1, true case "eplus": // EQUALS SIGN ABOVE PLUS SIGN - return rune(0x2a71), true + return {'\u2a71', 0}, 1, true case "epsi": // GREEK SMALL LETTER EPSILON - return rune(0x03b5), true + return {'\u03b5', 0}, 1, true case "epsilon": // GREEK SMALL LETTER EPSILON - return rune(0x03b5), true + return {'\u03b5', 0}, 1, true case "epsis": // GREEK LUNATE EPSILON SYMBOL - return rune(0x03f5), true + return {'\u03f5', 0}, 1, true case "epsiv": // GREEK LUNATE EPSILON SYMBOL - return rune(0x03f5), true + return {'\u03f5', 0}, 1, true case "eqcirc": // RING IN EQUAL TO - return rune(0x2256), true + return {'\u2256', 0}, 1, true case "eqcolon": // EQUALS COLON - return rune(0x2255), true + return {'\u2255', 0}, 1, true case "eqeq": // TWO CONSECUTIVE EQUALS SIGNS - return rune(0x2a75), true + return {'\u2a75', 0}, 1, true case "eqsim": // MINUS TILDE - return rune(0x2242), true + return {'\u2242', 0}, 1, true case "eqslantgtr": // SLANTED EQUAL TO OR GREATER-THAN - return rune(0x2a96), true + return {'\u2a96', 0}, 1, true case "eqslantless": // SLANTED EQUAL TO OR LESS-THAN - return rune(0x2a95), true + return {'\u2a95', 0}, 1, true case "equals": // EQUALS SIGN - return rune(0x3d), true + return {'=', 0}, 1, true case "equest": // QUESTIONED EQUAL TO - return rune(0x225f), true + return {'\u225f', 0}, 1, true case "equiv": // IDENTICAL TO - return rune(0x2261), true + return {'\u2261', 0}, 1, true case "equivDD": // EQUIVALENT WITH FOUR DOTS ABOVE - return rune(0x2a78), true + return {'\u2a78', 0}, 1, true case "eqvparsl": // IDENTICAL TO AND SLANTED PARALLEL - return rune(0x29e5), true + return {'\u29e5', 0}, 1, true case "erDot": // IMAGE OF OR APPROXIMATELY EQUAL TO - return rune(0x2253), true + return {'\u2253', 0}, 1, true case "erarr": // EQUALS SIGN ABOVE RIGHTWARDS ARROW - return rune(0x2971), true + return {'\u2971', 0}, 1, true case "escr": // SCRIPT SMALL E - return rune(0x212f), true + return {'\u212f', 0}, 1, true case "esdot": // APPROACHES THE LIMIT - return rune(0x2250), true + return {'\u2250', 0}, 1, true case "esim": // MINUS TILDE - return rune(0x2242), true + return {'\u2242', 0}, 1, true case "eta": // GREEK SMALL LETTER ETA - return rune(0x03b7), true + return {'\u03b7', 0}, 1, true case "eth": // LATIN SMALL LETTER ETH - return rune(0xf0), true + return {'ð', 0}, 1, true case "euml": // LATIN SMALL LETTER E WITH DIAERESIS - return rune(0xeb), true + return {'ë', 0}, 1, true case "euro": // EURO SIGN - return rune(0x20ac), true + return {'\u20ac', 0}, 1, true case "excl": // EXCLAMATION MARK - return rune(0x21), true + return {'!', 0}, 1, true case "exist": // THERE EXISTS - return rune(0x2203), true + return {'\u2203', 0}, 1, true case "expectation": // SCRIPT CAPITAL E - return rune(0x2130), true + return {'\u2130', 0}, 1, true case "exponentiale": // DOUBLE-STRUCK ITALIC SMALL E - return rune(0x2147), true + return {'\u2147', 0}, 1, true } case 'f': switch name { case "fallingdotseq": // APPROXIMATELY EQUAL TO OR THE IMAGE OF - return rune(0x2252), true + return {'\u2252', 0}, 1, true case "fbowtie": // BLACK BOWTIE - return rune(0x29d3), true + return {'\u29d3', 0}, 1, true case "fcy": // CYRILLIC SMALL LETTER EF - return rune(0x0444), true + return {'\u0444', 0}, 1, true case "fdiag": // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT - return rune(0x2572), true + return {'\u2572', 0}, 1, true case "fdiordi": // FALLING DIAGONAL CROSSING RISING DIAGONAL - return rune(0x292c), true + return {'\u292c', 0}, 1, true case "fdonearr": // FALLING DIAGONAL CROSSING NORTH EAST ARROW - return rune(0x292f), true + return {'\u292f', 0}, 1, true case "female": // FEMALE SIGN - return rune(0x2640), true + return {'\u2640', 0}, 1, true case "ffilig": // LATIN SMALL LIGATURE FFI - return rune(0xfb03), true + return {'\ufb03', 0}, 1, true case "fflig": // LATIN SMALL LIGATURE FF - return rune(0xfb00), true + return {'\ufb00', 0}, 1, true case "ffllig": // LATIN SMALL LIGATURE FFL - return rune(0xfb04), true + return {'\ufb04', 0}, 1, true case "ffr": // MATHEMATICAL FRAKTUR SMALL F - return rune(0x01d523), true + return {'\U0001d523', 0}, 1, true case "fhrglass": // BLACK HOURGLASS - return rune(0x29d7), true + return {'\u29d7', 0}, 1, true case "filig": // LATIN SMALL LIGATURE FI - return rune(0xfb01), true + return {'\ufb01', 0}, 1, true case "fjlig": // fj ligature - return rune(0x66), true + return {'f', 'j'}, 2, true case "flat": // MUSIC FLAT SIGN - return rune(0x266d), true + return {'\u266d', 0}, 1, true case "fllig": // LATIN SMALL LIGATURE FL - return rune(0xfb02), true + return {'\ufb02', 0}, 1, true case "fltns": // WHITE PARALLELOGRAM - return rune(0x25b1), true + return {'\u25b1', 0}, 1, true case "fnof": // LATIN SMALL LETTER F WITH HOOK - return rune(0x0192), true + return {'\u0192', 0}, 1, true case "fopf": // MATHEMATICAL DOUBLE-STRUCK SMALL F - return rune(0x01d557), true + return {'\U0001d557', 0}, 1, true case "forall": // FOR ALL - return rune(0x2200), true + return {'\u2200', 0}, 1, true case "fork": // PITCHFORK - return rune(0x22d4), true + return {'\u22d4', 0}, 1, true case "forkv": // ELEMENT OF OPENING DOWNWARDS - return rune(0x2ad9), true + return {'\u2ad9', 0}, 1, true case "fpartint": // FINITE PART INTEGRAL - return rune(0x2a0d), true + return {'\u2a0d', 0}, 1, true case "frac12": // VULGAR FRACTION ONE HALF - return rune(0xbd), true + return {'½', 0}, 1, true case "frac13": // VULGAR FRACTION ONE THIRD - return rune(0x2153), true + return {'\u2153', 0}, 1, true case "frac14": // VULGAR FRACTION ONE QUARTER - return rune(0xbc), true + return {'¼', 0}, 1, true case "frac15": // VULGAR FRACTION ONE FIFTH - return rune(0x2155), true + return {'\u2155', 0}, 1, true case "frac16": // VULGAR FRACTION ONE SIXTH - return rune(0x2159), true + return {'\u2159', 0}, 1, true case "frac18": // VULGAR FRACTION ONE EIGHTH - return rune(0x215b), true + return {'\u215b', 0}, 1, true case "frac23": // VULGAR FRACTION TWO THIRDS - return rune(0x2154), true + return {'\u2154', 0}, 1, true case "frac25": // VULGAR FRACTION TWO FIFTHS - return rune(0x2156), true + return {'\u2156', 0}, 1, true case "frac34": // VULGAR FRACTION THREE QUARTERS - return rune(0xbe), true + return {'¾', 0}, 1, true case "frac35": // VULGAR FRACTION THREE FIFTHS - return rune(0x2157), true + return {'\u2157', 0}, 1, true case "frac38": // VULGAR FRACTION THREE EIGHTHS - return rune(0x215c), true + return {'\u215c', 0}, 1, true case "frac45": // VULGAR FRACTION FOUR FIFTHS - return rune(0x2158), true + return {'\u2158', 0}, 1, true case "frac56": // VULGAR FRACTION FIVE SIXTHS - return rune(0x215a), true + return {'\u215a', 0}, 1, true case "frac58": // VULGAR FRACTION FIVE EIGHTHS - return rune(0x215d), true + return {'\u215d', 0}, 1, true case "frac78": // VULGAR FRACTION SEVEN EIGHTHS - return rune(0x215e), true + return {'\u215e', 0}, 1, true case "frasl": // FRACTION SLASH - return rune(0x2044), true + return {'\u2044', 0}, 1, true case "frown": // FROWN - return rune(0x2322), true + return {'\u2322', 0}, 1, true case "fscr": // MATHEMATICAL SCRIPT SMALL F - return rune(0x01d4bb), true + return {'\U0001d4bb', 0}, 1, true } case 'g': switch name { case "gE": // GREATER-THAN OVER EQUAL TO - return rune(0x2267), true + return {'\u2267', 0}, 1, true case "gEl": // GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN - return rune(0x2a8c), true + return {'\u2a8c', 0}, 1, true case "gacute": // LATIN SMALL LETTER G WITH ACUTE - return rune(0x01f5), true + return {'\u01f5', 0}, 1, true case "gamma": // GREEK SMALL LETTER GAMMA - return rune(0x03b3), true + return {'\u03b3', 0}, 1, true case "gammad": // GREEK SMALL LETTER DIGAMMA - return rune(0x03dd), true + return {'\u03dd', 0}, 1, true case "gap": // GREATER-THAN OR APPROXIMATE - return rune(0x2a86), true + return {'\u2a86', 0}, 1, true case "gbreve": // LATIN SMALL LETTER G WITH BREVE - return rune(0x011f), true + return {'\u011f', 0}, 1, true case "gcedil": // LATIN SMALL LETTER G WITH CEDILLA - return rune(0x0123), true + return {'\u0123', 0}, 1, true case "gcirc": // LATIN SMALL LETTER G WITH CIRCUMFLEX - return rune(0x011d), true + return {'\u011d', 0}, 1, true case "gcy": // CYRILLIC SMALL LETTER GHE - return rune(0x0433), true + return {'\u0433', 0}, 1, true case "gdot": // LATIN SMALL LETTER G WITH DOT ABOVE - return rune(0x0121), true + return {'\u0121', 0}, 1, true case "ge": // GREATER-THAN OR EQUAL TO - return rune(0x2265), true + return {'\u2265', 0}, 1, true case "gel": // GREATER-THAN EQUAL TO OR LESS-THAN - return rune(0x22db), true + return {'\u22db', 0}, 1, true case "geq": // GREATER-THAN OR EQUAL TO - return rune(0x2265), true + return {'\u2265', 0}, 1, true case "geqq": // GREATER-THAN OVER EQUAL TO - return rune(0x2267), true + return {'\u2267', 0}, 1, true case "geqslant": // GREATER-THAN OR SLANTED EQUAL TO - return rune(0x2a7e), true + return {'\u2a7e', 0}, 1, true case "ges": // GREATER-THAN OR SLANTED EQUAL TO - return rune(0x2a7e), true + return {'\u2a7e', 0}, 1, true case "gescc": // GREATER-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL - return rune(0x2aa9), true + return {'\u2aa9', 0}, 1, true case "gesdot": // GREATER-THAN OR SLANTED EQUAL TO WITH DOT INSIDE - return rune(0x2a80), true + return {'\u2a80', 0}, 1, true case "gesdoto": // GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE - return rune(0x2a82), true + return {'\u2a82', 0}, 1, true case "gesdotol": // GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE LEFT - return rune(0x2a84), true + return {'\u2a84', 0}, 1, true case "gesl": // GREATER-THAN slanted EQUAL TO OR LESS-THAN - return rune(0x22db), true + return {'\u22db', '\ufe00'}, 2, true case "gesles": // GREATER-THAN ABOVE SLANTED EQUAL ABOVE LESS-THAN ABOVE SLANTED EQUAL - return rune(0x2a94), true + return {'\u2a94', 0}, 1, true case "gfr": // MATHEMATICAL FRAKTUR SMALL G - return rune(0x01d524), true + return {'\U0001d524', 0}, 1, true case "gg": // MUCH GREATER-THAN - return rune(0x226b), true + return {'\u226b', 0}, 1, true case "ggg": // VERY MUCH GREATER-THAN - return rune(0x22d9), true + return {'\u22d9', 0}, 1, true case "ggr": // GREEK SMALL LETTER GAMMA - return rune(0x03b3), true + return {'\u03b3', 0}, 1, true case "gimel": // GIMEL SYMBOL - return rune(0x2137), true + return {'\u2137', 0}, 1, true case "gjcy": // CYRILLIC SMALL LETTER GJE - return rune(0x0453), true + return {'\u0453', 0}, 1, true case "gl": // GREATER-THAN OR LESS-THAN - return rune(0x2277), true + return {'\u2277', 0}, 1, true case "glE": // GREATER-THAN ABOVE LESS-THAN ABOVE DOUBLE-LINE EQUAL - return rune(0x2a92), true + return {'\u2a92', 0}, 1, true case "gla": // GREATER-THAN BESIDE LESS-THAN - return rune(0x2aa5), true + return {'\u2aa5', 0}, 1, true case "glj": // GREATER-THAN OVERLAPPING LESS-THAN - return rune(0x2aa4), true + return {'\u2aa4', 0}, 1, true case "gnE": // GREATER-THAN BUT NOT EQUAL TO - return rune(0x2269), true + return {'\u2269', 0}, 1, true case "gnap": // GREATER-THAN AND NOT APPROXIMATE - return rune(0x2a8a), true + return {'\u2a8a', 0}, 1, true case "gnapprox": // GREATER-THAN AND NOT APPROXIMATE - return rune(0x2a8a), true + return {'\u2a8a', 0}, 1, true case "gne": // GREATER-THAN AND SINGLE-LINE NOT EQUAL TO - return rune(0x2a88), true + return {'\u2a88', 0}, 1, true case "gneq": // GREATER-THAN AND SINGLE-LINE NOT EQUAL TO - return rune(0x2a88), true + return {'\u2a88', 0}, 1, true case "gneqq": // GREATER-THAN BUT NOT EQUAL TO - return rune(0x2269), true + return {'\u2269', 0}, 1, true case "gnsim": // GREATER-THAN BUT NOT EQUIVALENT TO - return rune(0x22e7), true + return {'\u22e7', 0}, 1, true case "gopf": // MATHEMATICAL DOUBLE-STRUCK SMALL G - return rune(0x01d558), true + return {'\U0001d558', 0}, 1, true case "grave": // GRAVE ACCENT - return rune(0x60), true + return {'`', 0}, 1, true case "gscr": // SCRIPT SMALL G - return rune(0x210a), true + return {'\u210a', 0}, 1, true case "gsdot": // GREATER-THAN WITH DOT - return rune(0x22d7), true + return {'\u22d7', 0}, 1, true case "gsim": // GREATER-THAN OR EQUIVALENT TO - return rune(0x2273), true + return {'\u2273', 0}, 1, true case "gsime": // GREATER-THAN ABOVE SIMILAR OR EQUAL - return rune(0x2a8e), true + return {'\u2a8e', 0}, 1, true case "gsiml": // GREATER-THAN ABOVE SIMILAR ABOVE LESS-THAN - return rune(0x2a90), true + return {'\u2a90', 0}, 1, true case "gt": // GREATER-THAN SIGN - return rune(0x3e), true + return {'>', 0}, 1, true case "gtcc": // GREATER-THAN CLOSED BY CURVE - return rune(0x2aa7), true + return {'\u2aa7', 0}, 1, true case "gtcir": // GREATER-THAN WITH CIRCLE INSIDE - return rune(0x2a7a), true + return {'\u2a7a', 0}, 1, true case "gtdot": // GREATER-THAN WITH DOT - return rune(0x22d7), true + return {'\u22d7', 0}, 1, true case "gtlPar": // DOUBLE LEFT ARC GREATER-THAN BRACKET - return rune(0x2995), true + return {'\u2995', 0}, 1, true case "gtquest": // GREATER-THAN WITH QUESTION MARK ABOVE - return rune(0x2a7c), true + return {'\u2a7c', 0}, 1, true case "gtrapprox": // GREATER-THAN OR APPROXIMATE - return rune(0x2a86), true + return {'\u2a86', 0}, 1, true case "gtrarr": // GREATER-THAN ABOVE RIGHTWARDS ARROW - return rune(0x2978), true + return {'\u2978', 0}, 1, true case "gtrdot": // GREATER-THAN WITH DOT - return rune(0x22d7), true + return {'\u22d7', 0}, 1, true case "gtreqless": // GREATER-THAN EQUAL TO OR LESS-THAN - return rune(0x22db), true + return {'\u22db', 0}, 1, true case "gtreqqless": // GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN - return rune(0x2a8c), true + return {'\u2a8c', 0}, 1, true case "gtrless": // GREATER-THAN OR LESS-THAN - return rune(0x2277), true + return {'\u2277', 0}, 1, true case "gtrpar": // SPHERICAL ANGLE OPENING LEFT - return rune(0x29a0), true + return {'\u29a0', 0}, 1, true case "gtrsim": // GREATER-THAN OR EQUIVALENT TO - return rune(0x2273), true + return {'\u2273', 0}, 1, true case "gvertneqq": // GREATER-THAN BUT NOT EQUAL TO - with vertical stroke - return rune(0x2269), true + return {'\u2269', '\ufe00'}, 2, true case "gvnE": // GREATER-THAN BUT NOT EQUAL TO - with vertical stroke - return rune(0x2269), true + return {'\u2269', '\ufe00'}, 2, true } case 'h': switch name { case "hArr": // LEFT RIGHT DOUBLE ARROW - return rune(0x21d4), true + return {'\u21d4', 0}, 1, true case "hairsp": // HAIR SPACE - return rune(0x200a), true + return {'\u200a', 0}, 1, true case "half": // VULGAR FRACTION ONE HALF - return rune(0xbd), true + return {'½', 0}, 1, true case "hamilt": // SCRIPT CAPITAL H - return rune(0x210b), true + return {'\u210b', 0}, 1, true case "hardcy": // CYRILLIC SMALL LETTER HARD SIGN - return rune(0x044a), true + return {'\u044a', 0}, 1, true case "harr": // LEFT RIGHT ARROW - return rune(0x2194), true + return {'\u2194', 0}, 1, true case "harrcir": // LEFT RIGHT ARROW THROUGH SMALL CIRCLE - return rune(0x2948), true + return {'\u2948', 0}, 1, true case "harrw": // LEFT RIGHT WAVE ARROW - return rune(0x21ad), true + return {'\u21ad', 0}, 1, true case "hbar": // PLANCK CONSTANT OVER TWO PI - return rune(0x210f), true + return {'\u210f', 0}, 1, true case "hcirc": // LATIN SMALL LETTER H WITH CIRCUMFLEX - return rune(0x0125), true + return {'\u0125', 0}, 1, true case "hearts": // BLACK HEART SUIT - return rune(0x2665), true + return {'\u2665', 0}, 1, true case "heartsuit": // BLACK HEART SUIT - return rune(0x2665), true + return {'\u2665', 0}, 1, true case "hellip": // HORIZONTAL ELLIPSIS - return rune(0x2026), true + return {'\u2026', 0}, 1, true case "hercon": // HERMITIAN CONJUGATE MATRIX - return rune(0x22b9), true + return {'\u22b9', 0}, 1, true case "hfr": // MATHEMATICAL FRAKTUR SMALL H - return rune(0x01d525), true + return {'\U0001d525', 0}, 1, true case "hksearow": // SOUTH EAST ARROW WITH HOOK - return rune(0x2925), true + return {'\u2925', 0}, 1, true case "hkswarow": // SOUTH WEST ARROW WITH HOOK - return rune(0x2926), true + return {'\u2926', 0}, 1, true case "hoarr": // LEFT RIGHT OPEN-HEADED ARROW - return rune(0x21ff), true + return {'\u21ff', 0}, 1, true case "homtht": // HOMOTHETIC - return rune(0x223b), true + return {'\u223b', 0}, 1, true case "hookleftarrow": // LEFTWARDS ARROW WITH HOOK - return rune(0x21a9), true + return {'\u21a9', 0}, 1, true case "hookrightarrow": // RIGHTWARDS ARROW WITH HOOK - return rune(0x21aa), true + return {'\u21aa', 0}, 1, true case "hopf": // MATHEMATICAL DOUBLE-STRUCK SMALL H - return rune(0x01d559), true + return {'\U0001d559', 0}, 1, true case "horbar": // HORIZONTAL BAR - return rune(0x2015), true + return {'\u2015', 0}, 1, true case "hrglass": // WHITE HOURGLASS - return rune(0x29d6), true + return {'\u29d6', 0}, 1, true case "hscr": // MATHEMATICAL SCRIPT SMALL H - return rune(0x01d4bd), true + return {'\U0001d4bd', 0}, 1, true case "hslash": // PLANCK CONSTANT OVER TWO PI - return rune(0x210f), true + return {'\u210f', 0}, 1, true case "hstrok": // LATIN SMALL LETTER H WITH STROKE - return rune(0x0127), true + return {'\u0127', 0}, 1, true case "htimes": // VECTOR OR CROSS PRODUCT - return rune(0x2a2f), true + return {'\u2a2f', 0}, 1, true case "hybull": // HYPHEN BULLET - return rune(0x2043), true + return {'\u2043', 0}, 1, true case "hyphen": // HYPHEN - return rune(0x2010), true + return {'\u2010', 0}, 1, true } case 'i': switch name { case "iacgr": // GREEK SMALL LETTER IOTA WITH TONOS - return rune(0x03af), true + return {'\u03af', 0}, 1, true case "iacute": // LATIN SMALL LETTER I WITH ACUTE - return rune(0xed), true + return {'í', 0}, 1, true case "ic": // INVISIBLE SEPARATOR - return rune(0x2063), true + return {'\u2063', 0}, 1, true case "icirc": // LATIN SMALL LETTER I WITH CIRCUMFLEX - return rune(0xee), true + return {'î', 0}, 1, true case "icy": // CYRILLIC SMALL LETTER I - return rune(0x0438), true + return {'\u0438', 0}, 1, true case "idiagr": // GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS - return rune(0x0390), true + return {'\u0390', 0}, 1, true case "idigr": // GREEK SMALL LETTER IOTA WITH DIALYTIKA - return rune(0x03ca), true + return {'\u03ca', 0}, 1, true case "iecy": // CYRILLIC SMALL LETTER IE - return rune(0x0435), true + return {'\u0435', 0}, 1, true case "iexcl": // INVERTED EXCLAMATION MARK - return rune(0xa1), true + return {'¡', 0}, 1, true case "iff": // LEFT RIGHT DOUBLE ARROW - return rune(0x21d4), true + return {'\u21d4', 0}, 1, true case "ifr": // MATHEMATICAL FRAKTUR SMALL I - return rune(0x01d526), true + return {'\U0001d526', 0}, 1, true case "igr": // GREEK SMALL LETTER IOTA - return rune(0x03b9), true + return {'\u03b9', 0}, 1, true case "igrave": // LATIN SMALL LETTER I WITH GRAVE - return rune(0xec), true + return {'ì', 0}, 1, true case "ii": // DOUBLE-STRUCK ITALIC SMALL I - return rune(0x2148), true + return {'\u2148', 0}, 1, true case "iiiint": // QUADRUPLE INTEGRAL OPERATOR - return rune(0x2a0c), true + return {'\u2a0c', 0}, 1, true case "iiint": // TRIPLE INTEGRAL - return rune(0x222d), true + return {'\u222d', 0}, 1, true case "iinfin": // INCOMPLETE INFINITY - return rune(0x29dc), true + return {'\u29dc', 0}, 1, true case "iiota": // TURNED GREEK SMALL LETTER IOTA - return rune(0x2129), true + return {'\u2129', 0}, 1, true case "ijlig": // LATIN SMALL LIGATURE IJ - return rune(0x0133), true + return {'\u0133', 0}, 1, true case "imacr": // LATIN SMALL LETTER I WITH MACRON - return rune(0x012b), true + return {'\u012b', 0}, 1, true case "image": // BLACK-LETTER CAPITAL I - return rune(0x2111), true + return {'\u2111', 0}, 1, true case "imagline": // SCRIPT CAPITAL I - return rune(0x2110), true + return {'\u2110', 0}, 1, true case "imagpart": // BLACK-LETTER CAPITAL I - return rune(0x2111), true + return {'\u2111', 0}, 1, true case "imath": // LATIN SMALL LETTER DOTLESS I - return rune(0x0131), true + return {'\u0131', 0}, 1, true case "imof": // IMAGE OF - return rune(0x22b7), true + return {'\u22b7', 0}, 1, true case "imped": // LATIN CAPITAL LETTER Z WITH STROKE - return rune(0x01b5), true + return {'\u01b5', 0}, 1, true case "in": // ELEMENT OF - return rune(0x2208), true + return {'\u2208', 0}, 1, true case "incare": // CARE OF - return rune(0x2105), true + return {'\u2105', 0}, 1, true case "infin": // INFINITY - return rune(0x221e), true + return {'\u221e', 0}, 1, true case "infintie": // TIE OVER INFINITY - return rune(0x29dd), true + return {'\u29dd', 0}, 1, true case "inodot": // LATIN SMALL LETTER DOTLESS I - return rune(0x0131), true + return {'\u0131', 0}, 1, true case "int": // INTEGRAL - return rune(0x222b), true + return {'\u222b', 0}, 1, true case "intcal": // INTERCALATE - return rune(0x22ba), true + return {'\u22ba', 0}, 1, true case "integers": // DOUBLE-STRUCK CAPITAL Z - return rune(0x2124), true + return {'\u2124', 0}, 1, true case "intercal": // INTERCALATE - return rune(0x22ba), true + return {'\u22ba', 0}, 1, true case "intlarhk": // INTEGRAL WITH LEFTWARDS ARROW WITH HOOK - return rune(0x2a17), true + return {'\u2a17', 0}, 1, true case "intprod": // INTERIOR PRODUCT - return rune(0x2a3c), true + return {'\u2a3c', 0}, 1, true case "iocy": // CYRILLIC SMALL LETTER IO - return rune(0x0451), true + return {'\u0451', 0}, 1, true case "iogon": // LATIN SMALL LETTER I WITH OGONEK - return rune(0x012f), true + return {'\u012f', 0}, 1, true case "iopf": // MATHEMATICAL DOUBLE-STRUCK SMALL I - return rune(0x01d55a), true + return {'\U0001d55a', 0}, 1, true case "iota": // GREEK SMALL LETTER IOTA - return rune(0x03b9), true + return {'\u03b9', 0}, 1, true case "iprod": // INTERIOR PRODUCT - return rune(0x2a3c), true + return {'\u2a3c', 0}, 1, true case "iprodr": // RIGHTHAND INTERIOR PRODUCT - return rune(0x2a3d), true + return {'\u2a3d', 0}, 1, true case "iquest": // INVERTED QUESTION MARK - return rune(0xbf), true + return {'¿', 0}, 1, true case "iscr": // MATHEMATICAL SCRIPT SMALL I - return rune(0x01d4be), true + return {'\U0001d4be', 0}, 1, true case "isin": // ELEMENT OF - return rune(0x2208), true + return {'\u2208', 0}, 1, true case "isinE": // ELEMENT OF WITH TWO HORIZONTAL STROKES - return rune(0x22f9), true + return {'\u22f9', 0}, 1, true case "isindot": // ELEMENT OF WITH DOT ABOVE - return rune(0x22f5), true + return {'\u22f5', 0}, 1, true case "isins": // SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE - return rune(0x22f4), true + return {'\u22f4', 0}, 1, true case "isinsv": // ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE - return rune(0x22f3), true + return {'\u22f3', 0}, 1, true case "isinv": // ELEMENT OF - return rune(0x2208), true + return {'\u2208', 0}, 1, true case "isinvb": // ELEMENT OF WITH UNDERBAR - return rune(0x22f8), true + return {'\u22f8', 0}, 1, true case "it": // INVISIBLE TIMES - return rune(0x2062), true + return {'\u2062', 0}, 1, true case "itilde": // LATIN SMALL LETTER I WITH TILDE - return rune(0x0129), true + return {'\u0129', 0}, 1, true case "iukcy": // CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I - return rune(0x0456), true + return {'\u0456', 0}, 1, true case "iuml": // LATIN SMALL LETTER I WITH DIAERESIS - return rune(0xef), true + return {'ï', 0}, 1, true } case 'j': switch name { case "jcirc": // LATIN SMALL LETTER J WITH CIRCUMFLEX - return rune(0x0135), true + return {'\u0135', 0}, 1, true case "jcy": // CYRILLIC SMALL LETTER SHORT I - return rune(0x0439), true + return {'\u0439', 0}, 1, true case "jfr": // MATHEMATICAL FRAKTUR SMALL J - return rune(0x01d527), true + return {'\U0001d527', 0}, 1, true case "jmath": // LATIN SMALL LETTER DOTLESS J - return rune(0x0237), true + return {'\u0237', 0}, 1, true case "jnodot": // LATIN SMALL LETTER DOTLESS J - return rune(0x0237), true + return {'\u0237', 0}, 1, true case "jopf": // MATHEMATICAL DOUBLE-STRUCK SMALL J - return rune(0x01d55b), true + return {'\U0001d55b', 0}, 1, true case "jscr": // MATHEMATICAL SCRIPT SMALL J - return rune(0x01d4bf), true + return {'\U0001d4bf', 0}, 1, true case "jsercy": // CYRILLIC SMALL LETTER JE - return rune(0x0458), true + return {'\u0458', 0}, 1, true case "jukcy": // CYRILLIC SMALL LETTER UKRAINIAN IE - return rune(0x0454), true + return {'\u0454', 0}, 1, true } case 'k': switch name { case "kappa": // GREEK SMALL LETTER KAPPA - return rune(0x03ba), true + return {'\u03ba', 0}, 1, true case "kappav": // GREEK KAPPA SYMBOL - return rune(0x03f0), true + return {'\u03f0', 0}, 1, true case "kcedil": // LATIN SMALL LETTER K WITH CEDILLA - return rune(0x0137), true + return {'\u0137', 0}, 1, true case "kcy": // CYRILLIC SMALL LETTER KA - return rune(0x043a), true + return {'\u043a', 0}, 1, true case "kfr": // MATHEMATICAL FRAKTUR SMALL K - return rune(0x01d528), true + return {'\U0001d528', 0}, 1, true case "kgr": // GREEK SMALL LETTER KAPPA - return rune(0x03ba), true + return {'\u03ba', 0}, 1, true case "kgreen": // LATIN SMALL LETTER KRA - return rune(0x0138), true + return {'\u0138', 0}, 1, true case "khcy": // CYRILLIC SMALL LETTER HA - return rune(0x0445), true + return {'\u0445', 0}, 1, true case "khgr": // GREEK SMALL LETTER CHI - return rune(0x03c7), true + return {'\u03c7', 0}, 1, true case "kjcy": // CYRILLIC SMALL LETTER KJE - return rune(0x045c), true + return {'\u045c', 0}, 1, true case "kopf": // MATHEMATICAL DOUBLE-STRUCK SMALL K - return rune(0x01d55c), true + return {'\U0001d55c', 0}, 1, true case "koppa": // GREEK LETTER KOPPA - return rune(0x03de), true + return {'\u03de', 0}, 1, true case "kscr": // MATHEMATICAL SCRIPT SMALL K - return rune(0x01d4c0), true + return {'\U0001d4c0', 0}, 1, true } case 'l': switch name { case "lAarr": // LEFTWARDS TRIPLE ARROW - return rune(0x21da), true + return {'\u21da', 0}, 1, true case "lArr": // LEFTWARDS DOUBLE ARROW - return rune(0x21d0), true + return {'\u21d0', 0}, 1, true case "lAtail": // LEFTWARDS DOUBLE ARROW-TAIL - return rune(0x291b), true + return {'\u291b', 0}, 1, true case "lBarr": // LEFTWARDS TRIPLE DASH ARROW - return rune(0x290e), true + return {'\u290e', 0}, 1, true case "lE": // LESS-THAN OVER EQUAL TO - return rune(0x2266), true + return {'\u2266', 0}, 1, true case "lEg": // LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN - return rune(0x2a8b), true + return {'\u2a8b', 0}, 1, true case "lHar": // LEFTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB DOWN - return rune(0x2962), true + return {'\u2962', 0}, 1, true case "lacute": // LATIN SMALL LETTER L WITH ACUTE - return rune(0x013a), true + return {'\u013a', 0}, 1, true case "laemptyv": // EMPTY SET WITH LEFT ARROW ABOVE - return rune(0x29b4), true + return {'\u29b4', 0}, 1, true case "lagran": // SCRIPT CAPITAL L - return rune(0x2112), true + return {'\u2112', 0}, 1, true case "lambda": // GREEK SMALL LETTER LAMDA - return rune(0x03bb), true + return {'\u03bb', 0}, 1, true case "lang": // MATHEMATICAL LEFT ANGLE BRACKET - return rune(0x27e8), true + return {'\u27e8', 0}, 1, true case "langd": // LEFT ANGLE BRACKET WITH DOT - return rune(0x2991), true + return {'\u2991', 0}, 1, true case "langle": // MATHEMATICAL LEFT ANGLE BRACKET - return rune(0x27e8), true + return {'\u27e8', 0}, 1, true case "lap": // LESS-THAN OR APPROXIMATE - return rune(0x2a85), true + return {'\u2a85', 0}, 1, true case "laquo": // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK - return rune(0xab), true + return {'«', 0}, 1, true case "larr": // LEFTWARDS ARROW - return rune(0x2190), true + return {'\u2190', 0}, 1, true case "larr2": // LEFTWARDS PAIRED ARROWS - return rune(0x21c7), true + return {'\u21c7', 0}, 1, true case "larrb": // LEFTWARDS ARROW TO BAR - return rune(0x21e4), true + return {'\u21e4', 0}, 1, true case "larrbfs": // LEFTWARDS ARROW FROM BAR TO BLACK DIAMOND - return rune(0x291f), true + return {'\u291f', 0}, 1, true case "larrfs": // LEFTWARDS ARROW TO BLACK DIAMOND - return rune(0x291d), true + return {'\u291d', 0}, 1, true case "larrhk": // LEFTWARDS ARROW WITH HOOK - return rune(0x21a9), true + return {'\u21a9', 0}, 1, true case "larrlp": // LEFTWARDS ARROW WITH LOOP - return rune(0x21ab), true + return {'\u21ab', 0}, 1, true case "larrpl": // LEFT-SIDE ARC ANTICLOCKWISE ARROW - return rune(0x2939), true + return {'\u2939', 0}, 1, true case "larrsim": // LEFTWARDS ARROW ABOVE TILDE OPERATOR - return rune(0x2973), true + return {'\u2973', 0}, 1, true case "larrtl": // LEFTWARDS ARROW WITH TAIL - return rune(0x21a2), true + return {'\u21a2', 0}, 1, true case "lat": // LARGER THAN - return rune(0x2aab), true + return {'\u2aab', 0}, 1, true case "latail": // LEFTWARDS ARROW-TAIL - return rune(0x2919), true + return {'\u2919', 0}, 1, true case "late": // LARGER THAN OR EQUAL TO - return rune(0x2aad), true + return {'\u2aad', 0}, 1, true case "lates": // LARGER THAN OR slanted EQUAL - return rune(0x2aad), true + return {'\u2aad', '\ufe00'}, 2, true case "lbarr": // LEFTWARDS DOUBLE DASH ARROW - return rune(0x290c), true + return {'\u290c', 0}, 1, true case "lbbrk": // LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT - return rune(0x2772), true + return {'\u2772', 0}, 1, true case "lbrace": // LEFT CURLY BRACKET - return rune(0x7b), true + return {'{', 0}, 1, true case "lbrack": // LEFT SQUARE BRACKET - return rune(0x5b), true + return {'[', 0}, 1, true case "lbrke": // LEFT SQUARE BRACKET WITH UNDERBAR - return rune(0x298b), true + return {'\u298b', 0}, 1, true case "lbrksld": // LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER - return rune(0x298f), true + return {'\u298f', 0}, 1, true case "lbrkslu": // LEFT SQUARE BRACKET WITH TICK IN TOP CORNER - return rune(0x298d), true + return {'\u298d', 0}, 1, true case "lcaron": // LATIN SMALL LETTER L WITH CARON - return rune(0x013e), true + return {'\u013e', 0}, 1, true case "lcedil": // LATIN SMALL LETTER L WITH CEDILLA - return rune(0x013c), true + return {'\u013c', 0}, 1, true case "lceil": // LEFT CEILING - return rune(0x2308), true + return {'\u2308', 0}, 1, true case "lcub": // LEFT CURLY BRACKET - return rune(0x7b), true + return {'{', 0}, 1, true case "lcy": // CYRILLIC SMALL LETTER EL - return rune(0x043b), true + return {'\u043b', 0}, 1, true case "ldca": // ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS - return rune(0x2936), true + return {'\u2936', 0}, 1, true case "ldharb": // LEFTWARDS HARPOON WITH BARB DOWN TO BAR - return rune(0x2956), true + return {'\u2956', 0}, 1, true case "ldot": // LESS-THAN WITH DOT - return rune(0x22d6), true + return {'\u22d6', 0}, 1, true case "ldquo": // LEFT DOUBLE QUOTATION MARK - return rune(0x201c), true + return {'\u201c', 0}, 1, true case "ldquor": // DOUBLE LOW-9 QUOTATION MARK - return rune(0x201e), true + return {'\u201e', 0}, 1, true case "ldrdhar": // LEFTWARDS HARPOON WITH BARB DOWN ABOVE RIGHTWARDS HARPOON WITH BARB DOWN - return rune(0x2967), true + return {'\u2967', 0}, 1, true case "ldrdshar": // LEFT BARB DOWN RIGHT BARB DOWN HARPOON - return rune(0x2950), true + return {'\u2950', 0}, 1, true case "ldrushar": // LEFT BARB DOWN RIGHT BARB UP HARPOON - return rune(0x294b), true + return {'\u294b', 0}, 1, true case "ldsh": // DOWNWARDS ARROW WITH TIP LEFTWARDS - return rune(0x21b2), true + return {'\u21b2', 0}, 1, true case "le": // LESS-THAN OR EQUAL TO - return rune(0x2264), true + return {'\u2264', 0}, 1, true case "leftarrow": // LEFTWARDS ARROW - return rune(0x2190), true + return {'\u2190', 0}, 1, true case "leftarrowtail": // LEFTWARDS ARROW WITH TAIL - return rune(0x21a2), true + return {'\u21a2', 0}, 1, true case "leftharpoondown": // LEFTWARDS HARPOON WITH BARB DOWNWARDS - return rune(0x21bd), true + return {'\u21bd', 0}, 1, true case "leftharpoonup": // LEFTWARDS HARPOON WITH BARB UPWARDS - return rune(0x21bc), true + return {'\u21bc', 0}, 1, true case "leftleftarrows": // LEFTWARDS PAIRED ARROWS - return rune(0x21c7), true + return {'\u21c7', 0}, 1, true case "leftrightarrow": // LEFT RIGHT ARROW - return rune(0x2194), true + return {'\u2194', 0}, 1, true case "leftrightarrows": // LEFTWARDS ARROW OVER RIGHTWARDS ARROW - return rune(0x21c6), true + return {'\u21c6', 0}, 1, true case "leftrightharpoons": // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON - return rune(0x21cb), true + return {'\u21cb', 0}, 1, true case "leftrightsquigarrow": // LEFT RIGHT WAVE ARROW - return rune(0x21ad), true + return {'\u21ad', 0}, 1, true case "leftthreetimes": // LEFT SEMIDIRECT PRODUCT - return rune(0x22cb), true + return {'\u22cb', 0}, 1, true case "leg": // LESS-THAN EQUAL TO OR GREATER-THAN - return rune(0x22da), true + return {'\u22da', 0}, 1, true case "leq": // LESS-THAN OR EQUAL TO - return rune(0x2264), true + return {'\u2264', 0}, 1, true case "leqq": // LESS-THAN OVER EQUAL TO - return rune(0x2266), true + return {'\u2266', 0}, 1, true case "leqslant": // LESS-THAN OR SLANTED EQUAL TO - return rune(0x2a7d), true + return {'\u2a7d', 0}, 1, true case "les": // LESS-THAN OR SLANTED EQUAL TO - return rune(0x2a7d), true + return {'\u2a7d', 0}, 1, true case "lescc": // LESS-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL - return rune(0x2aa8), true + return {'\u2aa8', 0}, 1, true case "lesdot": // LESS-THAN OR SLANTED EQUAL TO WITH DOT INSIDE - return rune(0x2a7f), true + return {'\u2a7f', 0}, 1, true case "lesdoto": // LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE - return rune(0x2a81), true + return {'\u2a81', 0}, 1, true case "lesdotor": // LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE RIGHT - return rune(0x2a83), true + return {'\u2a83', 0}, 1, true case "lesg": // LESS-THAN slanted EQUAL TO OR GREATER-THAN - return rune(0x22da), true + return {'\u22da', '\ufe00'}, 2, true case "lesges": // LESS-THAN ABOVE SLANTED EQUAL ABOVE GREATER-THAN ABOVE SLANTED EQUAL - return rune(0x2a93), true + return {'\u2a93', 0}, 1, true case "lessapprox": // LESS-THAN OR APPROXIMATE - return rune(0x2a85), true + return {'\u2a85', 0}, 1, true case "lessdot": // LESS-THAN WITH DOT - return rune(0x22d6), true + return {'\u22d6', 0}, 1, true case "lesseqgtr": // LESS-THAN EQUAL TO OR GREATER-THAN - return rune(0x22da), true + return {'\u22da', 0}, 1, true case "lesseqqgtr": // LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN - return rune(0x2a8b), true + return {'\u2a8b', 0}, 1, true case "lessgtr": // LESS-THAN OR GREATER-THAN - return rune(0x2276), true + return {'\u2276', 0}, 1, true case "lesssim": // LESS-THAN OR EQUIVALENT TO - return rune(0x2272), true + return {'\u2272', 0}, 1, true case "lfbowtie": // BOWTIE WITH LEFT HALF BLACK - return rune(0x29d1), true + return {'\u29d1', 0}, 1, true case "lfisht": // LEFT FISH TAIL - return rune(0x297c), true + return {'\u297c', 0}, 1, true case "lfloor": // LEFT FLOOR - return rune(0x230a), true + return {'\u230a', 0}, 1, true case "lfr": // MATHEMATICAL FRAKTUR SMALL L - return rune(0x01d529), true + return {'\U0001d529', 0}, 1, true case "lftimes": // TIMES WITH LEFT HALF BLACK - return rune(0x29d4), true + return {'\u29d4', 0}, 1, true case "lg": // LESS-THAN OR GREATER-THAN - return rune(0x2276), true + return {'\u2276', 0}, 1, true case "lgE": // LESS-THAN ABOVE GREATER-THAN ABOVE DOUBLE-LINE EQUAL - return rune(0x2a91), true + return {'\u2a91', 0}, 1, true case "lgr": // GREEK SMALL LETTER LAMDA - return rune(0x03bb), true + return {'\u03bb', 0}, 1, true case "lhard": // LEFTWARDS HARPOON WITH BARB DOWNWARDS - return rune(0x21bd), true + return {'\u21bd', 0}, 1, true case "lharu": // LEFTWARDS HARPOON WITH BARB UPWARDS - return rune(0x21bc), true + return {'\u21bc', 0}, 1, true case "lharul": // LEFTWARDS HARPOON WITH BARB UP ABOVE LONG DASH - return rune(0x296a), true + return {'\u296a', 0}, 1, true case "lhblk": // LOWER HALF BLOCK - return rune(0x2584), true + return {'\u2584', 0}, 1, true case "ljcy": // CYRILLIC SMALL LETTER LJE - return rune(0x0459), true + return {'\u0459', 0}, 1, true case "ll": // MUCH LESS-THAN - return rune(0x226a), true + return {'\u226a', 0}, 1, true case "llarr": // LEFTWARDS PAIRED ARROWS - return rune(0x21c7), true + return {'\u21c7', 0}, 1, true case "llcorner": // BOTTOM LEFT CORNER - return rune(0x231e), true + return {'\u231e', 0}, 1, true case "llhard": // LEFTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH - return rune(0x296b), true + return {'\u296b', 0}, 1, true case "lltri": // LOWER LEFT TRIANGLE - return rune(0x25fa), true + return {'\u25fa', 0}, 1, true case "lltrif": // BLACK LOWER LEFT TRIANGLE - return rune(0x25e3), true + return {'\u25e3', 0}, 1, true case "lmidot": // LATIN SMALL LETTER L WITH MIDDLE DOT - return rune(0x0140), true + return {'\u0140', 0}, 1, true case "lmoust": // UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION - return rune(0x23b0), true + return {'\u23b0', 0}, 1, true case "lmoustache": // UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION - return rune(0x23b0), true + return {'\u23b0', 0}, 1, true case "lnE": // LESS-THAN BUT NOT EQUAL TO - return rune(0x2268), true + return {'\u2268', 0}, 1, true case "lnap": // LESS-THAN AND NOT APPROXIMATE - return rune(0x2a89), true + return {'\u2a89', 0}, 1, true case "lnapprox": // LESS-THAN AND NOT APPROXIMATE - return rune(0x2a89), true + return {'\u2a89', 0}, 1, true case "lne": // LESS-THAN AND SINGLE-LINE NOT EQUAL TO - return rune(0x2a87), true + return {'\u2a87', 0}, 1, true case "lneq": // LESS-THAN AND SINGLE-LINE NOT EQUAL TO - return rune(0x2a87), true + return {'\u2a87', 0}, 1, true case "lneqq": // LESS-THAN BUT NOT EQUAL TO - return rune(0x2268), true + return {'\u2268', 0}, 1, true case "lnsim": // LESS-THAN BUT NOT EQUIVALENT TO - return rune(0x22e6), true + return {'\u22e6', 0}, 1, true case "loang": // MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET - return rune(0x27ec), true + return {'\u27ec', 0}, 1, true case "loarr": // LEFTWARDS OPEN-HEADED ARROW - return rune(0x21fd), true + return {'\u21fd', 0}, 1, true case "lobrk": // MATHEMATICAL LEFT WHITE SQUARE BRACKET - return rune(0x27e6), true + return {'\u27e6', 0}, 1, true case "locub": // LEFT WHITE CURLY BRACKET - return rune(0x2983), true + return {'\u2983', 0}, 1, true case "longleftarrow": // LONG LEFTWARDS ARROW - return rune(0x27f5), true + return {'\u27f5', 0}, 1, true case "longleftrightarrow": // LONG LEFT RIGHT ARROW - return rune(0x27f7), true + return {'\u27f7', 0}, 1, true case "longmapsto": // LONG RIGHTWARDS ARROW FROM BAR - return rune(0x27fc), true + return {'\u27fc', 0}, 1, true case "longrightarrow": // LONG RIGHTWARDS ARROW - return rune(0x27f6), true + return {'\u27f6', 0}, 1, true case "looparrowleft": // LEFTWARDS ARROW WITH LOOP - return rune(0x21ab), true + return {'\u21ab', 0}, 1, true case "looparrowright": // RIGHTWARDS ARROW WITH LOOP - return rune(0x21ac), true + return {'\u21ac', 0}, 1, true case "lopar": // LEFT WHITE PARENTHESIS - return rune(0x2985), true + return {'\u2985', 0}, 1, true case "lopf": // MATHEMATICAL DOUBLE-STRUCK SMALL L - return rune(0x01d55d), true + return {'\U0001d55d', 0}, 1, true case "loplus": // PLUS SIGN IN LEFT HALF CIRCLE - return rune(0x2a2d), true + return {'\u2a2d', 0}, 1, true case "lotimes": // MULTIPLICATION SIGN IN LEFT HALF CIRCLE - return rune(0x2a34), true + return {'\u2a34', 0}, 1, true case "lowast": // LOW ASTERISK - return rune(0x204e), true + return {'\u204e', 0}, 1, true case "lowbar": // LOW LINE - return rune(0x5f), true + return {'_', 0}, 1, true case "lowint": // INTEGRAL WITH UNDERBAR - return rune(0x2a1c), true + return {'\u2a1c', 0}, 1, true case "loz": // LOZENGE - return rune(0x25ca), true + return {'\u25ca', 0}, 1, true case "lozenge": // LOZENGE - return rune(0x25ca), true + return {'\u25ca', 0}, 1, true case "lozf": // BLACK LOZENGE - return rune(0x29eb), true + return {'\u29eb', 0}, 1, true case "lpar": // LEFT PARENTHESIS - return rune(0x28), true + return {'(', 0}, 1, true case "lpargt": // SPHERICAL ANGLE OPENING LEFT - return rune(0x29a0), true + return {'\u29a0', 0}, 1, true case "lparlt": // LEFT ARC LESS-THAN BRACKET - return rune(0x2993), true + return {'\u2993', 0}, 1, true case "lrarr": // LEFTWARDS ARROW OVER RIGHTWARDS ARROW - return rune(0x21c6), true + return {'\u21c6', 0}, 1, true case "lrarr2": // LEFTWARDS ARROW OVER RIGHTWARDS ARROW - return rune(0x21c6), true + return {'\u21c6', 0}, 1, true case "lrcorner": // BOTTOM RIGHT CORNER - return rune(0x231f), true + return {'\u231f', 0}, 1, true case "lrhar": // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON - return rune(0x21cb), true + return {'\u21cb', 0}, 1, true case "lrhar2": // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON - return rune(0x21cb), true + return {'\u21cb', 0}, 1, true case "lrhard": // RIGHTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH - return rune(0x296d), true + return {'\u296d', 0}, 1, true case "lrm": // LEFT-TO-RIGHT MARK - return rune(0x200e), true + return {'\u200e', 0}, 1, true case "lrtri": // RIGHT TRIANGLE - return rune(0x22bf), true + return {'\u22bf', 0}, 1, true case "lsaquo": // SINGLE LEFT-POINTING ANGLE QUOTATION MARK - return rune(0x2039), true + return {'\u2039', 0}, 1, true case "lscr": // MATHEMATICAL SCRIPT SMALL L - return rune(0x01d4c1), true + return {'\U0001d4c1', 0}, 1, true case "lsh": // UPWARDS ARROW WITH TIP LEFTWARDS - return rune(0x21b0), true + return {'\u21b0', 0}, 1, true case "lsim": // LESS-THAN OR EQUIVALENT TO - return rune(0x2272), true + return {'\u2272', 0}, 1, true case "lsime": // LESS-THAN ABOVE SIMILAR OR EQUAL - return rune(0x2a8d), true + return {'\u2a8d', 0}, 1, true case "lsimg": // LESS-THAN ABOVE SIMILAR ABOVE GREATER-THAN - return rune(0x2a8f), true + return {'\u2a8f', 0}, 1, true case "lsqb": // LEFT SQUARE BRACKET - return rune(0x5b), true + return {'[', 0}, 1, true case "lsquo": // LEFT SINGLE QUOTATION MARK - return rune(0x2018), true + return {'\u2018', 0}, 1, true case "lsquor": // SINGLE LOW-9 QUOTATION MARK - return rune(0x201a), true + return {'\u201a', 0}, 1, true case "lstrok": // LATIN SMALL LETTER L WITH STROKE - return rune(0x0142), true + return {'\u0142', 0}, 1, true case "lt": // LESS-THAN SIGN - return rune(0x3c), true + return {'<', 0}, 1, true case "ltcc": // LESS-THAN CLOSED BY CURVE - return rune(0x2aa6), true + return {'\u2aa6', 0}, 1, true case "ltcir": // LESS-THAN WITH CIRCLE INSIDE - return rune(0x2a79), true + return {'\u2a79', 0}, 1, true case "ltdot": // LESS-THAN WITH DOT - return rune(0x22d6), true + return {'\u22d6', 0}, 1, true case "lthree": // LEFT SEMIDIRECT PRODUCT - return rune(0x22cb), true + return {'\u22cb', 0}, 1, true case "ltimes": // LEFT NORMAL FACTOR SEMIDIRECT PRODUCT - return rune(0x22c9), true + return {'\u22c9', 0}, 1, true case "ltlarr": // LESS-THAN ABOVE LEFTWARDS ARROW - return rune(0x2976), true + return {'\u2976', 0}, 1, true case "ltquest": // LESS-THAN WITH QUESTION MARK ABOVE - return rune(0x2a7b), true + return {'\u2a7b', 0}, 1, true case "ltrPar": // DOUBLE RIGHT ARC LESS-THAN BRACKET - return rune(0x2996), true + return {'\u2996', 0}, 1, true case "ltri": // WHITE LEFT-POINTING SMALL TRIANGLE - return rune(0x25c3), true + return {'\u25c3', 0}, 1, true case "ltrie": // NORMAL SUBGROUP OF OR EQUAL TO - return rune(0x22b4), true + return {'\u22b4', 0}, 1, true case "ltrif": // BLACK LEFT-POINTING SMALL TRIANGLE - return rune(0x25c2), true + return {'\u25c2', 0}, 1, true case "ltrivb": // LEFT TRIANGLE BESIDE VERTICAL BAR - return rune(0x29cf), true + return {'\u29cf', 0}, 1, true case "luharb": // LEFTWARDS HARPOON WITH BARB UP TO BAR - return rune(0x2952), true + return {'\u2952', 0}, 1, true case "lurdshar": // LEFT BARB UP RIGHT BARB DOWN HARPOON - return rune(0x294a), true + return {'\u294a', 0}, 1, true case "luruhar": // LEFTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB UP - return rune(0x2966), true + return {'\u2966', 0}, 1, true case "lurushar": // LEFT BARB UP RIGHT BARB UP HARPOON - return rune(0x294e), true + return {'\u294e', 0}, 1, true case "lvertneqq": // LESS-THAN BUT NOT EQUAL TO - with vertical stroke - return rune(0x2268), true + return {'\u2268', '\ufe00'}, 2, true case "lvnE": // LESS-THAN BUT NOT EQUAL TO - with vertical stroke - return rune(0x2268), true + return {'\u2268', '\ufe00'}, 2, true } case 'm': switch name { case "mDDot": // GEOMETRIC PROPORTION - return rune(0x223a), true + return {'\u223a', 0}, 1, true case "macr": // MACRON - return rune(0xaf), true + return {'¯', 0}, 1, true case "male": // MALE SIGN - return rune(0x2642), true + return {'\u2642', 0}, 1, true case "malt": // MALTESE CROSS - return rune(0x2720), true + return {'\u2720', 0}, 1, true case "maltese": // MALTESE CROSS - return rune(0x2720), true + return {'\u2720', 0}, 1, true case "map": // RIGHTWARDS ARROW FROM BAR - return rune(0x21a6), true + return {'\u21a6', 0}, 1, true case "mapsto": // RIGHTWARDS ARROW FROM BAR - return rune(0x21a6), true + return {'\u21a6', 0}, 1, true case "mapstodown": // DOWNWARDS ARROW FROM BAR - return rune(0x21a7), true + return {'\u21a7', 0}, 1, true case "mapstoleft": // LEFTWARDS ARROW FROM BAR - return rune(0x21a4), true + return {'\u21a4', 0}, 1, true case "mapstoup": // UPWARDS ARROW FROM BAR - return rune(0x21a5), true + return {'\u21a5', 0}, 1, true case "marker": // BLACK VERTICAL RECTANGLE - return rune(0x25ae), true + return {'\u25ae', 0}, 1, true case "mcomma": // MINUS SIGN WITH COMMA ABOVE - return rune(0x2a29), true + return {'\u2a29', 0}, 1, true case "mcy": // CYRILLIC SMALL LETTER EM - return rune(0x043c), true + return {'\u043c', 0}, 1, true case "mdash": // EM DASH - return rune(0x2014), true + return {'\u2014', 0}, 1, true case "measuredangle": // MEASURED ANGLE - return rune(0x2221), true + return {'\u2221', 0}, 1, true case "mfr": // MATHEMATICAL FRAKTUR SMALL M - return rune(0x01d52a), true + return {'\U0001d52a', 0}, 1, true case "mgr": // GREEK SMALL LETTER MU - return rune(0x03bc), true + return {'\u03bc', 0}, 1, true case "mho": // INVERTED OHM SIGN - return rune(0x2127), true + return {'\u2127', 0}, 1, true case "micro": // MICRO SIGN - return rune(0xb5), true + return {'µ', 0}, 1, true case "mid": // DIVIDES - return rune(0x2223), true + return {'\u2223', 0}, 1, true case "midast": // ASTERISK - return rune(0x2a), true + return {'*', 0}, 1, true case "midcir": // VERTICAL LINE WITH CIRCLE BELOW - return rune(0x2af0), true + return {'\u2af0', 0}, 1, true case "middot": // MIDDLE DOT - return rune(0xb7), true + return {'·', 0}, 1, true case "minus": // MINUS SIGN - return rune(0x2212), true + return {'\u2212', 0}, 1, true case "minusb": // SQUARED MINUS - return rune(0x229f), true + return {'\u229f', 0}, 1, true case "minusd": // DOT MINUS - return rune(0x2238), true + return {'\u2238', 0}, 1, true case "minusdu": // MINUS SIGN WITH DOT BELOW - return rune(0x2a2a), true + return {'\u2a2a', 0}, 1, true case "mlcp": // TRANSVERSAL INTERSECTION - return rune(0x2adb), true + return {'\u2adb', 0}, 1, true case "mldr": // HORIZONTAL ELLIPSIS - return rune(0x2026), true + return {'\u2026', 0}, 1, true case "mnplus": // MINUS-OR-PLUS SIGN - return rune(0x2213), true + return {'\u2213', 0}, 1, true case "models": // MODELS - return rune(0x22a7), true + return {'\u22a7', 0}, 1, true case "mopf": // MATHEMATICAL DOUBLE-STRUCK SMALL M - return rune(0x01d55e), true + return {'\U0001d55e', 0}, 1, true case "mp": // MINUS-OR-PLUS SIGN - return rune(0x2213), true + return {'\u2213', 0}, 1, true case "mscr": // MATHEMATICAL SCRIPT SMALL M - return rune(0x01d4c2), true + return {'\U0001d4c2', 0}, 1, true case "mstpos": // INVERTED LAZY S - return rune(0x223e), true + return {'\u223e', 0}, 1, true case "mu": // GREEK SMALL LETTER MU - return rune(0x03bc), true + return {'\u03bc', 0}, 1, true case "multimap": // MULTIMAP - return rune(0x22b8), true + return {'\u22b8', 0}, 1, true case "mumap": // MULTIMAP - return rune(0x22b8), true + return {'\u22b8', 0}, 1, true } case 'n': switch name { case "nGg": // VERY MUCH GREATER-THAN with slash - return rune(0x22d9), true + return {'\u22d9', '\u0338'}, 2, true case "nGt": // MUCH GREATER THAN with vertical line - return rune(0x226b), true + return {'\u226b', '\u20d2'}, 2, true case "nGtv": // MUCH GREATER THAN with slash - return rune(0x226b), true + return {'\u226b', '\u0338'}, 2, true case "nLeftarrow": // LEFTWARDS DOUBLE ARROW WITH STROKE - return rune(0x21cd), true + return {'\u21cd', 0}, 1, true case "nLeftrightarrow": // LEFT RIGHT DOUBLE ARROW WITH STROKE - return rune(0x21ce), true + return {'\u21ce', 0}, 1, true case "nLl": // VERY MUCH LESS-THAN with slash - return rune(0x22d8), true + return {'\u22d8', '\u0338'}, 2, true case "nLt": // MUCH LESS THAN with vertical line - return rune(0x226a), true + return {'\u226a', '\u20d2'}, 2, true case "nLtv": // MUCH LESS THAN with slash - return rune(0x226a), true + return {'\u226a', '\u0338'}, 2, true case "nRightarrow": // RIGHTWARDS DOUBLE ARROW WITH STROKE - return rune(0x21cf), true + return {'\u21cf', 0}, 1, true case "nVDash": // NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE - return rune(0x22af), true + return {'\u22af', 0}, 1, true case "nVdash": // DOES NOT FORCE - return rune(0x22ae), true + return {'\u22ae', 0}, 1, true case "nabla": // NABLA - return rune(0x2207), true + return {'\u2207', 0}, 1, true case "nacute": // LATIN SMALL LETTER N WITH ACUTE - return rune(0x0144), true + return {'\u0144', 0}, 1, true case "nang": // ANGLE with vertical line - return rune(0x2220), true + return {'\u2220', '\u20d2'}, 2, true case "nap": // NOT ALMOST EQUAL TO - return rune(0x2249), true + return {'\u2249', 0}, 1, true case "napE": // APPROXIMATELY EQUAL OR EQUAL TO with slash - return rune(0x2a70), true + return {'\u2a70', '\u0338'}, 2, true case "napid": // TRIPLE TILDE with slash - return rune(0x224b), true + return {'\u224b', '\u0338'}, 2, true case "napos": // LATIN SMALL LETTER N PRECEDED BY APOSTROPHE - return rune(0x0149), true + return {'\u0149', 0}, 1, true case "napprox": // NOT ALMOST EQUAL TO - return rune(0x2249), true + return {'\u2249', 0}, 1, true case "natur": // MUSIC NATURAL SIGN - return rune(0x266e), true + return {'\u266e', 0}, 1, true case "natural": // MUSIC NATURAL SIGN - return rune(0x266e), true + return {'\u266e', 0}, 1, true case "naturals": // DOUBLE-STRUCK CAPITAL N - return rune(0x2115), true + return {'\u2115', 0}, 1, true case "nbsp": // NO-BREAK SPACE - return rune(0xa0), true + return {'\u00a0', 0}, 1, true case "nbump": // GEOMETRICALLY EQUIVALENT TO with slash - return rune(0x224e), true + return {'\u224e', '\u0338'}, 2, true case "nbumpe": // DIFFERENCE BETWEEN with slash - return rune(0x224f), true + return {'\u224f', '\u0338'}, 2, true case "ncap": // INTERSECTION WITH OVERBAR - return rune(0x2a43), true + return {'\u2a43', 0}, 1, true case "ncaron": // LATIN SMALL LETTER N WITH CARON - return rune(0x0148), true + return {'\u0148', 0}, 1, true case "ncedil": // LATIN SMALL LETTER N WITH CEDILLA - return rune(0x0146), true + return {'\u0146', 0}, 1, true case "ncong": // NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO - return rune(0x2247), true + return {'\u2247', 0}, 1, true case "ncongdot": // CONGRUENT WITH DOT ABOVE with slash - return rune(0x2a6d), true + return {'\u2a6d', '\u0338'}, 2, true case "ncup": // UNION WITH OVERBAR - return rune(0x2a42), true + return {'\u2a42', 0}, 1, true case "ncy": // CYRILLIC SMALL LETTER EN - return rune(0x043d), true + return {'\u043d', 0}, 1, true case "ndash": // EN DASH - return rune(0x2013), true + return {'\u2013', 0}, 1, true case "ne": // NOT EQUAL TO - return rune(0x2260), true + return {'\u2260', 0}, 1, true case "neArr": // NORTH EAST DOUBLE ARROW - return rune(0x21d7), true + return {'\u21d7', 0}, 1, true case "nearhk": // NORTH EAST ARROW WITH HOOK - return rune(0x2924), true + return {'\u2924', 0}, 1, true case "nearr": // NORTH EAST ARROW - return rune(0x2197), true + return {'\u2197', 0}, 1, true case "nearrow": // NORTH EAST ARROW - return rune(0x2197), true + return {'\u2197', 0}, 1, true case "nedot": // APPROACHES THE LIMIT with slash - return rune(0x2250), true + return {'\u2250', '\u0338'}, 2, true case "neonwarr": // NORTH EAST ARROW CROSSING NORTH WEST ARROW - return rune(0x2931), true + return {'\u2931', 0}, 1, true case "neosearr": // NORTH EAST ARROW CROSSING SOUTH EAST ARROW - return rune(0x292e), true + return {'\u292e', 0}, 1, true case "nequiv": // NOT IDENTICAL TO - return rune(0x2262), true + return {'\u2262', 0}, 1, true case "nesear": // NORTH EAST ARROW AND SOUTH EAST ARROW - return rune(0x2928), true + return {'\u2928', 0}, 1, true case "nesim": // MINUS TILDE with slash - return rune(0x2242), true + return {'\u2242', '\u0338'}, 2, true case "neswsarr": // NORTH EAST AND SOUTH WEST ARROW - return rune(0x2922), true + return {'\u2922', 0}, 1, true case "nexist": // THERE DOES NOT EXIST - return rune(0x2204), true + return {'\u2204', 0}, 1, true case "nexists": // THERE DOES NOT EXIST - return rune(0x2204), true + return {'\u2204', 0}, 1, true case "nfr": // MATHEMATICAL FRAKTUR SMALL N - return rune(0x01d52b), true + return {'\U0001d52b', 0}, 1, true case "ngE": // GREATER-THAN OVER EQUAL TO with slash - return rune(0x2267), true + return {'\u2267', '\u0338'}, 2, true case "nge": // NEITHER GREATER-THAN NOR EQUAL TO - return rune(0x2271), true + return {'\u2271', 0}, 1, true case "ngeq": // NEITHER GREATER-THAN NOR EQUAL TO - return rune(0x2271), true + return {'\u2271', 0}, 1, true case "ngeqq": // GREATER-THAN OVER EQUAL TO with slash - return rune(0x2267), true + return {'\u2267', '\u0338'}, 2, true case "ngeqslant": // GREATER-THAN OR SLANTED EQUAL TO with slash - return rune(0x2a7e), true + return {'\u2a7e', '\u0338'}, 2, true case "nges": // GREATER-THAN OR SLANTED EQUAL TO with slash - return rune(0x2a7e), true + return {'\u2a7e', '\u0338'}, 2, true case "ngr": // GREEK SMALL LETTER NU - return rune(0x03bd), true + return {'\u03bd', 0}, 1, true case "ngsim": // NEITHER GREATER-THAN NOR EQUIVALENT TO - return rune(0x2275), true + return {'\u2275', 0}, 1, true case "ngt": // NOT GREATER-THAN - return rune(0x226f), true + return {'\u226f', 0}, 1, true case "ngtr": // NOT GREATER-THAN - return rune(0x226f), true + return {'\u226f', 0}, 1, true case "nhArr": // LEFT RIGHT DOUBLE ARROW WITH STROKE - return rune(0x21ce), true + return {'\u21ce', 0}, 1, true case "nharr": // LEFT RIGHT ARROW WITH STROKE - return rune(0x21ae), true + return {'\u21ae', 0}, 1, true case "nhpar": // PARALLEL WITH HORIZONTAL STROKE - return rune(0x2af2), true + return {'\u2af2', 0}, 1, true case "ni": // CONTAINS AS MEMBER - return rune(0x220b), true + return {'\u220b', 0}, 1, true case "nis": // SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE - return rune(0x22fc), true + return {'\u22fc', 0}, 1, true case "nisd": // CONTAINS WITH LONG HORIZONTAL STROKE - return rune(0x22fa), true + return {'\u22fa', 0}, 1, true case "niv": // CONTAINS AS MEMBER - return rune(0x220b), true + return {'\u220b', 0}, 1, true case "njcy": // CYRILLIC SMALL LETTER NJE - return rune(0x045a), true + return {'\u045a', 0}, 1, true case "nlArr": // LEFTWARDS DOUBLE ARROW WITH STROKE - return rune(0x21cd), true + return {'\u21cd', 0}, 1, true case "nlE": // LESS-THAN OVER EQUAL TO with slash - return rune(0x2266), true + return {'\u2266', '\u0338'}, 2, true case "nlarr": // LEFTWARDS ARROW WITH STROKE - return rune(0x219a), true + return {'\u219a', 0}, 1, true case "nldr": // TWO DOT LEADER - return rune(0x2025), true + return {'\u2025', 0}, 1, true case "nle": // NEITHER LESS-THAN NOR EQUAL TO - return rune(0x2270), true + return {'\u2270', 0}, 1, true case "nleftarrow": // LEFTWARDS ARROW WITH STROKE - return rune(0x219a), true + return {'\u219a', 0}, 1, true case "nleftrightarrow": // LEFT RIGHT ARROW WITH STROKE - return rune(0x21ae), true + return {'\u21ae', 0}, 1, true case "nleq": // NEITHER LESS-THAN NOR EQUAL TO - return rune(0x2270), true + return {'\u2270', 0}, 1, true case "nleqq": // LESS-THAN OVER EQUAL TO with slash - return rune(0x2266), true + return {'\u2266', '\u0338'}, 2, true case "nleqslant": // LESS-THAN OR SLANTED EQUAL TO with slash - return rune(0x2a7d), true + return {'\u2a7d', '\u0338'}, 2, true case "nles": // LESS-THAN OR SLANTED EQUAL TO with slash - return rune(0x2a7d), true + return {'\u2a7d', '\u0338'}, 2, true case "nless": // NOT LESS-THAN - return rune(0x226e), true + return {'\u226e', 0}, 1, true case "nlsim": // NEITHER LESS-THAN NOR EQUIVALENT TO - return rune(0x2274), true + return {'\u2274', 0}, 1, true case "nlt": // NOT LESS-THAN - return rune(0x226e), true + return {'\u226e', 0}, 1, true case "nltri": // NOT NORMAL SUBGROUP OF - return rune(0x22ea), true + return {'\u22ea', 0}, 1, true case "nltrie": // NOT NORMAL SUBGROUP OF OR EQUAL TO - return rune(0x22ec), true + return {'\u22ec', 0}, 1, true case "nltrivb": // LEFT TRIANGLE BESIDE VERTICAL BAR with slash - return rune(0x29cf), true + return {'\u29cf', '\u0338'}, 2, true case "nmid": // DOES NOT DIVIDE - return rune(0x2224), true + return {'\u2224', 0}, 1, true case "nopf": // MATHEMATICAL DOUBLE-STRUCK SMALL N - return rune(0x01d55f), true + return {'\U0001d55f', 0}, 1, true case "not": // NOT SIGN - return rune(0xac), true + return {'¬', 0}, 1, true case "notin": // NOT AN ELEMENT OF - return rune(0x2209), true + return {'\u2209', 0}, 1, true case "notinE": // ELEMENT OF WITH TWO HORIZONTAL STROKES with slash - return rune(0x22f9), true + return {'\u22f9', '\u0338'}, 2, true case "notindot": // ELEMENT OF WITH DOT ABOVE with slash - return rune(0x22f5), true + return {'\u22f5', '\u0338'}, 2, true case "notinva": // NOT AN ELEMENT OF - return rune(0x2209), true + return {'\u2209', 0}, 1, true case "notinvb": // SMALL ELEMENT OF WITH OVERBAR - return rune(0x22f7), true + return {'\u22f7', 0}, 1, true case "notinvc": // ELEMENT OF WITH OVERBAR - return rune(0x22f6), true + return {'\u22f6', 0}, 1, true case "notni": // DOES NOT CONTAIN AS MEMBER - return rune(0x220c), true + return {'\u220c', 0}, 1, true case "notniva": // DOES NOT CONTAIN AS MEMBER - return rune(0x220c), true + return {'\u220c', 0}, 1, true case "notnivb": // SMALL CONTAINS WITH OVERBAR - return rune(0x22fe), true + return {'\u22fe', 0}, 1, true case "notnivc": // CONTAINS WITH OVERBAR - return rune(0x22fd), true + return {'\u22fd', 0}, 1, true case "npar": // NOT PARALLEL TO - return rune(0x2226), true + return {'\u2226', 0}, 1, true case "nparallel": // NOT PARALLEL TO - return rune(0x2226), true + return {'\u2226', 0}, 1, true case "nparsl": // DOUBLE SOLIDUS OPERATOR with reverse slash - return rune(0x2afd), true + return {'\u2afd', '\u20e5'}, 2, true case "npart": // PARTIAL DIFFERENTIAL with slash - return rune(0x2202), true + return {'\u2202', '\u0338'}, 2, true case "npolint": // LINE INTEGRATION NOT INCLUDING THE POLE - return rune(0x2a14), true + return {'\u2a14', 0}, 1, true case "npr": // DOES NOT PRECEDE - return rune(0x2280), true + return {'\u2280', 0}, 1, true case "nprcue": // DOES NOT PRECEDE OR EQUAL - return rune(0x22e0), true + return {'\u22e0', 0}, 1, true case "npre": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash - return rune(0x2aaf), true + return {'\u2aaf', '\u0338'}, 2, true case "nprec": // DOES NOT PRECEDE - return rune(0x2280), true + return {'\u2280', 0}, 1, true case "npreceq": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash - return rune(0x2aaf), true + return {'\u2aaf', '\u0338'}, 2, true case "nprsim": // PRECEDES OR EQUIVALENT TO with slash - return rune(0x227e), true + return {'\u227e', '\u0338'}, 2, true case "nrArr": // RIGHTWARDS DOUBLE ARROW WITH STROKE - return rune(0x21cf), true + return {'\u21cf', 0}, 1, true case "nrarr": // RIGHTWARDS ARROW WITH STROKE - return rune(0x219b), true + return {'\u219b', 0}, 1, true case "nrarrc": // WAVE ARROW POINTING DIRECTLY RIGHT with slash - return rune(0x2933), true + return {'\u2933', '\u0338'}, 2, true case "nrarrw": // RIGHTWARDS WAVE ARROW with slash - return rune(0x219d), true + return {'\u219d', '\u0338'}, 2, true case "nrightarrow": // RIGHTWARDS ARROW WITH STROKE - return rune(0x219b), true + return {'\u219b', 0}, 1, true case "nrtri": // DOES NOT CONTAIN AS NORMAL SUBGROUP - return rune(0x22eb), true + return {'\u22eb', 0}, 1, true case "nrtrie": // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL - return rune(0x22ed), true + return {'\u22ed', 0}, 1, true case "nsGt": // DOUBLE NESTED GREATER-THAN with slash - return rune(0x2aa2), true + return {'\u2aa2', '\u0338'}, 2, true case "nsLt": // DOUBLE NESTED LESS-THAN with slash - return rune(0x2aa1), true + return {'\u2aa1', '\u0338'}, 2, true case "nsc": // DOES NOT SUCCEED - return rune(0x2281), true + return {'\u2281', 0}, 1, true case "nsccue": // DOES NOT SUCCEED OR EQUAL - return rune(0x22e1), true + return {'\u22e1', 0}, 1, true case "nsce": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash - return rune(0x2ab0), true + return {'\u2ab0', '\u0338'}, 2, true case "nscr": // MATHEMATICAL SCRIPT SMALL N - return rune(0x01d4c3), true + return {'\U0001d4c3', 0}, 1, true case "nscsim": // SUCCEEDS OR EQUIVALENT TO with slash - return rune(0x227f), true + return {'\u227f', '\u0338'}, 2, true case "nshortmid": // DOES NOT DIVIDE - return rune(0x2224), true + return {'\u2224', 0}, 1, true case "nshortparallel": // NOT PARALLEL TO - return rune(0x2226), true + return {'\u2226', 0}, 1, true case "nsim": // NOT TILDE - return rune(0x2241), true + return {'\u2241', 0}, 1, true case "nsime": // NOT ASYMPTOTICALLY EQUAL TO - return rune(0x2244), true + return {'\u2244', 0}, 1, true case "nsimeq": // NOT ASYMPTOTICALLY EQUAL TO - return rune(0x2244), true + return {'\u2244', 0}, 1, true case "nsmid": // DOES NOT DIVIDE - return rune(0x2224), true + return {'\u2224', 0}, 1, true case "nspar": // NOT PARALLEL TO - return rune(0x2226), true + return {'\u2226', 0}, 1, true case "nsqsub": // SQUARE IMAGE OF with slash - return rune(0x228f), true + return {'\u228f', '\u0338'}, 2, true case "nsqsube": // NOT SQUARE IMAGE OF OR EQUAL TO - return rune(0x22e2), true + return {'\u22e2', 0}, 1, true case "nsqsup": // SQUARE ORIGINAL OF with slash - return rune(0x2290), true + return {'\u2290', '\u0338'}, 2, true case "nsqsupe": // NOT SQUARE ORIGINAL OF OR EQUAL TO - return rune(0x22e3), true + return {'\u22e3', 0}, 1, true case "nsub": // NOT A SUBSET OF - return rune(0x2284), true + return {'\u2284', 0}, 1, true case "nsubE": // SUBSET OF ABOVE EQUALS SIGN with slash - return rune(0x2ac5), true + return {'\u2ac5', '\u0338'}, 2, true case "nsube": // NEITHER A SUBSET OF NOR EQUAL TO - return rune(0x2288), true + return {'\u2288', 0}, 1, true case "nsubset": // SUBSET OF with vertical line - return rune(0x2282), true + return {'\u2282', '\u20d2'}, 2, true case "nsubseteq": // NEITHER A SUBSET OF NOR EQUAL TO - return rune(0x2288), true + return {'\u2288', 0}, 1, true case "nsubseteqq": // SUBSET OF ABOVE EQUALS SIGN with slash - return rune(0x2ac5), true + return {'\u2ac5', '\u0338'}, 2, true case "nsucc": // DOES NOT SUCCEED - return rune(0x2281), true + return {'\u2281', 0}, 1, true case "nsucceq": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash - return rune(0x2ab0), true + return {'\u2ab0', '\u0338'}, 2, true case "nsup": // NOT A SUPERSET OF - return rune(0x2285), true + return {'\u2285', 0}, 1, true case "nsupE": // SUPERSET OF ABOVE EQUALS SIGN with slash - return rune(0x2ac6), true + return {'\u2ac6', '\u0338'}, 2, true case "nsupe": // NEITHER A SUPERSET OF NOR EQUAL TO - return rune(0x2289), true + return {'\u2289', 0}, 1, true case "nsupset": // SUPERSET OF with vertical line - return rune(0x2283), true + return {'\u2283', '\u20d2'}, 2, true case "nsupseteq": // NEITHER A SUPERSET OF NOR EQUAL TO - return rune(0x2289), true + return {'\u2289', 0}, 1, true case "nsupseteqq": // SUPERSET OF ABOVE EQUALS SIGN with slash - return rune(0x2ac6), true + return {'\u2ac6', '\u0338'}, 2, true case "ntgl": // NEITHER GREATER-THAN NOR LESS-THAN - return rune(0x2279), true + return {'\u2279', 0}, 1, true case "ntilde": // LATIN SMALL LETTER N WITH TILDE - return rune(0xf1), true + return {'ñ', 0}, 1, true case "ntlg": // NEITHER LESS-THAN NOR GREATER-THAN - return rune(0x2278), true + return {'\u2278', 0}, 1, true case "ntriangleleft": // NOT NORMAL SUBGROUP OF - return rune(0x22ea), true + return {'\u22ea', 0}, 1, true case "ntrianglelefteq": // NOT NORMAL SUBGROUP OF OR EQUAL TO - return rune(0x22ec), true + return {'\u22ec', 0}, 1, true case "ntriangleright": // DOES NOT CONTAIN AS NORMAL SUBGROUP - return rune(0x22eb), true + return {'\u22eb', 0}, 1, true case "ntrianglerighteq": // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL - return rune(0x22ed), true + return {'\u22ed', 0}, 1, true case "nu": // GREEK SMALL LETTER NU - return rune(0x03bd), true + return {'\u03bd', 0}, 1, true case "num": // NUMBER SIGN - return rune(0x23), true + return {'#', 0}, 1, true case "numero": // NUMERO SIGN - return rune(0x2116), true + return {'\u2116', 0}, 1, true case "numsp": // FIGURE SPACE - return rune(0x2007), true + return {'\u2007', 0}, 1, true case "nvDash": // NOT TRUE - return rune(0x22ad), true + return {'\u22ad', 0}, 1, true case "nvHarr": // LEFT RIGHT DOUBLE ARROW WITH VERTICAL STROKE - return rune(0x2904), true + return {'\u2904', 0}, 1, true case "nvap": // EQUIVALENT TO with vertical line - return rune(0x224d), true + return {'\u224d', '\u20d2'}, 2, true case "nvbrtri": // VERTICAL BAR BESIDE RIGHT TRIANGLE with slash - return rune(0x29d0), true + return {'\u29d0', '\u0338'}, 2, true case "nvdash": // DOES NOT PROVE - return rune(0x22ac), true + return {'\u22ac', 0}, 1, true case "nvge": // GREATER-THAN OR EQUAL TO with vertical line - return rune(0x2265), true + return {'\u2265', '\u20d2'}, 2, true case "nvgt": // GREATER-THAN SIGN with vertical line - return rune(0x3e), true + return {'>', '\u20d2'}, 2, true case "nvinfin": // INFINITY NEGATED WITH VERTICAL BAR - return rune(0x29de), true + return {'\u29de', 0}, 1, true case "nvlArr": // LEFTWARDS DOUBLE ARROW WITH VERTICAL STROKE - return rune(0x2902), true + return {'\u2902', 0}, 1, true case "nvle": // LESS-THAN OR EQUAL TO with vertical line - return rune(0x2264), true + return {'\u2264', '\u20d2'}, 2, true case "nvlt": // LESS-THAN SIGN with vertical line - return rune(0x3c), true + return {'<', '\u20d2'}, 2, true case "nvltrie": // NORMAL SUBGROUP OF OR EQUAL TO with vertical line - return rune(0x22b4), true + return {'\u22b4', '\u20d2'}, 2, true case "nvrArr": // RIGHTWARDS DOUBLE ARROW WITH VERTICAL STROKE - return rune(0x2903), true + return {'\u2903', 0}, 1, true case "nvrtrie": // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO with vertical line - return rune(0x22b5), true + return {'\u22b5', '\u20d2'}, 2, true case "nvsim": // TILDE OPERATOR with vertical line - return rune(0x223c), true + return {'\u223c', '\u20d2'}, 2, true case "nwArr": // NORTH WEST DOUBLE ARROW - return rune(0x21d6), true + return {'\u21d6', 0}, 1, true case "nwarhk": // NORTH WEST ARROW WITH HOOK - return rune(0x2923), true + return {'\u2923', 0}, 1, true case "nwarr": // NORTH WEST ARROW - return rune(0x2196), true + return {'\u2196', 0}, 1, true case "nwarrow": // NORTH WEST ARROW - return rune(0x2196), true + return {'\u2196', 0}, 1, true case "nwnear": // NORTH WEST ARROW AND NORTH EAST ARROW - return rune(0x2927), true + return {'\u2927', 0}, 1, true case "nwonearr": // NORTH WEST ARROW CROSSING NORTH EAST ARROW - return rune(0x2932), true + return {'\u2932', 0}, 1, true case "nwsesarr": // NORTH WEST AND SOUTH EAST ARROW - return rune(0x2921), true + return {'\u2921', 0}, 1, true } case 'o': switch name { case "oS": // CIRCLED LATIN CAPITAL LETTER S - return rune(0x24c8), true + return {'\u24c8', 0}, 1, true case "oacgr": // GREEK SMALL LETTER OMICRON WITH TONOS - return rune(0x03cc), true + return {'\u03cc', 0}, 1, true case "oacute": // LATIN SMALL LETTER O WITH ACUTE - return rune(0xf3), true + return {'ó', 0}, 1, true case "oast": // CIRCLED ASTERISK OPERATOR - return rune(0x229b), true + return {'\u229b', 0}, 1, true case "obsol": // CIRCLED REVERSE SOLIDUS - return rune(0x29b8), true + return {'\u29b8', 0}, 1, true case "ocir": // CIRCLED RING OPERATOR - return rune(0x229a), true + return {'\u229a', 0}, 1, true case "ocirc": // LATIN SMALL LETTER O WITH CIRCUMFLEX - return rune(0xf4), true + return {'ô', 0}, 1, true case "ocy": // CYRILLIC SMALL LETTER O - return rune(0x043e), true + return {'\u043e', 0}, 1, true case "odash": // CIRCLED DASH - return rune(0x229d), true + return {'\u229d', 0}, 1, true case "odblac": // LATIN SMALL LETTER O WITH DOUBLE ACUTE - return rune(0x0151), true + return {'\u0151', 0}, 1, true case "odiv": // CIRCLED DIVISION SIGN - return rune(0x2a38), true + return {'\u2a38', 0}, 1, true case "odot": // CIRCLED DOT OPERATOR - return rune(0x2299), true + return {'\u2299', 0}, 1, true case "odsold": // CIRCLED ANTICLOCKWISE-ROTATED DIVISION SIGN - return rune(0x29bc), true + return {'\u29bc', 0}, 1, true case "oelig": // LATIN SMALL LIGATURE OE - return rune(0x0153), true + return {'\u0153', 0}, 1, true case "ofcir": // CIRCLED BULLET - return rune(0x29bf), true + return {'\u29bf', 0}, 1, true case "ofr": // MATHEMATICAL FRAKTUR SMALL O - return rune(0x01d52c), true + return {'\U0001d52c', 0}, 1, true case "ogon": // OGONEK - return rune(0x02db), true + return {'\u02db', 0}, 1, true case "ogr": // GREEK SMALL LETTER OMICRON - return rune(0x03bf), true + return {'\u03bf', 0}, 1, true case "ograve": // LATIN SMALL LETTER O WITH GRAVE - return rune(0xf2), true + return {'ò', 0}, 1, true case "ogt": // CIRCLED GREATER-THAN - return rune(0x29c1), true + return {'\u29c1', 0}, 1, true case "ohacgr": // GREEK SMALL LETTER OMEGA WITH TONOS - return rune(0x03ce), true + return {'\u03ce', 0}, 1, true case "ohbar": // CIRCLE WITH HORIZONTAL BAR - return rune(0x29b5), true + return {'\u29b5', 0}, 1, true case "ohgr": // GREEK SMALL LETTER OMEGA - return rune(0x03c9), true + return {'\u03c9', 0}, 1, true case "ohm": // GREEK CAPITAL LETTER OMEGA - return rune(0x03a9), true + return {'\u03a9', 0}, 1, true case "oint": // CONTOUR INTEGRAL - return rune(0x222e), true + return {'\u222e', 0}, 1, true case "olarr": // ANTICLOCKWISE OPEN CIRCLE ARROW - return rune(0x21ba), true + return {'\u21ba', 0}, 1, true case "olcir": // CIRCLED WHITE BULLET - return rune(0x29be), true + return {'\u29be', 0}, 1, true case "olcross": // CIRCLE WITH SUPERIMPOSED X - return rune(0x29bb), true + return {'\u29bb', 0}, 1, true case "oline": // OVERLINE - return rune(0x203e), true + return {'\u203e', 0}, 1, true case "olt": // CIRCLED LESS-THAN - return rune(0x29c0), true + return {'\u29c0', 0}, 1, true case "omacr": // LATIN SMALL LETTER O WITH MACRON - return rune(0x014d), true + return {'\u014d', 0}, 1, true case "omega": // GREEK SMALL LETTER OMEGA - return rune(0x03c9), true + return {'\u03c9', 0}, 1, true case "omicron": // GREEK SMALL LETTER OMICRON - return rune(0x03bf), true + return {'\u03bf', 0}, 1, true case "omid": // CIRCLED VERTICAL BAR - return rune(0x29b6), true + return {'\u29b6', 0}, 1, true case "ominus": // CIRCLED MINUS - return rune(0x2296), true + return {'\u2296', 0}, 1, true case "oopf": // MATHEMATICAL DOUBLE-STRUCK SMALL O - return rune(0x01d560), true + return {'\U0001d560', 0}, 1, true case "opar": // CIRCLED PARALLEL - return rune(0x29b7), true + return {'\u29b7', 0}, 1, true case "operp": // CIRCLED PERPENDICULAR - return rune(0x29b9), true + return {'\u29b9', 0}, 1, true case "opfgamma": // DOUBLE-STRUCK SMALL GAMMA - return rune(0x213d), true + return {'\u213d', 0}, 1, true case "opfpi": // DOUBLE-STRUCK CAPITAL PI - return rune(0x213f), true + return {'\u213f', 0}, 1, true case "opfsum": // DOUBLE-STRUCK N-ARY SUMMATION - return rune(0x2140), true + return {'\u2140', 0}, 1, true case "oplus": // CIRCLED PLUS - return rune(0x2295), true + return {'\u2295', 0}, 1, true case "or": // LOGICAL OR - return rune(0x2228), true + return {'\u2228', 0}, 1, true case "orarr": // CLOCKWISE OPEN CIRCLE ARROW - return rune(0x21bb), true + return {'\u21bb', 0}, 1, true case "ord": // LOGICAL OR WITH HORIZONTAL DASH - return rune(0x2a5d), true + return {'\u2a5d', 0}, 1, true case "order": // SCRIPT SMALL O - return rune(0x2134), true + return {'\u2134', 0}, 1, true case "orderof": // SCRIPT SMALL O - return rune(0x2134), true + return {'\u2134', 0}, 1, true case "ordf": // FEMININE ORDINAL INDICATOR - return rune(0xaa), true + return {'ª', 0}, 1, true case "ordm": // MASCULINE ORDINAL INDICATOR - return rune(0xba), true + return {'º', 0}, 1, true case "origof": // ORIGINAL OF - return rune(0x22b6), true + return {'\u22b6', 0}, 1, true case "oror": // TWO INTERSECTING LOGICAL OR - return rune(0x2a56), true + return {'\u2a56', 0}, 1, true case "orslope": // SLOPING LARGE OR - return rune(0x2a57), true + return {'\u2a57', 0}, 1, true case "orv": // LOGICAL OR WITH MIDDLE STEM - return rune(0x2a5b), true + return {'\u2a5b', 0}, 1, true case "oscr": // SCRIPT SMALL O - return rune(0x2134), true + return {'\u2134', 0}, 1, true case "oslash": // LATIN SMALL LETTER O WITH STROKE - return rune(0xf8), true + return {'ø', 0}, 1, true case "osol": // CIRCLED DIVISION SLASH - return rune(0x2298), true + return {'\u2298', 0}, 1, true case "otilde": // LATIN SMALL LETTER O WITH TILDE - return rune(0xf5), true + return {'õ', 0}, 1, true case "otimes": // CIRCLED TIMES - return rune(0x2297), true + return {'\u2297', 0}, 1, true case "otimesas": // CIRCLED MULTIPLICATION SIGN WITH CIRCUMFLEX ACCENT - return rune(0x2a36), true + return {'\u2a36', 0}, 1, true case "ouml": // LATIN SMALL LETTER O WITH DIAERESIS - return rune(0xf6), true + return {'ö', 0}, 1, true case "ovbar": // APL FUNCTIONAL SYMBOL CIRCLE STILE - return rune(0x233d), true + return {'\u233d', 0}, 1, true case "ovrbrk": // TOP SQUARE BRACKET - return rune(0x23b4), true + return {'\u23b4', 0}, 1, true case "ovrcub": // TOP CURLY BRACKET - return rune(0x23de), true + return {'\u23de', 0}, 1, true case "ovrpar": // TOP PARENTHESIS - return rune(0x23dc), true + return {'\u23dc', 0}, 1, true case "oxuarr": // UP ARROW THROUGH CIRCLE - return rune(0x29bd), true + return {'\u29bd', 0}, 1, true } case 'p': switch name { case "par": // PARALLEL TO - return rune(0x2225), true + return {'\u2225', 0}, 1, true case "para": // PILCROW SIGN - return rune(0xb6), true + return {'¶', 0}, 1, true case "parallel": // PARALLEL TO - return rune(0x2225), true + return {'\u2225', 0}, 1, true case "parsim": // PARALLEL WITH TILDE OPERATOR - return rune(0x2af3), true + return {'\u2af3', 0}, 1, true case "parsl": // DOUBLE SOLIDUS OPERATOR - return rune(0x2afd), true + return {'\u2afd', 0}, 1, true case "part": // PARTIAL DIFFERENTIAL - return rune(0x2202), true + return {'\u2202', 0}, 1, true case "pcy": // CYRILLIC SMALL LETTER PE - return rune(0x043f), true + return {'\u043f', 0}, 1, true case "percnt": // PERCENT SIGN - return rune(0x25), true + return {'%', 0}, 1, true case "period": // FULL STOP - return rune(0x2e), true + return {'.', 0}, 1, true case "permil": // PER MILLE SIGN - return rune(0x2030), true + return {'\u2030', 0}, 1, true case "perp": // UP TACK - return rune(0x22a5), true + return {'\u22a5', 0}, 1, true case "pertenk": // PER TEN THOUSAND SIGN - return rune(0x2031), true + return {'\u2031', 0}, 1, true case "pfr": // MATHEMATICAL FRAKTUR SMALL P - return rune(0x01d52d), true + return {'\U0001d52d', 0}, 1, true case "pgr": // GREEK SMALL LETTER PI - return rune(0x03c0), true + return {'\u03c0', 0}, 1, true case "phgr": // GREEK SMALL LETTER PHI - return rune(0x03c6), true + return {'\u03c6', 0}, 1, true case "phi": // GREEK SMALL LETTER PHI - return rune(0x03c6), true + return {'\u03c6', 0}, 1, true case "phis": // GREEK PHI SYMBOL - return rune(0x03d5), true + return {'\u03d5', 0}, 1, true case "phiv": // GREEK PHI SYMBOL - return rune(0x03d5), true + return {'\u03d5', 0}, 1, true case "phmmat": // SCRIPT CAPITAL M - return rune(0x2133), true + return {'\u2133', 0}, 1, true case "phone": // BLACK TELEPHONE - return rune(0x260e), true + return {'\u260e', 0}, 1, true case "pi": // GREEK SMALL LETTER PI - return rune(0x03c0), true + return {'\u03c0', 0}, 1, true case "pitchfork": // PITCHFORK - return rune(0x22d4), true + return {'\u22d4', 0}, 1, true case "piv": // GREEK PI SYMBOL - return rune(0x03d6), true + return {'\u03d6', 0}, 1, true case "planck": // PLANCK CONSTANT OVER TWO PI - return rune(0x210f), true + return {'\u210f', 0}, 1, true case "planckh": // PLANCK CONSTANT - return rune(0x210e), true + return {'\u210e', 0}, 1, true case "plankv": // PLANCK CONSTANT OVER TWO PI - return rune(0x210f), true + return {'\u210f', 0}, 1, true case "plus": // PLUS SIGN - return rune(0x2b), true + return {'+', 0}, 1, true case "plusacir": // PLUS SIGN WITH CIRCUMFLEX ACCENT ABOVE - return rune(0x2a23), true + return {'\u2a23', 0}, 1, true case "plusb": // SQUARED PLUS - return rune(0x229e), true + return {'\u229e', 0}, 1, true case "pluscir": // PLUS SIGN WITH SMALL CIRCLE ABOVE - return rune(0x2a22), true + return {'\u2a22', 0}, 1, true case "plusdo": // DOT PLUS - return rune(0x2214), true + return {'\u2214', 0}, 1, true case "plusdu": // PLUS SIGN WITH DOT BELOW - return rune(0x2a25), true + return {'\u2a25', 0}, 1, true case "pluse": // PLUS SIGN ABOVE EQUALS SIGN - return rune(0x2a72), true + return {'\u2a72', 0}, 1, true case "plusmn": // PLUS-MINUS SIGN - return rune(0xb1), true + return {'±', 0}, 1, true case "plussim": // PLUS SIGN WITH TILDE BELOW - return rune(0x2a26), true + return {'\u2a26', 0}, 1, true case "plustrif": // PLUS SIGN WITH BLACK TRIANGLE - return rune(0x2a28), true + return {'\u2a28', 0}, 1, true case "plustwo": // PLUS SIGN WITH SUBSCRIPT TWO - return rune(0x2a27), true + return {'\u2a27', 0}, 1, true case "pm": // PLUS-MINUS SIGN - return rune(0xb1), true + return {'±', 0}, 1, true case "pointint": // INTEGRAL AROUND A POINT OPERATOR - return rune(0x2a15), true + return {'\u2a15', 0}, 1, true case "popf": // MATHEMATICAL DOUBLE-STRUCK SMALL P - return rune(0x01d561), true + return {'\U0001d561', 0}, 1, true case "pound": // POUND SIGN - return rune(0xa3), true + return {'£', 0}, 1, true case "pr": // PRECEDES - return rune(0x227a), true + return {'\u227a', 0}, 1, true case "prE": // PRECEDES ABOVE EQUALS SIGN - return rune(0x2ab3), true + return {'\u2ab3', 0}, 1, true case "prap": // PRECEDES ABOVE ALMOST EQUAL TO - return rune(0x2ab7), true + return {'\u2ab7', 0}, 1, true case "prcue": // PRECEDES OR EQUAL TO - return rune(0x227c), true + return {'\u227c', 0}, 1, true case "pre": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN - return rune(0x2aaf), true + return {'\u2aaf', 0}, 1, true case "prec": // PRECEDES - return rune(0x227a), true + return {'\u227a', 0}, 1, true case "precapprox": // PRECEDES ABOVE ALMOST EQUAL TO - return rune(0x2ab7), true + return {'\u2ab7', 0}, 1, true case "preccurlyeq": // PRECEDES OR EQUAL TO - return rune(0x227c), true + return {'\u227c', 0}, 1, true case "preceq": // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN - return rune(0x2aaf), true + return {'\u2aaf', 0}, 1, true case "precnapprox": // PRECEDES ABOVE NOT ALMOST EQUAL TO - return rune(0x2ab9), true + return {'\u2ab9', 0}, 1, true case "precneqq": // PRECEDES ABOVE NOT EQUAL TO - return rune(0x2ab5), true + return {'\u2ab5', 0}, 1, true case "precnsim": // PRECEDES BUT NOT EQUIVALENT TO - return rune(0x22e8), true + return {'\u22e8', 0}, 1, true case "precsim": // PRECEDES OR EQUIVALENT TO - return rune(0x227e), true + return {'\u227e', 0}, 1, true case "prime": // PRIME - return rune(0x2032), true + return {'\u2032', 0}, 1, true case "primes": // DOUBLE-STRUCK CAPITAL P - return rune(0x2119), true + return {'\u2119', 0}, 1, true case "prnE": // PRECEDES ABOVE NOT EQUAL TO - return rune(0x2ab5), true + return {'\u2ab5', 0}, 1, true case "prnap": // PRECEDES ABOVE NOT ALMOST EQUAL TO - return rune(0x2ab9), true + return {'\u2ab9', 0}, 1, true case "prnsim": // PRECEDES BUT NOT EQUIVALENT TO - return rune(0x22e8), true + return {'\u22e8', 0}, 1, true case "prod": // N-ARY PRODUCT - return rune(0x220f), true + return {'\u220f', 0}, 1, true case "profalar": // ALL AROUND-PROFILE - return rune(0x232e), true + return {'\u232e', 0}, 1, true case "profline": // ARC - return rune(0x2312), true + return {'\u2312', 0}, 1, true case "profsurf": // SEGMENT - return rune(0x2313), true + return {'\u2313', 0}, 1, true case "prop": // PROPORTIONAL TO - return rune(0x221d), true + return {'\u221d', 0}, 1, true case "propto": // PROPORTIONAL TO - return rune(0x221d), true + return {'\u221d', 0}, 1, true case "prsim": // PRECEDES OR EQUIVALENT TO - return rune(0x227e), true + return {'\u227e', 0}, 1, true case "prurel": // PRECEDES UNDER RELATION - return rune(0x22b0), true + return {'\u22b0', 0}, 1, true case "pscr": // MATHEMATICAL SCRIPT SMALL P - return rune(0x01d4c5), true + return {'\U0001d4c5', 0}, 1, true case "psgr": // GREEK SMALL LETTER PSI - return rune(0x03c8), true + return {'\u03c8', 0}, 1, true case "psi": // GREEK SMALL LETTER PSI - return rune(0x03c8), true + return {'\u03c8', 0}, 1, true case "puncsp": // PUNCTUATION SPACE - return rune(0x2008), true + return {'\u2008', 0}, 1, true } case 'q': switch name { case "qfr": // MATHEMATICAL FRAKTUR SMALL Q - return rune(0x01d52e), true + return {'\U0001d52e', 0}, 1, true case "qint": // QUADRUPLE INTEGRAL OPERATOR - return rune(0x2a0c), true + return {'\u2a0c', 0}, 1, true case "qopf": // MATHEMATICAL DOUBLE-STRUCK SMALL Q - return rune(0x01d562), true + return {'\U0001d562', 0}, 1, true case "qprime": // QUADRUPLE PRIME - return rune(0x2057), true + return {'\u2057', 0}, 1, true case "qscr": // MATHEMATICAL SCRIPT SMALL Q - return rune(0x01d4c6), true + return {'\U0001d4c6', 0}, 1, true case "quaternions": // DOUBLE-STRUCK CAPITAL H - return rune(0x210d), true + return {'\u210d', 0}, 1, true case "quatint": // QUATERNION INTEGRAL OPERATOR - return rune(0x2a16), true + return {'\u2a16', 0}, 1, true case "quest": // QUESTION MARK - return rune(0x3f), true + return {'?', 0}, 1, true case "questeq": // QUESTIONED EQUAL TO - return rune(0x225f), true + return {'\u225f', 0}, 1, true case "quot": // QUOTATION MARK - return rune(0x22), true + return {'"', 0}, 1, true } case 'r': switch name { case "rAarr": // RIGHTWARDS TRIPLE ARROW - return rune(0x21db), true + return {'\u21db', 0}, 1, true case "rArr": // RIGHTWARDS DOUBLE ARROW - return rune(0x21d2), true + return {'\u21d2', 0}, 1, true case "rAtail": // RIGHTWARDS DOUBLE ARROW-TAIL - return rune(0x291c), true + return {'\u291c', 0}, 1, true case "rBarr": // RIGHTWARDS TRIPLE DASH ARROW - return rune(0x290f), true + return {'\u290f', 0}, 1, true case "rHar": // RIGHTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB DOWN - return rune(0x2964), true + return {'\u2964', 0}, 1, true case "race": // REVERSED TILDE with underline - return rune(0x223d), true + return {'\u223d', '\u0331'}, 2, true case "racute": // LATIN SMALL LETTER R WITH ACUTE - return rune(0x0155), true + return {'\u0155', 0}, 1, true case "radic": // SQUARE ROOT - return rune(0x221a), true + return {'\u221a', 0}, 1, true case "raemptyv": // EMPTY SET WITH RIGHT ARROW ABOVE - return rune(0x29b3), true + return {'\u29b3', 0}, 1, true case "rang": // MATHEMATICAL RIGHT ANGLE BRACKET - return rune(0x27e9), true + return {'\u27e9', 0}, 1, true case "rangd": // RIGHT ANGLE BRACKET WITH DOT - return rune(0x2992), true + return {'\u2992', 0}, 1, true case "range": // REVERSED ANGLE WITH UNDERBAR - return rune(0x29a5), true + return {'\u29a5', 0}, 1, true case "rangle": // MATHEMATICAL RIGHT ANGLE BRACKET - return rune(0x27e9), true + return {'\u27e9', 0}, 1, true case "raquo": // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK - return rune(0xbb), true + return {'»', 0}, 1, true case "rarr": // RIGHTWARDS ARROW - return rune(0x2192), true + return {'\u2192', 0}, 1, true case "rarr2": // RIGHTWARDS PAIRED ARROWS - return rune(0x21c9), true + return {'\u21c9', 0}, 1, true case "rarr3": // THREE RIGHTWARDS ARROWS - return rune(0x21f6), true + return {'\u21f6', 0}, 1, true case "rarrap": // RIGHTWARDS ARROW ABOVE ALMOST EQUAL TO - return rune(0x2975), true + return {'\u2975', 0}, 1, true case "rarrb": // RIGHTWARDS ARROW TO BAR - return rune(0x21e5), true + return {'\u21e5', 0}, 1, true case "rarrbfs": // RIGHTWARDS ARROW FROM BAR TO BLACK DIAMOND - return rune(0x2920), true + return {'\u2920', 0}, 1, true case "rarrc": // WAVE ARROW POINTING DIRECTLY RIGHT - return rune(0x2933), true + return {'\u2933', 0}, 1, true case "rarrfs": // RIGHTWARDS ARROW TO BLACK DIAMOND - return rune(0x291e), true + return {'\u291e', 0}, 1, true case "rarrhk": // RIGHTWARDS ARROW WITH HOOK - return rune(0x21aa), true + return {'\u21aa', 0}, 1, true case "rarrlp": // RIGHTWARDS ARROW WITH LOOP - return rune(0x21ac), true + return {'\u21ac', 0}, 1, true case "rarrpl": // RIGHTWARDS ARROW WITH PLUS BELOW - return rune(0x2945), true + return {'\u2945', 0}, 1, true case "rarrsim": // RIGHTWARDS ARROW ABOVE TILDE OPERATOR - return rune(0x2974), true + return {'\u2974', 0}, 1, true case "rarrtl": // RIGHTWARDS ARROW WITH TAIL - return rune(0x21a3), true + return {'\u21a3', 0}, 1, true case "rarrw": // RIGHTWARDS WAVE ARROW - return rune(0x219d), true + return {'\u219d', 0}, 1, true case "rarrx": // RIGHTWARDS ARROW THROUGH X - return rune(0x2947), true + return {'\u2947', 0}, 1, true case "ratail": // RIGHTWARDS ARROW-TAIL - return rune(0x291a), true + return {'\u291a', 0}, 1, true case "ratio": // RATIO - return rune(0x2236), true + return {'\u2236', 0}, 1, true case "rationals": // DOUBLE-STRUCK CAPITAL Q - return rune(0x211a), true + return {'\u211a', 0}, 1, true case "rbarr": // RIGHTWARDS DOUBLE DASH ARROW - return rune(0x290d), true + return {'\u290d', 0}, 1, true case "rbbrk": // LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT - return rune(0x2773), true + return {'\u2773', 0}, 1, true case "rbrace": // RIGHT CURLY BRACKET - return rune(0x7d), true + return {'}', 0}, 1, true case "rbrack": // RIGHT SQUARE BRACKET - return rune(0x5d), true + return {']', 0}, 1, true case "rbrke": // RIGHT SQUARE BRACKET WITH UNDERBAR - return rune(0x298c), true + return {'\u298c', 0}, 1, true case "rbrksld": // RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER - return rune(0x298e), true + return {'\u298e', 0}, 1, true case "rbrkslu": // RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER - return rune(0x2990), true + return {'\u2990', 0}, 1, true case "rcaron": // LATIN SMALL LETTER R WITH CARON - return rune(0x0159), true + return {'\u0159', 0}, 1, true case "rcedil": // LATIN SMALL LETTER R WITH CEDILLA - return rune(0x0157), true + return {'\u0157', 0}, 1, true case "rceil": // RIGHT CEILING - return rune(0x2309), true + return {'\u2309', 0}, 1, true case "rcub": // RIGHT CURLY BRACKET - return rune(0x7d), true + return {'}', 0}, 1, true case "rcy": // CYRILLIC SMALL LETTER ER - return rune(0x0440), true + return {'\u0440', 0}, 1, true case "rdca": // ARROW POINTING DOWNWARDS THEN CURVING RIGHTWARDS - return rune(0x2937), true + return {'\u2937', 0}, 1, true case "rdharb": // RIGHTWARDS HARPOON WITH BARB DOWN TO BAR - return rune(0x2957), true + return {'\u2957', 0}, 1, true case "rdiag": // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT - return rune(0x2571), true + return {'\u2571', 0}, 1, true case "rdiofdi": // RISING DIAGONAL CROSSING FALLING DIAGONAL - return rune(0x292b), true + return {'\u292b', 0}, 1, true case "rdldhar": // RIGHTWARDS HARPOON WITH BARB DOWN ABOVE LEFTWARDS HARPOON WITH BARB DOWN - return rune(0x2969), true + return {'\u2969', 0}, 1, true case "rdosearr": // RISING DIAGONAL CROSSING SOUTH EAST ARROW - return rune(0x2930), true + return {'\u2930', 0}, 1, true case "rdquo": // RIGHT DOUBLE QUOTATION MARK - return rune(0x201d), true + return {'\u201d', 0}, 1, true case "rdquor": // RIGHT DOUBLE QUOTATION MARK - return rune(0x201d), true + return {'\u201d', 0}, 1, true case "rdsh": // DOWNWARDS ARROW WITH TIP RIGHTWARDS - return rune(0x21b3), true + return {'\u21b3', 0}, 1, true case "real": // BLACK-LETTER CAPITAL R - return rune(0x211c), true + return {'\u211c', 0}, 1, true case "realine": // SCRIPT CAPITAL R - return rune(0x211b), true + return {'\u211b', 0}, 1, true case "realpart": // BLACK-LETTER CAPITAL R - return rune(0x211c), true + return {'\u211c', 0}, 1, true case "reals": // DOUBLE-STRUCK CAPITAL R - return rune(0x211d), true + return {'\u211d', 0}, 1, true case "rect": // WHITE RECTANGLE - return rune(0x25ad), true + return {'\u25ad', 0}, 1, true case "reg": // REGISTERED SIGN - return rune(0xae), true + return {'®', 0}, 1, true case "rfbowtie": // BOWTIE WITH RIGHT HALF BLACK - return rune(0x29d2), true + return {'\u29d2', 0}, 1, true case "rfisht": // RIGHT FISH TAIL - return rune(0x297d), true + return {'\u297d', 0}, 1, true case "rfloor": // RIGHT FLOOR - return rune(0x230b), true + return {'\u230b', 0}, 1, true case "rfr": // MATHEMATICAL FRAKTUR SMALL R - return rune(0x01d52f), true + return {'\U0001d52f', 0}, 1, true case "rftimes": // TIMES WITH RIGHT HALF BLACK - return rune(0x29d5), true + return {'\u29d5', 0}, 1, true case "rgr": // GREEK SMALL LETTER RHO - return rune(0x03c1), true + return {'\u03c1', 0}, 1, true case "rhard": // RIGHTWARDS HARPOON WITH BARB DOWNWARDS - return rune(0x21c1), true + return {'\u21c1', 0}, 1, true case "rharu": // RIGHTWARDS HARPOON WITH BARB UPWARDS - return rune(0x21c0), true + return {'\u21c0', 0}, 1, true case "rharul": // RIGHTWARDS HARPOON WITH BARB UP ABOVE LONG DASH - return rune(0x296c), true + return {'\u296c', 0}, 1, true case "rho": // GREEK SMALL LETTER RHO - return rune(0x03c1), true + return {'\u03c1', 0}, 1, true case "rhov": // GREEK RHO SYMBOL - return rune(0x03f1), true + return {'\u03f1', 0}, 1, true case "rightarrow": // RIGHTWARDS ARROW - return rune(0x2192), true + return {'\u2192', 0}, 1, true case "rightarrowtail": // RIGHTWARDS ARROW WITH TAIL - return rune(0x21a3), true + return {'\u21a3', 0}, 1, true case "rightharpoondown": // RIGHTWARDS HARPOON WITH BARB DOWNWARDS - return rune(0x21c1), true + return {'\u21c1', 0}, 1, true case "rightharpoonup": // RIGHTWARDS HARPOON WITH BARB UPWARDS - return rune(0x21c0), true + return {'\u21c0', 0}, 1, true case "rightleftarrows": // RIGHTWARDS ARROW OVER LEFTWARDS ARROW - return rune(0x21c4), true + return {'\u21c4', 0}, 1, true case "rightleftharpoons": // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON - return rune(0x21cc), true + return {'\u21cc', 0}, 1, true case "rightrightarrows": // RIGHTWARDS PAIRED ARROWS - return rune(0x21c9), true + return {'\u21c9', 0}, 1, true case "rightsquigarrow": // RIGHTWARDS WAVE ARROW - return rune(0x219d), true + return {'\u219d', 0}, 1, true case "rightthreetimes": // RIGHT SEMIDIRECT PRODUCT - return rune(0x22cc), true + return {'\u22cc', 0}, 1, true case "rimply": // RIGHT DOUBLE ARROW WITH ROUNDED HEAD - return rune(0x2970), true + return {'\u2970', 0}, 1, true case "ring": // RING ABOVE - return rune(0x02da), true + return {'\u02da', 0}, 1, true case "risingdotseq": // IMAGE OF OR APPROXIMATELY EQUAL TO - return rune(0x2253), true + return {'\u2253', 0}, 1, true case "rlarr": // RIGHTWARDS ARROW OVER LEFTWARDS ARROW - return rune(0x21c4), true + return {'\u21c4', 0}, 1, true case "rlarr2": // RIGHTWARDS ARROW OVER LEFTWARDS ARROW - return rune(0x21c4), true + return {'\u21c4', 0}, 1, true case "rlhar": // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON - return rune(0x21cc), true + return {'\u21cc', 0}, 1, true case "rlhar2": // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON - return rune(0x21cc), true + return {'\u21cc', 0}, 1, true case "rlm": // RIGHT-TO-LEFT MARK - return rune(0x200f), true + return {'\u200f', 0}, 1, true case "rmoust": // UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION - return rune(0x23b1), true + return {'\u23b1', 0}, 1, true case "rmoustache": // UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION - return rune(0x23b1), true + return {'\u23b1', 0}, 1, true case "rnmid": // DOES NOT DIVIDE WITH REVERSED NEGATION SLASH - return rune(0x2aee), true + return {'\u2aee', 0}, 1, true case "roang": // MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET - return rune(0x27ed), true + return {'\u27ed', 0}, 1, true case "roarr": // RIGHTWARDS OPEN-HEADED ARROW - return rune(0x21fe), true + return {'\u21fe', 0}, 1, true case "robrk": // MATHEMATICAL RIGHT WHITE SQUARE BRACKET - return rune(0x27e7), true + return {'\u27e7', 0}, 1, true case "rocub": // RIGHT WHITE CURLY BRACKET - return rune(0x2984), true + return {'\u2984', 0}, 1, true case "ropar": // RIGHT WHITE PARENTHESIS - return rune(0x2986), true + return {'\u2986', 0}, 1, true case "ropf": // MATHEMATICAL DOUBLE-STRUCK SMALL R - return rune(0x01d563), true + return {'\U0001d563', 0}, 1, true case "roplus": // PLUS SIGN IN RIGHT HALF CIRCLE - return rune(0x2a2e), true + return {'\u2a2e', 0}, 1, true case "rotimes": // MULTIPLICATION SIGN IN RIGHT HALF CIRCLE - return rune(0x2a35), true + return {'\u2a35', 0}, 1, true case "rpar": // RIGHT PARENTHESIS - return rune(0x29), true + return {')', 0}, 1, true case "rpargt": // RIGHT ARC GREATER-THAN BRACKET - return rune(0x2994), true + return {'\u2994', 0}, 1, true case "rppolint": // LINE INTEGRATION WITH RECTANGULAR PATH AROUND POLE - return rune(0x2a12), true + return {'\u2a12', 0}, 1, true case "rrarr": // RIGHTWARDS PAIRED ARROWS - return rune(0x21c9), true + return {'\u21c9', 0}, 1, true case "rsaquo": // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK - return rune(0x203a), true + return {'\u203a', 0}, 1, true case "rscr": // MATHEMATICAL SCRIPT SMALL R - return rune(0x01d4c7), true + return {'\U0001d4c7', 0}, 1, true case "rsh": // UPWARDS ARROW WITH TIP RIGHTWARDS - return rune(0x21b1), true + return {'\u21b1', 0}, 1, true case "rsolbar": // REVERSE SOLIDUS WITH HORIZONTAL STROKE - return rune(0x29f7), true + return {'\u29f7', 0}, 1, true case "rsqb": // RIGHT SQUARE BRACKET - return rune(0x5d), true + return {']', 0}, 1, true case "rsquo": // RIGHT SINGLE QUOTATION MARK - return rune(0x2019), true + return {'\u2019', 0}, 1, true case "rsquor": // RIGHT SINGLE QUOTATION MARK - return rune(0x2019), true + return {'\u2019', 0}, 1, true case "rthree": // RIGHT SEMIDIRECT PRODUCT - return rune(0x22cc), true + return {'\u22cc', 0}, 1, true case "rtimes": // RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT - return rune(0x22ca), true + return {'\u22ca', 0}, 1, true case "rtri": // WHITE RIGHT-POINTING SMALL TRIANGLE - return rune(0x25b9), true + return {'\u25b9', 0}, 1, true case "rtrie": // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO - return rune(0x22b5), true + return {'\u22b5', 0}, 1, true case "rtrif": // BLACK RIGHT-POINTING SMALL TRIANGLE - return rune(0x25b8), true + return {'\u25b8', 0}, 1, true case "rtriltri": // RIGHT TRIANGLE ABOVE LEFT TRIANGLE - return rune(0x29ce), true + return {'\u29ce', 0}, 1, true case "ruharb": // RIGHTWARDS HARPOON WITH BARB UP TO BAR - return rune(0x2953), true + return {'\u2953', 0}, 1, true case "ruluhar": // RIGHTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB UP - return rune(0x2968), true + return {'\u2968', 0}, 1, true case "rx": // PRESCRIPTION TAKE - return rune(0x211e), true + return {'\u211e', 0}, 1, true } case 's': switch name { case "sacute": // LATIN SMALL LETTER S WITH ACUTE - return rune(0x015b), true + return {'\u015b', 0}, 1, true case "samalg": // N-ARY COPRODUCT - return rune(0x2210), true + return {'\u2210', 0}, 1, true case "sampi": // GREEK LETTER SAMPI - return rune(0x03e0), true + return {'\u03e0', 0}, 1, true case "sbquo": // SINGLE LOW-9 QUOTATION MARK - return rune(0x201a), true + return {'\u201a', 0}, 1, true case "sbsol": // SMALL REVERSE SOLIDUS - return rune(0xfe68), true + return {'\ufe68', 0}, 1, true case "sc": // SUCCEEDS - return rune(0x227b), true + return {'\u227b', 0}, 1, true case "scE": // SUCCEEDS ABOVE EQUALS SIGN - return rune(0x2ab4), true + return {'\u2ab4', 0}, 1, true case "scap": // SUCCEEDS ABOVE ALMOST EQUAL TO - return rune(0x2ab8), true + return {'\u2ab8', 0}, 1, true case "scaron": // LATIN SMALL LETTER S WITH CARON - return rune(0x0161), true + return {'\u0161', 0}, 1, true case "sccue": // SUCCEEDS OR EQUAL TO - return rune(0x227d), true + return {'\u227d', 0}, 1, true case "sce": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN - return rune(0x2ab0), true + return {'\u2ab0', 0}, 1, true case "scedil": // LATIN SMALL LETTER S WITH CEDILLA - return rune(0x015f), true + return {'\u015f', 0}, 1, true case "scirc": // LATIN SMALL LETTER S WITH CIRCUMFLEX - return rune(0x015d), true + return {'\u015d', 0}, 1, true case "scnE": // SUCCEEDS ABOVE NOT EQUAL TO - return rune(0x2ab6), true + return {'\u2ab6', 0}, 1, true case "scnap": // SUCCEEDS ABOVE NOT ALMOST EQUAL TO - return rune(0x2aba), true + return {'\u2aba', 0}, 1, true case "scnsim": // SUCCEEDS BUT NOT EQUIVALENT TO - return rune(0x22e9), true + return {'\u22e9', 0}, 1, true case "scpolint": // LINE INTEGRATION WITH SEMICIRCULAR PATH AROUND POLE - return rune(0x2a13), true + return {'\u2a13', 0}, 1, true case "scsim": // SUCCEEDS OR EQUIVALENT TO - return rune(0x227f), true + return {'\u227f', 0}, 1, true case "scy": // CYRILLIC SMALL LETTER ES - return rune(0x0441), true + return {'\u0441', 0}, 1, true case "sdot": // DOT OPERATOR - return rune(0x22c5), true + return {'\u22c5', 0}, 1, true case "sdotb": // SQUARED DOT OPERATOR - return rune(0x22a1), true + return {'\u22a1', 0}, 1, true case "sdote": // EQUALS SIGN WITH DOT BELOW - return rune(0x2a66), true + return {'\u2a66', 0}, 1, true case "seArr": // SOUTH EAST DOUBLE ARROW - return rune(0x21d8), true + return {'\u21d8', 0}, 1, true case "searhk": // SOUTH EAST ARROW WITH HOOK - return rune(0x2925), true + return {'\u2925', 0}, 1, true case "searr": // SOUTH EAST ARROW - return rune(0x2198), true + return {'\u2198', 0}, 1, true case "searrow": // SOUTH EAST ARROW - return rune(0x2198), true + return {'\u2198', 0}, 1, true case "sect": // SECTION SIGN - return rune(0xa7), true + return {'§', 0}, 1, true case "semi": // SEMICOLON - return rune(0x3b), true + return {';', 0}, 1, true case "seonearr": // SOUTH EAST ARROW CROSSING NORTH EAST ARROW - return rune(0x292d), true + return {'\u292d', 0}, 1, true case "seswar": // SOUTH EAST ARROW AND SOUTH WEST ARROW - return rune(0x2929), true + return {'\u2929', 0}, 1, true case "setminus": // SET MINUS - return rune(0x2216), true + return {'\u2216', 0}, 1, true case "setmn": // SET MINUS - return rune(0x2216), true + return {'\u2216', 0}, 1, true case "sext": // SIX POINTED BLACK STAR - return rune(0x2736), true + return {'\u2736', 0}, 1, true case "sfgr": // GREEK SMALL LETTER FINAL SIGMA - return rune(0x03c2), true + return {'\u03c2', 0}, 1, true case "sfr": // MATHEMATICAL FRAKTUR SMALL S - return rune(0x01d530), true + return {'\U0001d530', 0}, 1, true case "sfrown": // FROWN - return rune(0x2322), true + return {'\u2322', 0}, 1, true case "sgr": // GREEK SMALL LETTER SIGMA - return rune(0x03c3), true + return {'\u03c3', 0}, 1, true case "sharp": // MUSIC SHARP SIGN - return rune(0x266f), true + return {'\u266f', 0}, 1, true case "shchcy": // CYRILLIC SMALL LETTER SHCHA - return rune(0x0449), true + return {'\u0449', 0}, 1, true case "shcy": // CYRILLIC SMALL LETTER SHA - return rune(0x0448), true + return {'\u0448', 0}, 1, true case "shortmid": // DIVIDES - return rune(0x2223), true + return {'\u2223', 0}, 1, true case "shortparallel": // PARALLEL TO - return rune(0x2225), true + return {'\u2225', 0}, 1, true case "shuffle": // SHUFFLE PRODUCT - return rune(0x29e2), true + return {'\u29e2', 0}, 1, true case "shy": // SOFT HYPHEN - return rune(0xad), true + return {'\u00ad', 0}, 1, true case "sigma": // GREEK SMALL LETTER SIGMA - return rune(0x03c3), true + return {'\u03c3', 0}, 1, true case "sigmaf": // GREEK SMALL LETTER FINAL SIGMA - return rune(0x03c2), true + return {'\u03c2', 0}, 1, true case "sigmav": // GREEK SMALL LETTER FINAL SIGMA - return rune(0x03c2), true + return {'\u03c2', 0}, 1, true case "sim": // TILDE OPERATOR - return rune(0x223c), true + return {'\u223c', 0}, 1, true case "simdot": // TILDE OPERATOR WITH DOT ABOVE - return rune(0x2a6a), true + return {'\u2a6a', 0}, 1, true case "sime": // ASYMPTOTICALLY EQUAL TO - return rune(0x2243), true + return {'\u2243', 0}, 1, true case "simeq": // ASYMPTOTICALLY EQUAL TO - return rune(0x2243), true + return {'\u2243', 0}, 1, true case "simg": // SIMILAR OR GREATER-THAN - return rune(0x2a9e), true + return {'\u2a9e', 0}, 1, true case "simgE": // SIMILAR ABOVE GREATER-THAN ABOVE EQUALS SIGN - return rune(0x2aa0), true + return {'\u2aa0', 0}, 1, true case "siml": // SIMILAR OR LESS-THAN - return rune(0x2a9d), true + return {'\u2a9d', 0}, 1, true case "simlE": // SIMILAR ABOVE LESS-THAN ABOVE EQUALS SIGN - return rune(0x2a9f), true + return {'\u2a9f', 0}, 1, true case "simne": // APPROXIMATELY BUT NOT ACTUALLY EQUAL TO - return rune(0x2246), true + return {'\u2246', 0}, 1, true case "simplus": // PLUS SIGN WITH TILDE ABOVE - return rune(0x2a24), true + return {'\u2a24', 0}, 1, true case "simrarr": // TILDE OPERATOR ABOVE RIGHTWARDS ARROW - return rune(0x2972), true + return {'\u2972', 0}, 1, true case "slarr": // LEFTWARDS ARROW - return rune(0x2190), true + return {'\u2190', 0}, 1, true case "slint": // INTEGRAL AVERAGE WITH SLASH - return rune(0x2a0f), true + return {'\u2a0f', 0}, 1, true case "smallsetminus": // SET MINUS - return rune(0x2216), true + return {'\u2216', 0}, 1, true case "smashp": // SMASH PRODUCT - return rune(0x2a33), true + return {'\u2a33', 0}, 1, true case "smeparsl": // EQUALS SIGN AND SLANTED PARALLEL WITH TILDE ABOVE - return rune(0x29e4), true + return {'\u29e4', 0}, 1, true case "smid": // DIVIDES - return rune(0x2223), true + return {'\u2223', 0}, 1, true case "smile": // SMILE - return rune(0x2323), true + return {'\u2323', 0}, 1, true case "smt": // SMALLER THAN - return rune(0x2aaa), true + return {'\u2aaa', 0}, 1, true case "smte": // SMALLER THAN OR EQUAL TO - return rune(0x2aac), true + return {'\u2aac', 0}, 1, true case "smtes": // SMALLER THAN OR slanted EQUAL - return rune(0x2aac), true + return {'\u2aac', '\ufe00'}, 2, true case "softcy": // CYRILLIC SMALL LETTER SOFT SIGN - return rune(0x044c), true + return {'\u044c', 0}, 1, true case "sol": // SOLIDUS - return rune(0x2f), true + return {'/', 0}, 1, true case "solb": // SQUARED RISING DIAGONAL SLASH - return rune(0x29c4), true + return {'\u29c4', 0}, 1, true case "solbar": // APL FUNCTIONAL SYMBOL SLASH BAR - return rune(0x233f), true + return {'\u233f', 0}, 1, true case "sopf": // MATHEMATICAL DOUBLE-STRUCK SMALL S - return rune(0x01d564), true + return {'\U0001d564', 0}, 1, true case "spades": // BLACK SPADE SUIT - return rune(0x2660), true + return {'\u2660', 0}, 1, true case "spadesuit": // BLACK SPADE SUIT - return rune(0x2660), true + return {'\u2660', 0}, 1, true case "spar": // PARALLEL TO - return rune(0x2225), true + return {'\u2225', 0}, 1, true case "sqcap": // SQUARE CAP - return rune(0x2293), true + return {'\u2293', 0}, 1, true case "sqcaps": // SQUARE CAP with serifs - return rune(0x2293), true + return {'\u2293', '\ufe00'}, 2, true case "sqcup": // SQUARE CUP - return rune(0x2294), true + return {'\u2294', 0}, 1, true case "sqcups": // SQUARE CUP with serifs - return rune(0x2294), true + return {'\u2294', '\ufe00'}, 2, true case "sqsub": // SQUARE IMAGE OF - return rune(0x228f), true + return {'\u228f', 0}, 1, true case "sqsube": // SQUARE IMAGE OF OR EQUAL TO - return rune(0x2291), true + return {'\u2291', 0}, 1, true case "sqsubset": // SQUARE IMAGE OF - return rune(0x228f), true + return {'\u228f', 0}, 1, true case "sqsubseteq": // SQUARE IMAGE OF OR EQUAL TO - return rune(0x2291), true + return {'\u2291', 0}, 1, true case "sqsup": // SQUARE ORIGINAL OF - return rune(0x2290), true + return {'\u2290', 0}, 1, true case "sqsupe": // SQUARE ORIGINAL OF OR EQUAL TO - return rune(0x2292), true + return {'\u2292', 0}, 1, true case "sqsupset": // SQUARE ORIGINAL OF - return rune(0x2290), true + return {'\u2290', 0}, 1, true case "sqsupseteq": // SQUARE ORIGINAL OF OR EQUAL TO - return rune(0x2292), true + return {'\u2292', 0}, 1, true case "squ": // WHITE SQUARE - return rune(0x25a1), true + return {'\u25a1', 0}, 1, true case "square": // WHITE SQUARE - return rune(0x25a1), true + return {'\u25a1', 0}, 1, true case "squarf": // BLACK SMALL SQUARE - return rune(0x25aa), true + return {'\u25aa', 0}, 1, true case "squb": // SQUARED SQUARE - return rune(0x29c8), true + return {'\u29c8', 0}, 1, true case "squerr": // ERROR-BARRED WHITE SQUARE - return rune(0x29ee), true + return {'\u29ee', 0}, 1, true case "squf": // BLACK SMALL SQUARE - return rune(0x25aa), true + return {'\u25aa', 0}, 1, true case "squferr": // ERROR-BARRED BLACK SQUARE - return rune(0x29ef), true + return {'\u29ef', 0}, 1, true case "srarr": // RIGHTWARDS ARROW - return rune(0x2192), true + return {'\u2192', 0}, 1, true case "sscr": // MATHEMATICAL SCRIPT SMALL S - return rune(0x01d4c8), true + return {'\U0001d4c8', 0}, 1, true case "ssetmn": // SET MINUS - return rune(0x2216), true + return {'\u2216', 0}, 1, true case "ssmile": // SMILE - return rune(0x2323), true + return {'\u2323', 0}, 1, true case "sstarf": // STAR OPERATOR - return rune(0x22c6), true + return {'\u22c6', 0}, 1, true case "star": // WHITE STAR - return rune(0x2606), true + return {'\u2606', 0}, 1, true case "starf": // BLACK STAR - return rune(0x2605), true + return {'\u2605', 0}, 1, true case "stigma": // GREEK LETTER STIGMA - return rune(0x03da), true + return {'\u03da', 0}, 1, true case "straightepsilon": // GREEK LUNATE EPSILON SYMBOL - return rune(0x03f5), true + return {'\u03f5', 0}, 1, true case "straightphi": // GREEK PHI SYMBOL - return rune(0x03d5), true + return {'\u03d5', 0}, 1, true case "strns": // MACRON - return rune(0xaf), true + return {'¯', 0}, 1, true case "sub": // SUBSET OF - return rune(0x2282), true + return {'\u2282', 0}, 1, true case "subE": // SUBSET OF ABOVE EQUALS SIGN - return rune(0x2ac5), true + return {'\u2ac5', 0}, 1, true case "subdot": // SUBSET WITH DOT - return rune(0x2abd), true + return {'\u2abd', 0}, 1, true case "sube": // SUBSET OF OR EQUAL TO - return rune(0x2286), true + return {'\u2286', 0}, 1, true case "subedot": // SUBSET OF OR EQUAL TO WITH DOT ABOVE - return rune(0x2ac3), true + return {'\u2ac3', 0}, 1, true case "submult": // SUBSET WITH MULTIPLICATION SIGN BELOW - return rune(0x2ac1), true + return {'\u2ac1', 0}, 1, true case "subnE": // SUBSET OF ABOVE NOT EQUAL TO - return rune(0x2acb), true + return {'\u2acb', 0}, 1, true case "subne": // SUBSET OF WITH NOT EQUAL TO - return rune(0x228a), true + return {'\u228a', 0}, 1, true case "subplus": // SUBSET WITH PLUS SIGN BELOW - return rune(0x2abf), true + return {'\u2abf', 0}, 1, true case "subrarr": // SUBSET ABOVE RIGHTWARDS ARROW - return rune(0x2979), true + return {'\u2979', 0}, 1, true case "subset": // SUBSET OF - return rune(0x2282), true + return {'\u2282', 0}, 1, true case "subseteq": // SUBSET OF OR EQUAL TO - return rune(0x2286), true + return {'\u2286', 0}, 1, true case "subseteqq": // SUBSET OF ABOVE EQUALS SIGN - return rune(0x2ac5), true + return {'\u2ac5', 0}, 1, true case "subsetneq": // SUBSET OF WITH NOT EQUAL TO - return rune(0x228a), true + return {'\u228a', 0}, 1, true case "subsetneqq": // SUBSET OF ABOVE NOT EQUAL TO - return rune(0x2acb), true + return {'\u2acb', 0}, 1, true case "subsim": // SUBSET OF ABOVE TILDE OPERATOR - return rune(0x2ac7), true + return {'\u2ac7', 0}, 1, true case "subsub": // SUBSET ABOVE SUBSET - return rune(0x2ad5), true + return {'\u2ad5', 0}, 1, true case "subsup": // SUBSET ABOVE SUPERSET - return rune(0x2ad3), true + return {'\u2ad3', 0}, 1, true case "succ": // SUCCEEDS - return rune(0x227b), true + return {'\u227b', 0}, 1, true case "succapprox": // SUCCEEDS ABOVE ALMOST EQUAL TO - return rune(0x2ab8), true + return {'\u2ab8', 0}, 1, true case "succcurlyeq": // SUCCEEDS OR EQUAL TO - return rune(0x227d), true + return {'\u227d', 0}, 1, true case "succeq": // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN - return rune(0x2ab0), true + return {'\u2ab0', 0}, 1, true case "succnapprox": // SUCCEEDS ABOVE NOT ALMOST EQUAL TO - return rune(0x2aba), true + return {'\u2aba', 0}, 1, true case "succneqq": // SUCCEEDS ABOVE NOT EQUAL TO - return rune(0x2ab6), true + return {'\u2ab6', 0}, 1, true case "succnsim": // SUCCEEDS BUT NOT EQUIVALENT TO - return rune(0x22e9), true + return {'\u22e9', 0}, 1, true case "succsim": // SUCCEEDS OR EQUIVALENT TO - return rune(0x227f), true + return {'\u227f', 0}, 1, true case "sum": // N-ARY SUMMATION - return rune(0x2211), true + return {'\u2211', 0}, 1, true case "sumint": // SUMMATION WITH INTEGRAL - return rune(0x2a0b), true + return {'\u2a0b', 0}, 1, true case "sung": // EIGHTH NOTE - return rune(0x266a), true + return {'\u266a', 0}, 1, true case "sup": // SUPERSET OF - return rune(0x2283), true + return {'\u2283', 0}, 1, true case "sup1": // SUPERSCRIPT ONE - return rune(0xb9), true + return {'¹', 0}, 1, true case "sup2": // SUPERSCRIPT TWO - return rune(0xb2), true + return {'²', 0}, 1, true case "sup3": // SUPERSCRIPT THREE - return rune(0xb3), true + return {'³', 0}, 1, true case "supE": // SUPERSET OF ABOVE EQUALS SIGN - return rune(0x2ac6), true + return {'\u2ac6', 0}, 1, true case "supdot": // SUPERSET WITH DOT - return rune(0x2abe), true + return {'\u2abe', 0}, 1, true case "supdsub": // SUPERSET BESIDE AND JOINED BY DASH WITH SUBSET - return rune(0x2ad8), true + return {'\u2ad8', 0}, 1, true case "supe": // SUPERSET OF OR EQUAL TO - return rune(0x2287), true + return {'\u2287', 0}, 1, true case "supedot": // SUPERSET OF OR EQUAL TO WITH DOT ABOVE - return rune(0x2ac4), true + return {'\u2ac4', 0}, 1, true case "suphsol": // SUPERSET PRECEDING SOLIDUS - return rune(0x27c9), true + return {'\u27c9', 0}, 1, true case "suphsub": // SUPERSET BESIDE SUBSET - return rune(0x2ad7), true + return {'\u2ad7', 0}, 1, true case "suplarr": // SUPERSET ABOVE LEFTWARDS ARROW - return rune(0x297b), true + return {'\u297b', 0}, 1, true case "supmult": // SUPERSET WITH MULTIPLICATION SIGN BELOW - return rune(0x2ac2), true + return {'\u2ac2', 0}, 1, true case "supnE": // SUPERSET OF ABOVE NOT EQUAL TO - return rune(0x2acc), true + return {'\u2acc', 0}, 1, true case "supne": // SUPERSET OF WITH NOT EQUAL TO - return rune(0x228b), true + return {'\u228b', 0}, 1, true case "supplus": // SUPERSET WITH PLUS SIGN BELOW - return rune(0x2ac0), true + return {'\u2ac0', 0}, 1, true case "supset": // SUPERSET OF - return rune(0x2283), true + return {'\u2283', 0}, 1, true case "supseteq": // SUPERSET OF OR EQUAL TO - return rune(0x2287), true + return {'\u2287', 0}, 1, true case "supseteqq": // SUPERSET OF ABOVE EQUALS SIGN - return rune(0x2ac6), true + return {'\u2ac6', 0}, 1, true case "supsetneq": // SUPERSET OF WITH NOT EQUAL TO - return rune(0x228b), true + return {'\u228b', 0}, 1, true case "supsetneqq": // SUPERSET OF ABOVE NOT EQUAL TO - return rune(0x2acc), true + return {'\u2acc', 0}, 1, true case "supsim": // SUPERSET OF ABOVE TILDE OPERATOR - return rune(0x2ac8), true + return {'\u2ac8', 0}, 1, true case "supsub": // SUPERSET ABOVE SUBSET - return rune(0x2ad4), true + return {'\u2ad4', 0}, 1, true case "supsup": // SUPERSET ABOVE SUPERSET - return rune(0x2ad6), true + return {'\u2ad6', 0}, 1, true case "swArr": // SOUTH WEST DOUBLE ARROW - return rune(0x21d9), true + return {'\u21d9', 0}, 1, true case "swarhk": // SOUTH WEST ARROW WITH HOOK - return rune(0x2926), true + return {'\u2926', 0}, 1, true case "swarr": // SOUTH WEST ARROW - return rune(0x2199), true + return {'\u2199', 0}, 1, true case "swarrow": // SOUTH WEST ARROW - return rune(0x2199), true + return {'\u2199', 0}, 1, true case "swnwar": // SOUTH WEST ARROW AND NORTH WEST ARROW - return rune(0x292a), true + return {'\u292a', 0}, 1, true case "szlig": // LATIN SMALL LETTER SHARP S - return rune(0xdf), true + return {'ß', 0}, 1, true } case 't': switch name { case "target": // POSITION INDICATOR - return rune(0x2316), true + return {'\u2316', 0}, 1, true case "tau": // GREEK SMALL LETTER TAU - return rune(0x03c4), true + return {'\u03c4', 0}, 1, true case "tbrk": // TOP SQUARE BRACKET - return rune(0x23b4), true + return {'\u23b4', 0}, 1, true case "tcaron": // LATIN SMALL LETTER T WITH CARON - return rune(0x0165), true + return {'\u0165', 0}, 1, true case "tcedil": // LATIN SMALL LETTER T WITH CEDILLA - return rune(0x0163), true + return {'\u0163', 0}, 1, true case "tcy": // CYRILLIC SMALL LETTER TE - return rune(0x0442), true + return {'\u0442', 0}, 1, true case "tdot": // COMBINING THREE DOTS ABOVE - return rune(0x20db), true + return {'\u20db', 0}, 1, true case "telrec": // TELEPHONE RECORDER - return rune(0x2315), true + return {'\u2315', 0}, 1, true case "tfr": // MATHEMATICAL FRAKTUR SMALL T - return rune(0x01d531), true + return {'\U0001d531', 0}, 1, true case "tgr": // GREEK SMALL LETTER TAU - return rune(0x03c4), true + return {'\u03c4', 0}, 1, true case "there4": // THEREFORE - return rune(0x2234), true + return {'\u2234', 0}, 1, true case "therefore": // THEREFORE - return rune(0x2234), true + return {'\u2234', 0}, 1, true case "thermod": // THERMODYNAMIC - return rune(0x29e7), true + return {'\u29e7', 0}, 1, true case "theta": // GREEK SMALL LETTER THETA - return rune(0x03b8), true + return {'\u03b8', 0}, 1, true case "thetas": // GREEK SMALL LETTER THETA - return rune(0x03b8), true + return {'\u03b8', 0}, 1, true case "thetasym": // GREEK THETA SYMBOL - return rune(0x03d1), true + return {'\u03d1', 0}, 1, true case "thetav": // GREEK THETA SYMBOL - return rune(0x03d1), true + return {'\u03d1', 0}, 1, true case "thgr": // GREEK SMALL LETTER THETA - return rune(0x03b8), true + return {'\u03b8', 0}, 1, true case "thickapprox": // ALMOST EQUAL TO - return rune(0x2248), true + return {'\u2248', 0}, 1, true case "thicksim": // TILDE OPERATOR - return rune(0x223c), true + return {'\u223c', 0}, 1, true case "thinsp": // THIN SPACE - return rune(0x2009), true + return {'\u2009', 0}, 1, true case "thkap": // ALMOST EQUAL TO - return rune(0x2248), true + return {'\u2248', 0}, 1, true case "thksim": // TILDE OPERATOR - return rune(0x223c), true + return {'\u223c', 0}, 1, true case "thorn": // LATIN SMALL LETTER THORN - return rune(0xfe), true + return {'þ', 0}, 1, true case "tilde": // SMALL TILDE - return rune(0x02dc), true + return {'\u02dc', 0}, 1, true case "timeint": // INTEGRAL WITH TIMES SIGN - return rune(0x2a18), true + return {'\u2a18', 0}, 1, true case "times": // MULTIPLICATION SIGN - return rune(0xd7), true + return {'×', 0}, 1, true case "timesb": // SQUARED TIMES - return rune(0x22a0), true + return {'\u22a0', 0}, 1, true case "timesbar": // MULTIPLICATION SIGN WITH UNDERBAR - return rune(0x2a31), true + return {'\u2a31', 0}, 1, true case "timesd": // MULTIPLICATION SIGN WITH DOT ABOVE - return rune(0x2a30), true + return {'\u2a30', 0}, 1, true case "tint": // TRIPLE INTEGRAL - return rune(0x222d), true + return {'\u222d', 0}, 1, true case "toea": // NORTH EAST ARROW AND SOUTH EAST ARROW - return rune(0x2928), true + return {'\u2928', 0}, 1, true case "top": // DOWN TACK - return rune(0x22a4), true + return {'\u22a4', 0}, 1, true case "topbot": // APL FUNCTIONAL SYMBOL I-BEAM - return rune(0x2336), true + return {'\u2336', 0}, 1, true case "topcir": // DOWN TACK WITH CIRCLE BELOW - return rune(0x2af1), true + return {'\u2af1', 0}, 1, true case "topf": // MATHEMATICAL DOUBLE-STRUCK SMALL T - return rune(0x01d565), true + return {'\U0001d565', 0}, 1, true case "topfork": // PITCHFORK WITH TEE TOP - return rune(0x2ada), true + return {'\u2ada', 0}, 1, true case "tosa": // SOUTH EAST ARROW AND SOUTH WEST ARROW - return rune(0x2929), true + return {'\u2929', 0}, 1, true case "tprime": // TRIPLE PRIME - return rune(0x2034), true + return {'\u2034', 0}, 1, true case "trade": // TRADE MARK SIGN - return rune(0x2122), true + return {'\u2122', 0}, 1, true case "triS": // S IN TRIANGLE - return rune(0x29cc), true + return {'\u29cc', 0}, 1, true case "triangle": // WHITE UP-POINTING SMALL TRIANGLE - return rune(0x25b5), true + return {'\u25b5', 0}, 1, true case "triangledown": // WHITE DOWN-POINTING SMALL TRIANGLE - return rune(0x25bf), true + return {'\u25bf', 0}, 1, true case "triangleleft": // WHITE LEFT-POINTING SMALL TRIANGLE - return rune(0x25c3), true + return {'\u25c3', 0}, 1, true case "trianglelefteq": // NORMAL SUBGROUP OF OR EQUAL TO - return rune(0x22b4), true + return {'\u22b4', 0}, 1, true case "triangleq": // DELTA EQUAL TO - return rune(0x225c), true + return {'\u225c', 0}, 1, true case "triangleright": // WHITE RIGHT-POINTING SMALL TRIANGLE - return rune(0x25b9), true + return {'\u25b9', 0}, 1, true case "trianglerighteq": // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO - return rune(0x22b5), true + return {'\u22b5', 0}, 1, true case "tribar": // TRIANGLE WITH UNDERBAR - return rune(0x29cb), true + return {'\u29cb', 0}, 1, true case "tridot": // WHITE UP-POINTING TRIANGLE WITH DOT - return rune(0x25ec), true + return {'\u25ec', 0}, 1, true case "tridoto": // TRIANGLE WITH DOT ABOVE - return rune(0x29ca), true + return {'\u29ca', 0}, 1, true case "trie": // DELTA EQUAL TO - return rune(0x225c), true + return {'\u225c', 0}, 1, true case "triminus": // MINUS SIGN IN TRIANGLE - return rune(0x2a3a), true + return {'\u2a3a', 0}, 1, true case "triplus": // PLUS SIGN IN TRIANGLE - return rune(0x2a39), true + return {'\u2a39', 0}, 1, true case "trisb": // TRIANGLE WITH SERIFS AT BOTTOM - return rune(0x29cd), true + return {'\u29cd', 0}, 1, true case "tritime": // MULTIPLICATION SIGN IN TRIANGLE - return rune(0x2a3b), true + return {'\u2a3b', 0}, 1, true case "trpezium": // WHITE TRAPEZIUM - return rune(0x23e2), true + return {'\u23e2', 0}, 1, true case "tscr": // MATHEMATICAL SCRIPT SMALL T - return rune(0x01d4c9), true + return {'\U0001d4c9', 0}, 1, true case "tscy": // CYRILLIC SMALL LETTER TSE - return rune(0x0446), true + return {'\u0446', 0}, 1, true case "tshcy": // CYRILLIC SMALL LETTER TSHE - return rune(0x045b), true + return {'\u045b', 0}, 1, true case "tstrok": // LATIN SMALL LETTER T WITH STROKE - return rune(0x0167), true + return {'\u0167', 0}, 1, true case "tverbar": // TRIPLE VERTICAL BAR DELIMITER - return rune(0x2980), true + return {'\u2980', 0}, 1, true case "twixt": // BETWEEN - return rune(0x226c), true + return {'\u226c', 0}, 1, true case "twoheadleftarrow": // LEFTWARDS TWO HEADED ARROW - return rune(0x219e), true + return {'\u219e', 0}, 1, true case "twoheadrightarrow": // RIGHTWARDS TWO HEADED ARROW - return rune(0x21a0), true + return {'\u21a0', 0}, 1, true } case 'u': switch name { case "uAarr": // UPWARDS TRIPLE ARROW - return rune(0x290a), true + return {'\u290a', 0}, 1, true case "uArr": // UPWARDS DOUBLE ARROW - return rune(0x21d1), true + return {'\u21d1', 0}, 1, true case "uHar": // UPWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT - return rune(0x2963), true + return {'\u2963', 0}, 1, true case "uacgr": // GREEK SMALL LETTER UPSILON WITH TONOS - return rune(0x03cd), true + return {'\u03cd', 0}, 1, true case "uacute": // LATIN SMALL LETTER U WITH ACUTE - return rune(0xfa), true + return {'ú', 0}, 1, true case "uarr": // UPWARDS ARROW - return rune(0x2191), true + return {'\u2191', 0}, 1, true case "uarr2": // UPWARDS PAIRED ARROWS - return rune(0x21c8), true + return {'\u21c8', 0}, 1, true case "uarrb": // UPWARDS ARROW TO BAR - return rune(0x2912), true + return {'\u2912', 0}, 1, true case "uarrln": // UPWARDS ARROW WITH HORIZONTAL STROKE - return rune(0x2909), true + return {'\u2909', 0}, 1, true case "ubrcy": // CYRILLIC SMALL LETTER SHORT U - return rune(0x045e), true + return {'\u045e', 0}, 1, true case "ubreve": // LATIN SMALL LETTER U WITH BREVE - return rune(0x016d), true + return {'\u016d', 0}, 1, true case "ucirc": // LATIN SMALL LETTER U WITH CIRCUMFLEX - return rune(0xfb), true + return {'û', 0}, 1, true case "ucy": // CYRILLIC SMALL LETTER U - return rune(0x0443), true + return {'\u0443', 0}, 1, true case "udarr": // UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW - return rune(0x21c5), true + return {'\u21c5', 0}, 1, true case "udblac": // LATIN SMALL LETTER U WITH DOUBLE ACUTE - return rune(0x0171), true + return {'\u0171', 0}, 1, true case "udhar": // UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT - return rune(0x296e), true + return {'\u296e', 0}, 1, true case "udiagr": // GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS - return rune(0x03b0), true + return {'\u03b0', 0}, 1, true case "udigr": // GREEK SMALL LETTER UPSILON WITH DIALYTIKA - return rune(0x03cb), true + return {'\u03cb', 0}, 1, true case "udrbrk": // BOTTOM SQUARE BRACKET - return rune(0x23b5), true + return {'\u23b5', 0}, 1, true case "udrcub": // BOTTOM CURLY BRACKET - return rune(0x23df), true + return {'\u23df', 0}, 1, true case "udrpar": // BOTTOM PARENTHESIS - return rune(0x23dd), true + return {'\u23dd', 0}, 1, true case "ufisht": // UP FISH TAIL - return rune(0x297e), true + return {'\u297e', 0}, 1, true case "ufr": // MATHEMATICAL FRAKTUR SMALL U - return rune(0x01d532), true + return {'\U0001d532', 0}, 1, true case "ugr": // GREEK SMALL LETTER UPSILON - return rune(0x03c5), true + return {'\u03c5', 0}, 1, true case "ugrave": // LATIN SMALL LETTER U WITH GRAVE - return rune(0xf9), true + return {'ù', 0}, 1, true case "uharl": // UPWARDS HARPOON WITH BARB LEFTWARDS - return rune(0x21bf), true + return {'\u21bf', 0}, 1, true case "uharr": // UPWARDS HARPOON WITH BARB RIGHTWARDS - return rune(0x21be), true + return {'\u21be', 0}, 1, true case "uhblk": // UPPER HALF BLOCK - return rune(0x2580), true + return {'\u2580', 0}, 1, true case "ulcorn": // TOP LEFT CORNER - return rune(0x231c), true + return {'\u231c', 0}, 1, true case "ulcorner": // TOP LEFT CORNER - return rune(0x231c), true + return {'\u231c', 0}, 1, true case "ulcrop": // TOP LEFT CROP - return rune(0x230f), true + return {'\u230f', 0}, 1, true case "uldlshar": // UP BARB LEFT DOWN BARB LEFT HARPOON - return rune(0x2951), true + return {'\u2951', 0}, 1, true case "ulharb": // UPWARDS HARPOON WITH BARB LEFT TO BAR - return rune(0x2958), true + return {'\u2958', 0}, 1, true case "ultri": // UPPER LEFT TRIANGLE - return rune(0x25f8), true + return {'\u25f8', 0}, 1, true case "umacr": // LATIN SMALL LETTER U WITH MACRON - return rune(0x016b), true + return {'\u016b', 0}, 1, true case "uml": // DIAERESIS - return rune(0xa8), true + return {'¨', 0}, 1, true case "uogon": // LATIN SMALL LETTER U WITH OGONEK - return rune(0x0173), true + return {'\u0173', 0}, 1, true case "uopf": // MATHEMATICAL DOUBLE-STRUCK SMALL U - return rune(0x01d566), true + return {'\U0001d566', 0}, 1, true case "uparrow": // UPWARDS ARROW - return rune(0x2191), true + return {'\u2191', 0}, 1, true case "updownarrow": // UP DOWN ARROW - return rune(0x2195), true + return {'\u2195', 0}, 1, true case "upharpoonleft": // UPWARDS HARPOON WITH BARB LEFTWARDS - return rune(0x21bf), true + return {'\u21bf', 0}, 1, true case "upharpoonright": // UPWARDS HARPOON WITH BARB RIGHTWARDS - return rune(0x21be), true + return {'\u21be', 0}, 1, true case "upint": // INTEGRAL WITH OVERBAR - return rune(0x2a1b), true + return {'\u2a1b', 0}, 1, true case "uplus": // MULTISET UNION - return rune(0x228e), true + return {'\u228e', 0}, 1, true case "upsi": // GREEK SMALL LETTER UPSILON - return rune(0x03c5), true + return {'\u03c5', 0}, 1, true case "upsih": // GREEK UPSILON WITH HOOK SYMBOL - return rune(0x03d2), true + return {'\u03d2', 0}, 1, true case "upsilon": // GREEK SMALL LETTER UPSILON - return rune(0x03c5), true + return {'\u03c5', 0}, 1, true case "upuparrows": // UPWARDS PAIRED ARROWS - return rune(0x21c8), true + return {'\u21c8', 0}, 1, true case "urcorn": // TOP RIGHT CORNER - return rune(0x231d), true + return {'\u231d', 0}, 1, true case "urcorner": // TOP RIGHT CORNER - return rune(0x231d), true + return {'\u231d', 0}, 1, true case "urcrop": // TOP RIGHT CROP - return rune(0x230e), true + return {'\u230e', 0}, 1, true case "urdrshar": // UP BARB RIGHT DOWN BARB RIGHT HARPOON - return rune(0x294f), true + return {'\u294f', 0}, 1, true case "urharb": // UPWARDS HARPOON WITH BARB RIGHT TO BAR - return rune(0x2954), true + return {'\u2954', 0}, 1, true case "uring": // LATIN SMALL LETTER U WITH RING ABOVE - return rune(0x016f), true + return {'\u016f', 0}, 1, true case "urtri": // UPPER RIGHT TRIANGLE - return rune(0x25f9), true + return {'\u25f9', 0}, 1, true case "urtrif": // BLACK UPPER RIGHT TRIANGLE - return rune(0x25e5), true + return {'\u25e5', 0}, 1, true case "uscr": // MATHEMATICAL SCRIPT SMALL U - return rune(0x01d4ca), true + return {'\U0001d4ca', 0}, 1, true case "utdot": // UP RIGHT DIAGONAL ELLIPSIS - return rune(0x22f0), true + return {'\u22f0', 0}, 1, true case "utilde": // LATIN SMALL LETTER U WITH TILDE - return rune(0x0169), true + return {'\u0169', 0}, 1, true case "utri": // WHITE UP-POINTING SMALL TRIANGLE - return rune(0x25b5), true + return {'\u25b5', 0}, 1, true case "utrif": // BLACK UP-POINTING SMALL TRIANGLE - return rune(0x25b4), true + return {'\u25b4', 0}, 1, true case "uuarr": // UPWARDS PAIRED ARROWS - return rune(0x21c8), true + return {'\u21c8', 0}, 1, true case "uuml": // LATIN SMALL LETTER U WITH DIAERESIS - return rune(0xfc), true + return {'ü', 0}, 1, true case "uwangle": // OBLIQUE ANGLE OPENING DOWN - return rune(0x29a7), true + return {'\u29a7', 0}, 1, true } case 'v': switch name { case "vArr": // UP DOWN DOUBLE ARROW - return rune(0x21d5), true + return {'\u21d5', 0}, 1, true case "vBar": // SHORT UP TACK WITH UNDERBAR - return rune(0x2ae8), true + return {'\u2ae8', 0}, 1, true case "vBarv": // SHORT UP TACK ABOVE SHORT DOWN TACK - return rune(0x2ae9), true + return {'\u2ae9', 0}, 1, true case "vDash": // TRUE - return rune(0x22a8), true + return {'\u22a8', 0}, 1, true case "vDdash": // VERTICAL BAR TRIPLE RIGHT TURNSTILE - return rune(0x2ae2), true + return {'\u2ae2', 0}, 1, true case "vangrt": // RIGHT ANGLE VARIANT WITH SQUARE - return rune(0x299c), true + return {'\u299c', 0}, 1, true case "varepsilon": // GREEK LUNATE EPSILON SYMBOL - return rune(0x03f5), true + return {'\u03f5', 0}, 1, true case "varkappa": // GREEK KAPPA SYMBOL - return rune(0x03f0), true + return {'\u03f0', 0}, 1, true case "varnothing": // EMPTY SET - return rune(0x2205), true + return {'\u2205', 0}, 1, true case "varphi": // GREEK PHI SYMBOL - return rune(0x03d5), true + return {'\u03d5', 0}, 1, true case "varpi": // GREEK PI SYMBOL - return rune(0x03d6), true + return {'\u03d6', 0}, 1, true case "varpropto": // PROPORTIONAL TO - return rune(0x221d), true + return {'\u221d', 0}, 1, true case "varr": // UP DOWN ARROW - return rune(0x2195), true + return {'\u2195', 0}, 1, true case "varrho": // GREEK RHO SYMBOL - return rune(0x03f1), true + return {'\u03f1', 0}, 1, true case "varsigma": // GREEK SMALL LETTER FINAL SIGMA - return rune(0x03c2), true + return {'\u03c2', 0}, 1, true case "varsubsetneq": // SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members - return rune(0x228a), true + return {'\u228a', '\ufe00'}, 2, true case "varsubsetneqq": // SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members - return rune(0x2acb), true + return {'\u2acb', '\ufe00'}, 2, true case "varsupsetneq": // SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members - return rune(0x228b), true + return {'\u228b', '\ufe00'}, 2, true case "varsupsetneqq": // SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members - return rune(0x2acc), true + return {'\u2acc', '\ufe00'}, 2, true case "vartheta": // GREEK THETA SYMBOL - return rune(0x03d1), true + return {'\u03d1', 0}, 1, true case "vartriangleleft": // NORMAL SUBGROUP OF - return rune(0x22b2), true + return {'\u22b2', 0}, 1, true case "vartriangleright": // CONTAINS AS NORMAL SUBGROUP - return rune(0x22b3), true + return {'\u22b3', 0}, 1, true case "vbrtri": // VERTICAL BAR BESIDE RIGHT TRIANGLE - return rune(0x29d0), true + return {'\u29d0', 0}, 1, true case "vcy": // CYRILLIC SMALL LETTER VE - return rune(0x0432), true + return {'\u0432', 0}, 1, true case "vdash": // RIGHT TACK - return rune(0x22a2), true + return {'\u22a2', 0}, 1, true case "vee": // LOGICAL OR - return rune(0x2228), true + return {'\u2228', 0}, 1, true case "veeBar": // LOGICAL OR WITH DOUBLE UNDERBAR - return rune(0x2a63), true + return {'\u2a63', 0}, 1, true case "veebar": // XOR - return rune(0x22bb), true + return {'\u22bb', 0}, 1, true case "veeeq": // EQUIANGULAR TO - return rune(0x225a), true + return {'\u225a', 0}, 1, true case "vellip": // VERTICAL ELLIPSIS - return rune(0x22ee), true + return {'\u22ee', 0}, 1, true case "vellip4": // DOTTED FENCE - return rune(0x2999), true + return {'\u2999', 0}, 1, true case "vellipv": // TRIPLE COLON OPERATOR - return rune(0x2af6), true + return {'\u2af6', 0}, 1, true case "verbar": // VERTICAL LINE - return rune(0x7c), true + return {'|', 0}, 1, true case "vert": // VERTICAL LINE - return rune(0x7c), true + return {'|', 0}, 1, true case "vert3": // TRIPLE VERTICAL BAR BINARY RELATION - return rune(0x2af4), true + return {'\u2af4', 0}, 1, true case "vfr": // MATHEMATICAL FRAKTUR SMALL V - return rune(0x01d533), true + return {'\U0001d533', 0}, 1, true case "vldash": // LEFT SQUARE BRACKET LOWER CORNER - return rune(0x23a3), true + return {'\u23a3', 0}, 1, true case "vltri": // NORMAL SUBGROUP OF - return rune(0x22b2), true + return {'\u22b2', 0}, 1, true case "vnsub": // SUBSET OF with vertical line - return rune(0x2282), true + return {'\u2282', '\u20d2'}, 2, true case "vnsup": // SUPERSET OF with vertical line - return rune(0x2283), true + return {'\u2283', '\u20d2'}, 2, true case "vopf": // MATHEMATICAL DOUBLE-STRUCK SMALL V - return rune(0x01d567), true + return {'\U0001d567', 0}, 1, true case "vprime": // PRIME - return rune(0x2032), true + return {'\u2032', 0}, 1, true case "vprop": // PROPORTIONAL TO - return rune(0x221d), true + return {'\u221d', 0}, 1, true case "vrtri": // CONTAINS AS NORMAL SUBGROUP - return rune(0x22b3), true + return {'\u22b3', 0}, 1, true case "vscr": // MATHEMATICAL SCRIPT SMALL V - return rune(0x01d4cb), true + return {'\U0001d4cb', 0}, 1, true case "vsubnE": // SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members - return rune(0x2acb), true + return {'\u2acb', '\ufe00'}, 2, true case "vsubne": // SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members - return rune(0x228a), true + return {'\u228a', '\ufe00'}, 2, true case "vsupnE": // SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members - return rune(0x2acc), true + return {'\u2acc', '\ufe00'}, 2, true case "vsupne": // SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members - return rune(0x228b), true + return {'\u228b', '\ufe00'}, 2, true case "vzigzag": // VERTICAL ZIGZAG LINE - return rune(0x299a), true + return {'\u299a', 0}, 1, true } case 'w': switch name { case "wcirc": // LATIN SMALL LETTER W WITH CIRCUMFLEX - return rune(0x0175), true + return {'\u0175', 0}, 1, true case "wedbar": // LOGICAL AND WITH UNDERBAR - return rune(0x2a5f), true + return {'\u2a5f', 0}, 1, true case "wedge": // LOGICAL AND - return rune(0x2227), true + return {'\u2227', 0}, 1, true case "wedgeq": // ESTIMATES - return rune(0x2259), true + return {'\u2259', 0}, 1, true case "weierp": // SCRIPT CAPITAL P - return rune(0x2118), true + return {'\u2118', 0}, 1, true case "wfr": // MATHEMATICAL FRAKTUR SMALL W - return rune(0x01d534), true + return {'\U0001d534', 0}, 1, true case "wopf": // MATHEMATICAL DOUBLE-STRUCK SMALL W - return rune(0x01d568), true + return {'\U0001d568', 0}, 1, true case "wp": // SCRIPT CAPITAL P - return rune(0x2118), true + return {'\u2118', 0}, 1, true case "wr": // WREATH PRODUCT - return rune(0x2240), true + return {'\u2240', 0}, 1, true case "wreath": // WREATH PRODUCT - return rune(0x2240), true + return {'\u2240', 0}, 1, true case "wscr": // MATHEMATICAL SCRIPT SMALL W - return rune(0x01d4cc), true + return {'\U0001d4cc', 0}, 1, true } case 'x': switch name { case "xandand": // TWO LOGICAL AND OPERATOR - return rune(0x2a07), true + return {'\u2a07', 0}, 1, true case "xbsol": // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT - return rune(0x2571), true + return {'\u2571', 0}, 1, true case "xcap": // N-ARY INTERSECTION - return rune(0x22c2), true + return {'\u22c2', 0}, 1, true case "xcirc": // LARGE CIRCLE - return rune(0x25ef), true + return {'\u25ef', 0}, 1, true case "xcup": // N-ARY UNION - return rune(0x22c3), true + return {'\u22c3', 0}, 1, true case "xcupdot": // N-ARY UNION OPERATOR WITH DOT - return rune(0x2a03), true + return {'\u2a03', 0}, 1, true case "xdtri": // WHITE DOWN-POINTING TRIANGLE - return rune(0x25bd), true + return {'\u25bd', 0}, 1, true case "xfr": // MATHEMATICAL FRAKTUR SMALL X - return rune(0x01d535), true + return {'\U0001d535', 0}, 1, true case "xgr": // GREEK SMALL LETTER XI - return rune(0x03be), true + return {'\u03be', 0}, 1, true case "xhArr": // LONG LEFT RIGHT DOUBLE ARROW - return rune(0x27fa), true + return {'\u27fa', 0}, 1, true case "xharr": // LONG LEFT RIGHT ARROW - return rune(0x27f7), true + return {'\u27f7', 0}, 1, true case "xi": // GREEK SMALL LETTER XI - return rune(0x03be), true + return {'\u03be', 0}, 1, true case "xlArr": // LONG LEFTWARDS DOUBLE ARROW - return rune(0x27f8), true + return {'\u27f8', 0}, 1, true case "xlarr": // LONG LEFTWARDS ARROW - return rune(0x27f5), true + return {'\u27f5', 0}, 1, true case "xmap": // LONG RIGHTWARDS ARROW FROM BAR - return rune(0x27fc), true + return {'\u27fc', 0}, 1, true case "xnis": // CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE - return rune(0x22fb), true + return {'\u22fb', 0}, 1, true case "xodot": // N-ARY CIRCLED DOT OPERATOR - return rune(0x2a00), true + return {'\u2a00', 0}, 1, true case "xopf": // MATHEMATICAL DOUBLE-STRUCK SMALL X - return rune(0x01d569), true + return {'\U0001d569', 0}, 1, true case "xoplus": // N-ARY CIRCLED PLUS OPERATOR - return rune(0x2a01), true + return {'\u2a01', 0}, 1, true case "xoror": // TWO LOGICAL OR OPERATOR - return rune(0x2a08), true + return {'\u2a08', 0}, 1, true case "xotime": // N-ARY CIRCLED TIMES OPERATOR - return rune(0x2a02), true + return {'\u2a02', 0}, 1, true case "xrArr": // LONG RIGHTWARDS DOUBLE ARROW - return rune(0x27f9), true + return {'\u27f9', 0}, 1, true case "xrarr": // LONG RIGHTWARDS ARROW - return rune(0x27f6), true + return {'\u27f6', 0}, 1, true case "xscr": // MATHEMATICAL SCRIPT SMALL X - return rune(0x01d4cd), true + return {'\U0001d4cd', 0}, 1, true case "xsol": // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT - return rune(0x2572), true + return {'\u2572', 0}, 1, true case "xsqcap": // N-ARY SQUARE INTERSECTION OPERATOR - return rune(0x2a05), true + return {'\u2a05', 0}, 1, true case "xsqcup": // N-ARY SQUARE UNION OPERATOR - return rune(0x2a06), true + return {'\u2a06', 0}, 1, true case "xsqu": // WHITE MEDIUM SQUARE - return rune(0x25fb), true + return {'\u25fb', 0}, 1, true case "xsquf": // BLACK MEDIUM SQUARE - return rune(0x25fc), true + return {'\u25fc', 0}, 1, true case "xtimes": // N-ARY TIMES OPERATOR - return rune(0x2a09), true + return {'\u2a09', 0}, 1, true case "xuplus": // N-ARY UNION OPERATOR WITH PLUS - return rune(0x2a04), true + return {'\u2a04', 0}, 1, true case "xutri": // WHITE UP-POINTING TRIANGLE - return rune(0x25b3), true + return {'\u25b3', 0}, 1, true case "xvee": // N-ARY LOGICAL OR - return rune(0x22c1), true + return {'\u22c1', 0}, 1, true case "xwedge": // N-ARY LOGICAL AND - return rune(0x22c0), true + return {'\u22c0', 0}, 1, true } case 'y': switch name { case "yacute": // LATIN SMALL LETTER Y WITH ACUTE - return rune(0xfd), true + return {'ý', 0}, 1, true case "yacy": // CYRILLIC SMALL LETTER YA - return rune(0x044f), true + return {'\u044f', 0}, 1, true case "ycirc": // LATIN SMALL LETTER Y WITH CIRCUMFLEX - return rune(0x0177), true + return {'\u0177', 0}, 1, true case "ycy": // CYRILLIC SMALL LETTER YERU - return rune(0x044b), true + return {'\u044b', 0}, 1, true case "yen": // YEN SIGN - return rune(0xa5), true + return {'¥', 0}, 1, true case "yfr": // MATHEMATICAL FRAKTUR SMALL Y - return rune(0x01d536), true + return {'\U0001d536', 0}, 1, true case "yicy": // CYRILLIC SMALL LETTER YI - return rune(0x0457), true + return {'\u0457', 0}, 1, true case "yopf": // MATHEMATICAL DOUBLE-STRUCK SMALL Y - return rune(0x01d56a), true + return {'\U0001d56a', 0}, 1, true case "yscr": // MATHEMATICAL SCRIPT SMALL Y - return rune(0x01d4ce), true + return {'\U0001d4ce', 0}, 1, true case "yucy": // CYRILLIC SMALL LETTER YU - return rune(0x044e), true + return {'\u044e', 0}, 1, true case "yuml": // LATIN SMALL LETTER Y WITH DIAERESIS - return rune(0xff), true + return {'ÿ', 0}, 1, true } case 'z': switch name { case "zacute": // LATIN SMALL LETTER Z WITH ACUTE - return rune(0x017a), true + return {'\u017a', 0}, 1, true case "zcaron": // LATIN SMALL LETTER Z WITH CARON - return rune(0x017e), true + return {'\u017e', 0}, 1, true case "zcy": // CYRILLIC SMALL LETTER ZE - return rune(0x0437), true + return {'\u0437', 0}, 1, true case "zdot": // LATIN SMALL LETTER Z WITH DOT ABOVE - return rune(0x017c), true + return {'\u017c', 0}, 1, true case "zeetrf": // BLACK-LETTER CAPITAL Z - return rune(0x2128), true + return {'\u2128', 0}, 1, true case "zeta": // GREEK SMALL LETTER ZETA - return rune(0x03b6), true + return {'\u03b6', 0}, 1, true case "zfr": // MATHEMATICAL FRAKTUR SMALL Z - return rune(0x01d537), true + return {'\U0001d537', 0}, 1, true case "zgr": // GREEK SMALL LETTER ZETA - return rune(0x03b6), true + return {'\u03b6', 0}, 1, true case "zhcy": // CYRILLIC SMALL LETTER ZHE - return rune(0x0436), true + return {'\u0436', 0}, 1, true case "zigrarr": // RIGHTWARDS SQUIGGLE ARROW - return rune(0x21dd), true + return {'\u21dd', 0}, 1, true case "zopf": // MATHEMATICAL DOUBLE-STRUCK SMALL Z - return rune(0x01d56b), true + return {'\U0001d56b', 0}, 1, true case "zscr": // MATHEMATICAL SCRIPT SMALL Z - return rune(0x01d4cf), true + return {'\U0001d4cf', 0}, 1, true case "zwj": // ZERO WIDTH JOINER - return rune(0x200d), true + return {'\u200d', 0}, 1, true case "zwnj": // ZERO WIDTH NON-JOINER - return rune(0x200c), true + return {'\u200c', 0}, 1, true } } - return -1, false + return } /* diff --git a/core/flags/constants.odin b/core/flags/constants.odin index dc2663e2a..154ed3cec 100644 --- a/core/flags/constants.odin +++ b/core/flags/constants.odin @@ -11,8 +11,7 @@ NO_CORE_NAMED_TYPES :: #config(ODIN_CORE_FLAGS_NO_CORE_NAMED_TYPES, false) IMPORTING_TIME :: #config(ODIN_CORE_FLAGS_USE_TIME, time.IS_SUPPORTED) // Override support for parsing `net` types. -// TODO: Update this when the BSDs are supported. -IMPORTING_NET :: #config(ODIN_CORE_FLAGS_USE_NET, ODIN_OS == .Windows || ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD) +IMPORTING_NET :: #config(ODIN_CORE_FLAGS_USE_NET, ODIN_OS == .Windows || ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD) TAG_ARGS :: "args" SUBTAG_NAME :: "name" diff --git a/core/flags/errors.odin b/core/flags/errors.odin index 3d34a95d3..e9b2e18c8 100644 --- a/core/flags/errors.odin +++ b/core/flags/errors.odin @@ -1,5 +1,7 @@ package flags +import "base:runtime" +import "core:net" import "core:os" Parse_Error_Reason :: enum { @@ -24,6 +26,12 @@ Parse_Error :: struct { message: string, } +Unified_Parse_Error_Reason :: union #shared_nil { + Parse_Error_Reason, + runtime.Allocator_Error, + net.Parse_Endpoint_Error, +} + // Raised during parsing. // Provides more granular information than what just a string could hold. Open_File_Error :: struct { diff --git a/core/flags/errors_bsd.odin b/core/flags/errors_bsd.odin deleted file mode 100644 index 4d98d2ee4..000000000 --- a/core/flags/errors_bsd.odin +++ /dev/null @@ -1,9 +0,0 @@ -#+build netbsd, openbsd -package flags - -import "base:runtime" - -Unified_Parse_Error_Reason :: union #shared_nil { - Parse_Error_Reason, - runtime.Allocator_Error, -} diff --git a/core/flags/errors_nonbsd.odin b/core/flags/errors_nonbsd.odin deleted file mode 100644 index 28912b57f..000000000 --- a/core/flags/errors_nonbsd.odin +++ /dev/null @@ -1,12 +0,0 @@ -#+build !netbsd -#+build !openbsd -package flags - -import "base:runtime" -import "core:net" - -Unified_Parse_Error_Reason :: union #shared_nil { - Parse_Error_Reason, - runtime.Allocator_Error, - net.Parse_Endpoint_Error, -} diff --git a/core/flags/internal_rtti.odin b/core/flags/internal_rtti.odin index a1b050597..b3880afa0 100644 --- a/core/flags/internal_rtti.odin +++ b/core/flags/internal_rtti.odin @@ -5,6 +5,7 @@ import "base:intrinsics" import "base:runtime" import "core:fmt" import "core:mem" +import "core:net" import "core:os" import "core:reflect" import "core:strconv" @@ -310,7 +311,18 @@ parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type: } when IMPORTING_NET { - if try_net_parse_workaround(data_type, str, ptr, out_error) { + if data_type == net.Host_Or_Endpoint { + addr, net_error := net.parse_hostname_or_endpoint(str) + if net_error != nil { + // We pass along `net.Error` here. + out_error^ = Parse_Error { + net_error, + "Invalid Host/Endpoint.", + } + return + } + + (cast(^net.Host_Or_Endpoint)ptr)^ = addr return } } diff --git a/core/flags/internal_rtti_nonbsd.odin b/core/flags/internal_rtti_nonbsd.odin deleted file mode 100644 index e1286186b..000000000 --- a/core/flags/internal_rtti_nonbsd.odin +++ /dev/null @@ -1,32 +0,0 @@ -#+private -#+build !netbsd -#+build !openbsd -package flags - -import "core:net" - -// This proc exists purely as a workaround for import restrictions. -// Returns true if caller should return early. -try_net_parse_workaround :: #force_inline proc ( - data_type: typeid, - str: string, - ptr: rawptr, - out_error: ^Error, -) -> bool { - if data_type == net.Host_Or_Endpoint { - addr, net_error := net.parse_hostname_or_endpoint(str) - if net_error != nil { - // We pass along `net.Error` here. - out_error^ = Parse_Error { - net_error, - "Invalid Host/Endpoint.", - } - return true - } - - (cast(^net.Host_Or_Endpoint)ptr)^ = addr - return true - } - - return false -} diff --git a/core/image/general.odin b/core/image/general.odin index 1662cf14e..779f5d0b1 100644 --- a/core/image/general.odin +++ b/core/image/general.odin @@ -86,7 +86,7 @@ which_bytes :: proc(data: []byte) -> Which_File_Type { return v } get16le :: #force_inline proc(s: ^string) -> u16 { - v := u16(s[0]) | u16(s[1])<<16 + v := u16(s[0]) | u16(s[1])<<8 s^ = s[2:] return v } diff --git a/core/math/rand/rand_pcg.odin b/core/math/rand/rand_pcg.odin index 009e139be..79c18acbb 100644 --- a/core/math/rand/rand_pcg.odin +++ b/core/math/rand/rand_pcg.odin @@ -55,16 +55,20 @@ pcg_random_generator_proc :: proc(data: rawptr, mode: runtime.Random_Generator_M intrinsics.unaligned_store((^u64)(raw_data(p)), read_u64(r)) case: // All other cases. - pos := i8(0) - val := u64(0) - for &v in p { - if pos == 0 { - val = read_u64(r) - pos = 8 + n := len(p) / size_of(u64) + buff := ([^]u64)(raw_data(p))[:n] + for &e in buff { + intrinsics.unaligned_store(&e, read_u64(r)) + } + // Handle remaining bytes + rem := len(p) % size_of(u64) + if rem > 0 { + val := read_u64(r) + tail := p[len(p) - rem:] + for &b in tail { + b = byte(val) + val >>= 8 } - v = byte(val) - val >>= 8 - pos -= 1 } } diff --git a/core/math/rand/rand_xoshiro256.odin b/core/math/rand/rand_xoshiro256.odin index 54dd02130..7326ba8d5 100644 --- a/core/math/rand/rand_xoshiro256.odin +++ b/core/math/rand/rand_xoshiro256.odin @@ -74,16 +74,20 @@ xoshiro256_random_generator_proc :: proc(data: rawptr, mode: runtime.Random_Gene intrinsics.unaligned_store((^u64)(raw_data(p)), read_u64(r)) case: // All other cases. - pos := i8(0) - val := u64(0) - for &v in p { - if pos == 0 { - val = read_u64(r) - pos = 8 + n := len(p) / size_of(u64) + buff := ([^]u64)(raw_data(p))[:n] + for &e in buff { + intrinsics.unaligned_store(&e, read_u64(r)) + } + // Handle remaining bytes + rem := len(p) % size_of(u64) + if rem > 0 { + val := read_u64(r) + tail := p[len(p) - rem:] + for &b in tail { + b = byte(val) + val >>= 8 } - v = byte(val) - val >>= 8 - pos -= 1 } } diff --git a/core/nbio/doc.odin b/core/nbio/doc.odin new file mode 100644 index 000000000..c43196923 --- /dev/null +++ b/core/nbio/doc.odin @@ -0,0 +1,195 @@ +/* +package nbio implements a non-blocking I/O and event loop abstraction layer +over several platform-specific asynchronous I/O APIs. + +More examples can be found in Odin's examples repository +at [[ examples/nbio ; https://github.com/odin-lang/examples/nbio ]]. + +**Event Loop**: + +Each thread may have at most one event loop associated with it. +This is enforced by the package, as running multiple event loops on a single +thread does not make sense. + +Event loops are reference counted and managed by the package. + +`acquire_thread_event_loop` and `release_thread_event_loop` can be used +to acquire and release a reference. Acquiring must be done before any operation +is done. + +The event loop progresses in ticks. A tick checks if any work is to be done, +and based on the given timeout may block waiting for work. + +Ticks are typically done using the `tick`, `run`, and `run_until` procedures. + +Example: + package main + + import "core:nbio" + import "core:time" + import "core:fmt" + + main :: proc() { + err := nbio.acquire_thread_event_loop() + assert(err == nil) + defer nbio.release_thread_event_loop() + + nbio.timeout(time.Second, proc(_: ^nbio.Operation) { + fmt.println("Hellope after 1 second!") + }) + + err = nbio.run() + assert(err == nil) + } + + +**Time and timeouts**: + +Timeouts are intentionally *slightly inaccurate* by design. + +A timeout is not checked continuously, instead, it is evaluated only when +a tick occurs. This means if a tick took a long time, your timeout may be ready +for a bit of time already before the callback is called. + +The function `now` returns the current time as perceived by the event +loop. This value is cached at least once per tick so it is fast to retrieve. + +Most operations also take an optional timeout when executed. +If the timeout completes before the operation, the operation is cancelled and +called back with a `.Timeout` error. + + +**Threading**: + +The package has a concept of I/O threads (threads that are ticking) and worker +threads (any other thread). + +An I/O thread is mostly self contained, operations are executed on it, and +callbacks run on it. + +If you try to execute an operation on a thread that has no running event loop +a panic will be executed. Instead a worker thread can execute operations onto +a running event loop by taking it's reference and executing operations with +that reference. + +In this case: +- The operation is enqueued from the worker thread +- The I/O thread is optionally woken up from blocking for work with `wake_up` +- The next tick, the operation is executed by the I/O thread +- The callback is invoked on the I/O thread + +Example: + package main + + import "core:nbio" + import "core:net" + import "core:thread" + import "core:time" + + Connection :: struct { + loop: ^nbio.Event_Loop, + socket: net.TCP_Socket, + } + + main :: proc() { + workers: thread.Pool + thread.pool_init(&workers, context.allocator, 2) + thread.pool_start(&workers) + + err := nbio.acquire_thread_event_loop() + defer nbio.release_thread_event_loop() + assert(err == nil) + + server, listen_err := nbio.listen_tcp({nbio.IP4_Any, 1234}) + assert(listen_err == nil) + nbio.accept_poly(server, &workers, on_accept) + + err = nbio.run() + assert(err == nil) + + on_accept :: proc(op: ^nbio.Operation, workers: ^thread.Pool) { + assert(op.accept.err == nil) + + nbio.accept_poly(op.accept.socket, workers, on_accept) + + thread.pool_add_task(workers, context.allocator, do_work, new_clone(Connection{ + loop = op.l, + socket = op.accept.client, + })) + } + + do_work :: proc(t: thread.Task) { + connection := (^Connection)(t.data) + + // Imagine CPU intensive work that's been ofloaded to a worker thread. + time.sleep(time.Second * 1) + + nbio.send_poly(connection.socket, {transmute([]byte)string("Hellope!\n")}, connection, on_sent, l=connection.loop) + } + + on_sent :: proc(op: ^nbio.Operation, connection: ^Connection) { + assert(op.send.err == nil) + // Client got our message, clean up. + nbio.close(connection.socket) + free(connection) + } + } + + +**Handle and socket association**: + +Most platforms require handles (files, sockets, etc.) to be explicitly +associated with an event loop or configured for non-blocking/asynchronous +operation. + +On some platforms (notably Windows), this requires a specific flag at open +time (`.Non_Blocking` for `core:os`) and association may fail if the handle was not created +correctly. + +For this reason, prefer `open` and `create_socket` from this package instead. + +`associate_handle`, `associate_file`, and `associate_socket` can be used for externally opened +files/sockets. + + +**Offsets and positional I/O**: + +Operations do not implicitly use or modify a handle’s internal file +offset. + +Instead, operations such as `read` and `write` are *positional* and require +an explicit offset. + +This avoids ambiguity and subtle bugs when multiple asynchronous operations +are issued concurrently against the same handle. + + +**Contexts and callbacks**: + +The `context` inside a callback is *not* the context that submitted the +operation. + +Instead, the callback receives the context that was active when the event +loop function (`tick`, `run`, etc.) was called. + +This is because otherwise the context would have to be copied and held onto for each operation. + +If the submitting context is required inside the callback, it must be copied +into the operation’s user data explicitly. + +Example: + nbio.timeout_poly(time.Second, new_clone(context), proc(_: ^Operation, ctx: ^runtime.Context) { + context = ctx^ + free(ctx) + }) + + +**Callback scheduling guarantees**: + +Callbacks are guaranteed to be invoked in a later tick, never synchronously. +This means that the operation returned from a procedure is at least valid till the end of the +current tick, because an operation is freed after it's callback is called. +Thus you can set user data after an execution is queued, or call `remove`, removing subtle "race" +conditions and simplifying control flow. +*/ +package nbio diff --git a/core/nbio/errors.odin b/core/nbio/errors.odin new file mode 100644 index 000000000..f3bd381d5 --- /dev/null +++ b/core/nbio/errors.odin @@ -0,0 +1,84 @@ +package nbio + +import "base:intrinsics" + +import "core:reflect" + +Error :: intrinsics.type_merge( + Network_Error, + union #shared_nil { + General_Error, + FS_Error, + }, +) +#assert(size_of(Error) == 8) + +// Errors regarding general usage of the event loop. +General_Error :: enum i32 { + None, + + Allocation_Failed = i32(PLATFORM_ERR_ALLOCATION_FAILED), + Unsupported = i32(PLATFORM_ERR_UNSUPPORTED), +} + +// Errors gotten from file system operations. +FS_Error :: enum i32 { + None, + Unsupported = i32(PLATFORM_ERR_UNSUPPORTED), + Allocation_Failed = i32(PLATFORM_ERR_ALLOCATION_FAILED), + Timeout = i32(PLATFORM_ERR_TIMEOUT), + Invalid_Argument = i32(PLATFORM_ERR_INVALID_ARGUMENT), + Permission_Denied = i32(PLATFORM_ERR_PERMISSION_DENIED), + EOF = i32(PLATFORM_ERR_EOF), + Exists = i32(PLATFORM_ERR_EXISTS), + Not_Found = i32(PLATFORM_ERR_NOT_FOUND), +} + +Platform_Error :: _Platform_Error + +error_string :: proc(err: Error) -> string { + err := err + variant := any{ + id = reflect.union_variant_typeid(err), + data = &err, + } + str := reflect.enum_string(variant) + + if str == "" { + #partial switch uerr in err { + case FS_Error: + str, _ = reflect.enum_name_from_value(Platform_Error(uerr)) + case General_Error: + str, _ = reflect.enum_name_from_value(Platform_Error(uerr)) + } + } + if str == "" { + str = "Unknown" + } + + return str +} + +error_string_recv :: proc(recv_err: Recv_Error) -> string { + switch err in recv_err { + case TCP_Recv_Error: return error_string(err) + case UDP_Recv_Error: return error_string(err) + case: return "Unknown" + } +} + +error_string_send :: proc(send_err: Send_Error) -> string { + switch err in send_err { + case TCP_Send_Error: return error_string(err) + case UDP_Send_Error: return error_string(err) + case: return "Unknown" + } +} + +error_string_sendfile :: proc(send_err: Send_File_Error) -> string { + switch err in send_err { + case TCP_Send_Error: return error_string(err) + case FS_Error: return error_string(err) + case: return "Unknown" + } +} diff --git a/core/nbio/errors_linux.odin b/core/nbio/errors_linux.odin new file mode 100644 index 000000000..5c3472500 --- /dev/null +++ b/core/nbio/errors_linux.odin @@ -0,0 +1,16 @@ +#+private +package nbio + +import "core:sys/linux" + +PLATFORM_ERR_UNSUPPORTED :: linux.Errno.ENOSYS +PLATFORM_ERR_ALLOCATION_FAILED :: linux.Errno.ENOMEM +PLATFORM_ERR_TIMEOUT :: linux.Errno.ECANCELED +PLATFORM_ERR_INVALID_ARGUMENT :: linux.Errno.EINVAL +PLATFORM_ERR_OVERFLOW :: linux.Errno.E2BIG +PLATFORM_ERR_NOT_FOUND :: linux.Errno.ENOENT +PLATFORM_ERR_EXISTS :: linux.Errno.EEXIST +PLATFORM_ERR_PERMISSION_DENIED :: linux.Errno.EPERM +PLATFORM_ERR_EOF :: -100 // There is no EOF errno, we use negative for our own error codes. + +_Platform_Error :: linux.Errno diff --git a/core/nbio/errors_others.odin b/core/nbio/errors_others.odin new file mode 100644 index 000000000..f27c91178 --- /dev/null +++ b/core/nbio/errors_others.odin @@ -0,0 +1,20 @@ +#+build !darwin +#+build !freebsd +#+build !openbsd +#+build !netbsd +#+build !linux +#+build !windows +#+private +package nbio + +PLATFORM_ERR_UNSUPPORTED :: 1 +PLATFORM_ERR_ALLOCATION_FAILED :: 2 +PLATFORM_ERR_TIMEOUT :: 3 +PLATFORM_ERR_INVALID_ARGUMENT :: 4 +PLATFORM_ERR_OVERFLOW :: 5 +PLATFORM_ERR_NOT_FOUND :: 6 +PLATFORM_ERR_EXISTS :: 7 +PLATFORM_ERR_PERMISSION_DENIED :: 8 +PLATFORM_ERR_EOF :: 9 + +_Platform_Error :: enum i32 {} diff --git a/core/nbio/errors_posix.odin b/core/nbio/errors_posix.odin new file mode 100644 index 000000000..3dd8f781d --- /dev/null +++ b/core/nbio/errors_posix.odin @@ -0,0 +1,17 @@ +#+build darwin, freebsd, netbsd, openbsd +#+private +package nbio + +import "core:sys/posix" + +PLATFORM_ERR_UNSUPPORTED :: posix.Errno.ENOSYS +PLATFORM_ERR_ALLOCATION_FAILED :: posix.Errno.ENOMEM +PLATFORM_ERR_TIMEOUT :: posix.Errno.ECANCELED +PLATFORM_ERR_INVALID_ARGUMENT :: posix.Errno.EINVAL +PLATFORM_ERR_OVERFLOW :: posix.Errno.E2BIG +PLATFORM_ERR_NOT_FOUND :: posix.Errno.ENOENT +PLATFORM_ERR_EXISTS :: posix.Errno.EEXIST +PLATFORM_ERR_PERMISSION_DENIED :: posix.Errno.EPERM +PLATFORM_ERR_EOF :: -100 // There is no EOF errno, we use negative for our own error codes. + +_Platform_Error :: posix.Errno diff --git a/core/nbio/errors_windows.odin b/core/nbio/errors_windows.odin new file mode 100644 index 000000000..d9b3b7e4d --- /dev/null +++ b/core/nbio/errors_windows.odin @@ -0,0 +1,17 @@ +#+private +package nbio + +import win "core:sys/windows" + +PLATFORM_ERR_UNSUPPORTED :: win.System_Error.NOT_SUPPORTED + +PLATFORM_ERR_ALLOCATION_FAILED :: win.System_Error.OUTOFMEMORY +PLATFORM_ERR_TIMEOUT :: win.System_Error.WAIT_TIMEOUT +PLATFORM_ERR_INVALID_ARGUMENT :: win.System_Error.BAD_ARGUMENTS +PLATFORM_ERR_OVERFLOW :: win.System_Error.BUFFER_OVERFLOW +PLATFORM_ERR_NOT_FOUND :: win.System_Error.FILE_NOT_FOUND +PLATFORM_ERR_EXISTS :: win.System_Error.FILE_EXISTS +PLATFORM_ERR_PERMISSION_DENIED :: win.System_Error.ACCESS_DENIED +PLATFORM_ERR_EOF :: win.System_Error.HANDLE_EOF + +_Platform_Error :: win.System_Error diff --git a/core/nbio/impl.odin b/core/nbio/impl.odin new file mode 100644 index 000000000..476b8ab43 --- /dev/null +++ b/core/nbio/impl.odin @@ -0,0 +1,259 @@ +#+private +package nbio + +import "base:runtime" +import "base:intrinsics" + +import "core:container/pool" +import "core:net" +import "core:strings" +import "core:time" +import "core:reflect" + +@(init, private) +init_thread_local_cleaner :: proc "contextless" () { + runtime.add_thread_local_cleaner(proc() { + l := &_tls_event_loop + if l.refs > 0 { + l.refs = 1 + _release_thread_event_loop() + } + }) +} + +@(thread_local) +_tls_event_loop: Event_Loop + +_acquire_thread_event_loop :: proc() -> General_Error { + l := &_tls_event_loop + if l.err == nil && l.refs == 0 { + when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 && ODIN_OS != .Orca { + allocator := runtime.default_wasm_allocator() + } else { + allocator := runtime.heap_allocator() + } + + l.allocator = allocator + + if alloc_err := mpsc_init(&l.queue, 128, l.allocator); alloc_err != nil { + l.err = .Allocation_Failed + return l.err + } + defer if l.err != nil { mpsc_destroy(&l.queue, l.allocator) } + + if pool_err := pool.init(&l.operation_pool, "_pool_link"); pool_err != nil { + l.err = .Allocation_Failed + return l.err + } + defer if l.err != nil { pool.destroy(&l.operation_pool) } + + l.err = _init(l, allocator) + l.now = time.now() + } + + if l.err != nil { + return l.err + } + + l.refs += 1 + return nil +} + +_release_thread_event_loop :: proc() { + l := &_tls_event_loop + if l.err != nil { + assert(l.refs == 0) + return + } + + if l.refs > 0 { + l.refs -= 1 + if l.refs == 0 { + mpsc_destroy(&l.queue, l.allocator) + pool.destroy(&l.operation_pool) + _destroy(l) + l^ = {} + } + } +} + +_current_thread_event_loop :: #force_inline proc(loc := #caller_location) -> (^Event_Loop) { + l := &_tls_event_loop + + if intrinsics.expect(l.refs == 0, false) { + return nil + } + + return l +} + +_tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> (err: General_Error) { + // Receive operations queued from other threads first. + for { + op := (^Operation)(mpsc_dequeue(&l.queue)) + if op == nil { break } + _exec(op) + } + + return __tick(l, timeout) +} + +_listen_tcp :: proc( + l: ^Event_Loop, + endpoint: Endpoint, + backlog := 1000, + loc := #caller_location, +) -> ( + socket: TCP_Socket, + err: Network_Error, +) { + family := family_from_endpoint(endpoint) + socket = create_tcp_socket(family, l, loc) or_return + defer if err != nil { close(socket, l=l) } + + net.set_option(socket, .Reuse_Address, true) + + bind(socket, endpoint) or_return + + _listen(socket, backlog) or_return + return +} + +_read_entire_file :: proc(l: ^Event_Loop, path: string, user_data: rawptr, cb: Read_Entire_File_Callback, allocator := context.allocator, dir := CWD) { + open_poly3(path, user_data, cb, allocator, on_open, dir=dir, l=l) + + on_open :: proc(op: ^Operation, user_data: rawptr, cb: Read_Entire_File_Callback, allocator: runtime.Allocator) { + if op.open.err != nil { + cb(user_data, nil, {.Open, op.open.err}) + return + } + + stat_poly3(op.open.handle, user_data, cb, allocator, on_stat) + } + + on_stat :: proc(op: ^Operation, user_data: rawptr, cb: Read_Entire_File_Callback, allocator: runtime.Allocator) { + if op.stat.err != nil { + close(op.stat.handle) + cb(user_data, nil, {.Stat, op.stat.err}) + return + } + + if op.stat.type != .Regular { + close(op.stat.handle) + cb(user_data, nil, {.Stat, .Unsupported}) + return + } + + buf, err := make([]byte, op.stat.size, allocator) + if err != nil { + close(op.stat.handle) + cb(user_data, nil, {.Read, .Allocation_Failed}) + return + } + + read_poly3(op.stat.handle, 0, buf, user_data, cb, allocator, on_read, all=true) + } + + on_read :: proc(op: ^Operation, user_data: rawptr, cb: Read_Entire_File_Callback, allocator: runtime.Allocator) { + close(op.read.handle) + + if op.read.err != nil { + delete(op.read.buf, allocator) + cb(user_data, nil, {.Read, op.read.err}) + return + } + + assert(op.read.read == len(op.read.buf)) + cb(user_data, op.read.buf, {}) + } +} + +NBIO_DEBUG :: #config(NBIO_DEBUG, false) + +Debuggable :: union { + Operation_Type, + string, + int, + time.Time, + time.Duration, +} + +@(disabled=!NBIO_DEBUG) +debug :: proc(contents: ..Debuggable, location := #caller_location) { + if context.logger.procedure == nil || .Debug < context.logger.lowest_level { + return + } + + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + b: strings.Builder + b.buf.allocator = context.temp_allocator + + strings.write_string(&b, "[nbio] ") + + for content, i in contents { + switch val in content { + case Operation_Type: + name, _ := reflect.enum_name_from_value(val) + strings.write_string(&b, name) + case string: + strings.write_string(&b, val) + case int: + strings.write_int(&b, val) + case time.Duration: + ms := time.duration_milliseconds(val) + strings.write_f64(&b, ms, 'f') + strings.write_string(&b, "ms") + + case time.Time: + buf: [time.MIN_HMS_LEN+1]byte + h, m, s, ns := time.precise_clock_from_time(val) + buf[8] = '.' + buf[7] = '0' + u8(s % 10); s /= 10 + buf[6] = '0' + u8(s) + buf[5] = ':' + buf[4] = '0' + u8(m % 10); m /= 10 + buf[3] = '0' + u8(m) + buf[2] = ':' + buf[1] = '0' + u8(h % 10); h /= 10 + buf[0] = '0' + u8(h) + + strings.write_string(&b, string(buf[:])) + strings.write_int(&b, ns) + } + + if i < len(contents)-1 { + strings.write_byte(&b, ' ') + } + } + + context.logger.procedure(context.logger.data, .Debug, strings.to_string(b), context.logger.options, location) +} + +warn :: proc(text: string, location := #caller_location) { + if context.logger.procedure == nil || .Warning < context.logger.lowest_level { + return + } + + context.logger.procedure(context.logger.data, .Warning, text, context.logger.options, location) +} + +@(require_results) +constraint_bufs_to_max_rw :: proc(bufs: [][]byte) -> (constrained: [][]byte, total: int) { + for buf in bufs { + total += len(buf) + } + + constrained = bufs + for n := total; n > MAX_RW; { + last := &constrained[len(constrained)-1] + take := min(len(last), n-MAX_RW) + last^ = last[:take] + if len(last) == 0 { + constrained = constrained[:len(constrained)-1] + } + n -= take + } + + return +} diff --git a/core/nbio/impl_linux.odin b/core/nbio/impl_linux.odin new file mode 100644 index 000000000..47d47d77c --- /dev/null +++ b/core/nbio/impl_linux.odin @@ -0,0 +1,1423 @@ +#+private file +package nbio + +import "base:intrinsics" + +import "core:container/pool" +import "core:container/queue" +import "core:mem" +import "core:net" +import "core:slice" +import "core:strings" +import "core:sys/linux" +import "core:sys/linux/uring" +import "core:time" + +@(private="package") +_FULLY_SUPPORTED :: true + +@(private="package") +_Event_Loop :: struct { + ring: uring.Ring, + // Ready to be submitted to kernel, if kernel is full. + unqueued: queue.Queue(^Operation), + // Ready to run callbacks, mainly next tick, some other ops that error outside the kernel. + completed: queue.Queue(^Operation), + wake: ^Operation, +} + +@(private="package") +_Handle :: linux.Fd + +@(private="package") +_CWD :: linux.AT_FDCWD + +@(private="package") +MAX_RW :: mem.Gigabyte + +@(private="package") +_Operation :: struct { + removal: ^Operation, + sqe: ^linux.IO_Uring_SQE, + expires: linux.Time_Spec, +} + +@(private="package") +_Accept :: struct { + sockaddr: linux.Sock_Addr_Any, + sockaddr_len: i32, +} + +@(private="package") +_Close :: struct {} + +@(private="package") +_Dial :: struct { + sockaddr: linux.Sock_Addr_Any, +} + +@(private="package") +_Read :: struct {} + +@(private="package") +_Write :: struct {} + +@(private="package") +_Send :: struct { + endpoint: linux.Sock_Addr_Any, + msghdr: linux.Msg_Hdr, + small_bufs: [1][]byte, +} + +@(private="package") +_Recv :: struct { + addr_out: linux.Sock_Addr_Any, + msghdr: linux.Msg_Hdr, + small_bufs: [1][]byte, +} + +@(private="package") +_Timeout :: struct { + expires: linux.Time_Spec, +} + +@(private="package") +_Poll :: struct {} + +@(private="package") +_Remove :: struct { + target: ^Operation, +} + +@(private="package") +_Link_Timeout :: struct { + target: ^Operation, + expires: linux.Time_Spec, +} + +@(private="package") +_Send_File :: struct { + len: int, + pipe: Handle, + + splice: ^Operation, +} + +@(private="package") +_Splice :: struct { + off: int, + len: int, + file: Handle, + pipe: Handle, + + written: int, + + sendfile: ^Operation, +} + +@(private="package") +_Open :: struct { + cpath: cstring, +} + +@(private="package") +_Stat :: struct { + buf: linux.Statx, +} + +@(private="package") +_init :: proc(l: ^Event_Loop, alloc: mem.Allocator) -> (err: General_Error) { + params := uring.DEFAULT_PARAMS + params.flags += {.SUBMIT_ALL, .COOP_TASKRUN, .SINGLE_ISSUER} + + uerr := uring.init(&l.ring, ¶ms, QUEUE_SIZE) + if uerr != nil { + err = General_Error(uerr) + return + } + defer if err != nil { uring.destroy(&l.ring) } + + if perr := queue.init(&l.unqueued, allocator = alloc); perr != nil { + err = .Allocation_Failed + return + } + defer if err != nil { queue.destroy(&l.unqueued) } + + if perr := queue.init(&l.completed, allocator = alloc); perr != nil { + err = .Allocation_Failed + return + } + defer if err != nil { queue.destroy(&l.completed) } + + set_up_wake_up(l) or_return + + return + + set_up_wake_up :: proc(l: ^Event_Loop) -> General_Error { + wakefd, wakefd_err := linux.eventfd(0, {.SEMAPHORE, .CLOEXEC, .NONBLOCK}) + if wakefd_err != nil { + return General_Error(wakefd_err) + } + + op, alloc_err := new(Operation, l.allocator) + if alloc_err != nil { + linux.close(wakefd) + return .Allocation_Failed + } + + l.wake = op + l.wake.detached = true + l.wake.l = l + l.wake.type = .Read + l.wake.cb = wake_up_callback + l.wake.read.handle = wakefd + l.wake.read.buf = ([^]byte)(&l.wake.user_data)[:8] + _exec(l.wake) + + return nil + } + + wake_up_callback :: proc(op: ^Operation) { + assert(op.type == .Read) + assert(op == op.l.wake) + assert(op.read.err == nil) + assert(op.read.read == 8) + value := intrinsics.unaligned_load((^u64)(&op.user_data)) + assert(value > 0) + debug(int(value), "wake_up calls handled") + + op.read.read = 0 + op.user_data = {} + _exec(op) + } +} + +@(private="package") +_destroy :: proc(l: ^Event_Loop) { + linux.close(l.wake.read.handle) + free(l.wake, l.allocator) + + queue.destroy(&l.unqueued) + queue.destroy(&l.completed) + uring.destroy(&l.ring) +} + +@(private="package") +__tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> General_Error { + debug("tick") + + // Execute completed operations, mostly next tick ops, also some other ops that may error before + // adding it to the Uring. + n := queue.len(l.completed) + if n > 0 { + l.now = time.now() + for _ in 0 ..< n { + completed := queue.pop_front(&l.completed) + if completed._impl.removal == nil { + completed.cb(completed) + } else if completed._impl.removal != (^Operation)(REMOVED) { + completed._impl.removal._remove.target = nil + } + if !completed.detached { + pool.put(&l.operation_pool, completed) + } + } + } + + err := _flush_submissions(l, timeout) + if err != nil { return General_Error(err) } + + l.now = time.now() + + err = _flush_completions(l, false) + if err != nil { return General_Error(err) } + + return nil + + _flush_completions :: proc(l: ^Event_Loop, wait: bool) -> linux.Errno { + wait := wait + cqes: [128]linux.IO_Uring_CQE = --- + for { + completed, err := uring.copy_cqes(&l.ring, cqes[:], 1 if wait else 0) + if err == .EINTR { + continue + } else if err != nil { + return err + } + + _flush_unqueued(l) + + if completed > 0 { + debug(int(completed), "operations returned from uring") + } + + for cqe in cqes[:completed] { + assert(cqe.user_data != 0) + op, is_timeout := unpack_operation(cqe.user_data) + if is_timeout { + link_timeout_callback(op, cqe.res) + } else { + handle_completed(op, cqe.res) + } + } + + if completed < len(cqes) { break } + + debug("more events ready than our results buffer handles, getting more") + wait = false + } + + return nil + } + + _flush_submissions :: proc(l: ^Event_Loop, timeout: time.Duration) -> linux.Errno { + for { + ts: linux.Time_Spec + ts.time_nsec = uint(timeout) + _, err := uring.submit(&l.ring, 0 if timeout == 0 else 1, nil if timeout < 0 else &ts) + #partial switch err { + case .NONE, .ETIME: + case .EINTR: + warn("uring interrupted") + continue + case .ENOMEM: + // It's full, wait for at least one operation to complete and try again. + warn("could not flush submissions, ENOMEM, waiting for operations to complete before continuing") + ferr := _flush_completions(l, true) + if ferr != nil { return ferr } + continue + case: + return err + } + + break + } + + return nil + } + + _flush_unqueued :: proc(l: ^Event_Loop) { + n := queue.len(l.unqueued) + for _ in 0..<n { + unqueued := queue.pop_front(&l.unqueued) + + if unqueued._impl.removal != nil { + debug(unqueued.type, "was removed and has not been on the ring yet") + if unqueued._impl.removal != (^Operation)(REMOVED) { + // Set the removal target to nil to indicate we've already done it. + unqueued._impl.removal._remove.target = nil + } + if !unqueued.detached { + pool.put(&l.operation_pool, unqueued) + } + continue + } else if unqueued.type == ._Remove { + if unqueued._remove.target == nil { + // If the removal was set to nil by the branch above, we don't need to do anything. + debug("removal target was nil, skipping remove") + if !unqueued.detached { + pool.put(&l.operation_pool, unqueued) + } + continue + } else { + debug("removal was added to ring later") + enqueue(unqueued, uring.async_cancel( + &unqueued.l.ring, + u64(uintptr(unqueued._remove.target)), + u64(uintptr(unqueued)), + )) + continue + } + } + _exec(unqueued) + } + } +} + +@(private="package") +_exec :: proc(op: ^Operation) { + assert(op.l == &_tls_event_loop) + switch op.type { + case .Accept: accept_exec(op) + case .Dial: dial_exec(op) + case .Read: read_exec(op) + case .Write: write_exec(op) + case .Recv: recv_exec(op) + case .Send: send_exec(op) + case .Poll: poll_exec(op) + case .Close: close_exec(op) + case .Timeout: timeout_exec(op) + case .Send_File: sendfile_exec(op) + case .Open: open_exec(op) + case .Stat: stat_exec(op) + case ._Splice: + // This is only reachable when the queue was full the last tick. + // And if that's the case, it would still be full for the real sendfile (splice B) and will be enqueued there. + // So it is safe to do nothing here. + case ._Remove: unreachable() + case ._Link_Timeout: unreachable() + case .None: unreachable() + } +} + +@(private="package") +_open_sync :: proc(l: ^Event_Loop, path: string, dir: Handle, mode: File_Flags, perm: Permissions) -> (handle: Handle, err: FS_Error) { + if path == "" { + err = .Invalid_Argument + return + } + + cpath, cerr := strings.clone_to_cstring(path, l.allocator) + if cerr != nil { + err = .Allocation_Failed + return + } + defer delete(cpath, l.allocator) + + sys_flags := linux.Open_Flags{.NOCTTY, .CLOEXEC, .NONBLOCK} + + if .Write in mode { + if .Read in mode { + sys_flags += {.RDWR} + } else { + sys_flags += {.WRONLY} + } + } + + if .Append in mode { sys_flags += {.APPEND} } + if .Create in mode { sys_flags += {.CREAT} } + if .Excl in mode { sys_flags += {.EXCL} } + if .Sync in mode { sys_flags += {.DSYNC} } + if .Trunc in mode { sys_flags += {.TRUNC} } + // if .Inheritable in mode { sys_flags -= {.CLOEXEC} } + + errno: linux.Errno + handle, errno = linux.openat(dir, cpath, sys_flags, transmute(linux.Mode)perm) + if errno != nil { + err = FS_Error(errno) + } + + return +} + +@(private="package") +_create_socket :: proc( + _: ^Event_Loop, + family: Address_Family, + protocol: Socket_Protocol, +) -> ( + socket: Any_Socket, + err: Create_Socket_Error, +) { + socket = net.create_socket(family, protocol) or_return + // NOTE: this doesn't seem needed with io uring. + // defer if err != nil { net.close(socket) } + // net.set_blocking(socket, false) or_return + return +} + +@(private="package") +_listen :: proc(socket: TCP_Socket, backlog := 1000) -> Listen_Error { + err := linux.listen(linux.Fd(socket), i32(backlog)) + if err != nil { + return net._listen_error(err) + } + return nil +} + +@(private="package") +_remove :: proc(target: ^Operation) { + target := target + assert(target != nil) + + if target._impl.removal != nil { + return + } + + op := _prep(target.l, proc(_: ^Operation) {}, ._Remove) + op._remove.target = target + + target._impl.removal = op + + enqueue(op, uring.async_cancel( + &op.l.ring, + u64(uintptr(target)), + u64(uintptr(op)), + )) +} + +@(private="package") +_associate_handle :: proc(handle: uintptr, l: ^Event_Loop) -> (Handle, Association_Error) { + // Works by default. + return Handle(handle), nil +} + +@(private="package") +_associate_socket :: proc(socket: Any_Socket, l: ^Event_Loop) -> Association_Error { + // Works by default. + return nil +} + +@(private="package") +_wake_up :: proc(l: ^Event_Loop) { + assert(l != &_tls_event_loop) + one: u64 = 1 + // Called from another thread, in which we can't use the uring. + n, err := linux.write(l.wake.read.handle, ([^]byte)(&one)[:size_of(one)]) + // Shouldn't fail. + assert(err == nil) + assert(n == 8) +} + +@(private="package") +_yield :: proc() { + linux.sched_yield() +} + +// Start file private. + +// The size of the IO Uring queues. +QUEUE_SIZE :: #config(ODIN_NBIO_QUEUE_SIZE, 2048) +#assert(QUEUE_SIZE <= uring.MAX_ENTRIES) + +#assert(size_of(Operation) <= 384) // Just so we see when we make it bigger. +#assert(size_of(Specifics) <= 288) // Just so we see when we make it bigger. + +REMOVED :: rawptr(max(uintptr)-1) + +handle_completed :: proc(op: ^Operation, res: i32) { + debug("handling", op.type, "result", int(res)) + + switch op.type { + case .Accept: + accept_callback(op, res) + case .Dial: + dial_callback(op, res) + case .Timeout: + timeout_callback(op, res) + case .Write: + if !write_callback(op, res) { return } + case .Read: + if !read_callback(op, res) { return } + case .Close: + close_callback(op, res) + case .Poll: + poll_callback(op, res) + case .Send: + if !send_callback(op, res) { return } + maybe_callback(op) + if len(op.send.bufs) > 1 { delete(op.send.bufs, op.l.allocator) } + cleanup(op) + return + case .Recv: + if !recv_callback(op, res) { return } + maybe_callback(op) + if len(op.recv.bufs) > 1 { delete(op.recv.bufs, op.l.allocator) } + cleanup(op) + return + case .Open: + open_callback(op, res) + case .Stat: + stat_callback(op, res) + case .Send_File: + if !sendfile_callback(op, res) { return } + case ._Splice: + if !splice_callback(op, res) { return } + case ._Remove: + if !remove_callback(op, res) { return } + case ._Link_Timeout: + unreachable() + case .None: + fallthrough + case: + panic("corrupted operation") + } + + maybe_callback(op) + cleanup(op) + + maybe_callback :: proc(op: ^Operation) { + if op._impl.removal == nil { + debug("done, calling back", op.type) + op.cb(op) + } else if op._impl.removal == (^Operation)(REMOVED) { + debug("done but was cancelled by remove", op.type) + } else { + debug("done but has removal pending", op.type) + // If the remove callback sees their target is nil, they know it is done already. + op._impl.removal._remove.target = nil + } + } + + cleanup :: proc(op: ^Operation) { + if !op.detached { + pool.put(&op.l.operation_pool, op) + } + } +} + +enqueue :: proc(op: ^Operation, sqe: ^linux.IO_Uring_SQE, ok: bool) { + assert(uintptr(op) & LINK_TIMEOUT_MASK == 0) + debug("enqueue", op.type) + if !ok { + warn("queueing for next tick because the ring is full, queue size may need increasing") + pok, _ := queue.push_back(&op.l.unqueued, op) + ensure(pok, "unqueued queue allocation failure") + return + } + + op._impl.sqe = sqe +} + +LINK_TIMEOUT_MASK :: 1 + +link_timeout :: proc(target: ^Operation, expires: time.Time) { + if expires == {} { + return + } + + // If the last op was queued because kernel is full, return. + if target._impl.sqe == nil { + assert(queue.len(target.l.unqueued) > 0 && queue.back_ptr(&target.l.unqueued)^ == target) + return + } + + target._impl.sqe.flags += {.IO_LINK} + target._impl.expires = ns_to_time_spec(expires._nsec) + + // Tag the pointer as a timeout. + p := uintptr(target) + assert(p & LINK_TIMEOUT_MASK == 0) + p |= LINK_TIMEOUT_MASK + + _, ok := uring.link_timeout( + &target.l.ring, + u64(p), + &target._impl.expires, + {.ABS, .REALTIME}, + ) + // If the target wasn't queued, the link timeout should not need to be queued, because uring + // leaves one spot specifically for a link. + assert(ok) +} + +link_timeout_callback :: proc(op: ^Operation, res: i32) { + err := linux.Errno(-res) + if err != nil && err != .ETIME && err != .ECANCELED { + panic("unexpected nbio.link_timeout() error") + } +} + +unpack_operation :: #force_inline proc(user_data: u64) -> (op: ^Operation, timed_out: bool) { + p := uintptr(user_data) + return (^Operation)(p &~ LINK_TIMEOUT_MASK), bool(p & LINK_TIMEOUT_MASK) +} + +@(require_results) +remove_callback :: proc(op: ^Operation, res: i32) -> bool { + assert(op.type == ._Remove) + err := linux.Errno(-res) + + target := op._remove.target + if target == nil { + debug("remove target nil, already handled") + return true + } + + assert(target.type != .None) + assert(target._impl.removal == op) + + if err == .ENOENT { + debug("remove ENOENT, trying again") + + enqueue(op, uring.async_cancel( + &op.l.ring, + u64(uintptr(target)), + u64(uintptr(op)), + )) + + return false + } else if err == .EALREADY { + debug("remove is accepted and will be tried") + } else if err != nil { + assert(false, "unexpected nbio.remove() error") + } + + // Set to sentinel so nothing references the operation that will be reused. + target._impl.removal = (^Operation)(REMOVED) + return true +} + +accept_exec :: proc(op: ^Operation) { + assert(op.type == .Accept) + op.accept._impl.sockaddr_len = size_of(op.accept._impl.sockaddr) + enqueue(op, uring.accept( + &op.l.ring, + u64(uintptr(op)), + linux.Fd(op.accept.socket), + &op.accept._impl.sockaddr, + &op.accept._impl.sockaddr_len, + {}, + )) + link_timeout(op, op.accept.expires) +} + +accept_callback :: proc(op: ^Operation, res: i32) { + assert(op.type == .Accept) + if res < 0 { + errno := linux.Errno(-res) + #partial switch errno { + case .ECANCELED: + op.accept.err = .Timeout + case: + op.accept.err = net._accept_error(errno) + } + + return + } + + op.accept.client = TCP_Socket(res) + // net.set_blocking(net.TCP_Socket(op.accept.client), false) + op.accept.client_endpoint = sockaddr_storage_to_endpoint(&op.accept._impl.sockaddr) +} + +dial_exec :: proc(op: ^Operation) { + assert(op.type == .Dial) + if op.dial.socket == {} { + if op.dial.endpoint.port == 0 { + op.dial.err = .Port_Required + queue.push_back(&op.l.completed, op) + return + } + + sock, err := create_socket(net.family_from_endpoint(op.dial.endpoint), .TCP) + if err != nil { + op.dial.err = err + queue.push_back(&op.l.completed, op) + return + } + + op.dial.socket = sock.(TCP_Socket) + op.dial._impl.sockaddr = endpoint_to_sockaddr(op.dial.endpoint) + } + + enqueue(op, uring.connect( + &op.l.ring, + u64(uintptr(op)), + linux.Fd(op.dial.socket), + &op.dial._impl.sockaddr, + )) + link_timeout(op, op.dial.expires) +} + +dial_callback :: proc(op: ^Operation, res: i32) { + assert(op.type == .Dial) + errno := linux.Errno(-res) + if errno != nil { + #partial switch errno { + case .ECANCELED: + op.dial.err = Dial_Error.Timeout + case: + op.dial.err = net._dial_error(errno) + } + close(op.dial.socket) + } +} + +timeout_exec :: proc(op: ^Operation) { + assert(op.type == .Timeout) + if op.timeout.duration <= 0 { + queue.push_back(&op.l.completed, op) + return + } + + expires := time.time_add(op.l.now, op.timeout.duration) + op.timeout._impl.expires = ns_to_time_spec(expires._nsec) + + enqueue(op, uring.timeout( + &op.l.ring, + u64(uintptr(op)), + &op.timeout._impl.expires, + 0, + {.ABS, .REALTIME}, + )) +} + +timeout_callback :: proc(op: ^Operation, res: i32) { + if res < 0 { + errno := linux.Errno(-res) + #partial switch errno { + case .ETIME, .ECANCELED: // OK. + case: + debug("unexpected timeout error:", int(errno)) + panic("unexpected timeout error") + } + } +} + +close_exec :: proc(op: ^Operation) { + assert(op.type == .Close) + + fd: linux.Fd + switch closable in op.close.subject { + case Handle: fd = linux.Fd(closable) + case TCP_Socket: fd = linux.Fd(closable) + case UDP_Socket: fd = linux.Fd(closable) + case: op.close.err = .Invalid_Argument; return + } + + enqueue(op, uring.close( + &op.l.ring, + u64(uintptr(op)), + fd, + )) +} + +close_callback :: proc(op: ^Operation, res: i32) { + assert(op.type == .Close) + op.close.err = FS_Error(linux.Errno(-res)) +} + +recv_exec :: proc(op: ^Operation) { + assert(op.type == .Recv) + + if op.recv.err != nil { + queue.push_back(&op.l.completed, op) + return + } + + bufs := slice.advance_slices(op.recv.bufs, op.recv.received) + bufs, _ = constraint_bufs_to_max_rw(bufs) + op.recv._impl.msghdr.iov = transmute([]linux.IO_Vec)bufs + + sock: linux.Fd + switch socket in op.recv.socket { + case TCP_Socket: + sock = linux.Fd(socket) + case UDP_Socket: + sock = linux.Fd(socket) + op.recv._impl.msghdr.name = &op.recv._impl.addr_out + op.recv._impl.msghdr.namelen = size_of(op.recv._impl.addr_out) + } + + enqueue(op, uring.recvmsg( + &op.l.ring, + u64(uintptr(op)), + linux.Fd(sock), + &op.recv._impl.msghdr, + {.NOSIGNAL}, + )) + link_timeout(op, op.recv.expires) +} + +@(require_results) +recv_callback :: proc(op: ^Operation, res: i32) -> bool { + assert(op.type == .Recv) + + if res < 0 { + errno := linux.Errno(-res) + switch sock in op.recv.socket { + case TCP_Socket: + #partial switch errno { + case .ECANCELED: + op.recv.err = TCP_Recv_Error.Timeout + case: + op.recv.err = net._tcp_recv_error(errno) + } + case UDP_Socket: + #partial switch errno { + case .ECANCELED: + op.recv.err = UDP_Recv_Error.Timeout + case: + op.recv.err = net._udp_recv_error(errno) + } + } + + return true + } + + op.recv.received += int(res) + + switch sock in op.recv.socket { + case TCP_Socket: + if res == 0 { + // Connection closed. + return true + } + + if op.recv.all { + total: int + for buf in op.recv.bufs { + total += len(buf) + } + + if op.recv.received < total { + recv_exec(op) + return false + } + } + + case UDP_Socket: + op.recv.source = sockaddr_storage_to_endpoint(&op.recv._impl.addr_out) + } + + return true +} + +send_exec :: proc(op: ^Operation) { + assert(op.type == .Send) + + if op.send.err != nil { + queue.push_back(&op.l.completed, op) + return + } + + bufs := slice.advance_slices(op.send.bufs, op.send.sent) + bufs, _ = constraint_bufs_to_max_rw(bufs) + op.send._impl.msghdr.iov = transmute([]linux.IO_Vec)bufs + + sock: linux.Fd + switch socket in op.send.socket { + case TCP_Socket: + sock = linux.Fd(socket) + case UDP_Socket: + sock = linux.Fd(socket) + op.send._impl.endpoint = endpoint_to_sockaddr(op.send.endpoint) + op.send._impl.msghdr.name = &op.send._impl.endpoint + op.send._impl.msghdr.namelen = size_of(op.send._impl.endpoint) + } + + enqueue(op, uring.sendmsg( + &op.l.ring, + u64(uintptr(op)), + sock, + &op.send._impl.msghdr, + {.NOSIGNAL}, + )) + link_timeout(op, op.send.expires) +} + +@(require_results) +send_callback :: proc(op: ^Operation, res: i32) -> bool { + assert(op.type == .Send) + if res < 0 { + errno := linux.Errno(-res) + switch sock in op.send.socket { + case TCP_Socket: + #partial switch errno { + case .ECANCELED: + op.send.err = TCP_Send_Error.Timeout + case: + op.send.err = net._tcp_send_error(errno) + } + case UDP_Socket: + #partial switch errno { + case .ECANCELED: + op.send.err = UDP_Send_Error.Timeout + case: + op.send.err = net._udp_send_error(errno) + } + case: panic("corrupted socket") + } + + return true + } + + op.send.sent += int(res) + + if op.send.all { + total: int + for buf in op.send.bufs { + total += len(buf) + } + + if op.send.sent < total { + assert(res > 0) + send_exec(op) + return false + } + } + + return true +} + +write_exec :: proc(op: ^Operation) { + assert(op.type == .Write) + + buf := op.write.buf[op.write.written:] + buf = buf[:min(MAX_RW, len(buf))] + + enqueue(op, uring.write( + &op.l.ring, + u64(uintptr(op)), + op.write.handle, + buf, + u64(op.write.offset) + u64(op.write.written), + )) + link_timeout(op, op.write.expires) +} + +@(require_results) +write_callback :: proc(op: ^Operation, res: i32) -> bool { + if res < 0 { + errno := linux.Errno(-res) + op.write.err = FS_Error(errno) + return true + } + + op.write.written += int(res) + + if op.write.all && op.write.written < len(op.write.buf) { + write_exec(op) + return false + } + + return true +} + +read_exec :: proc(op: ^Operation) { + assert(op.type == .Read) + + buf := op.read.buf[op.read.read:] + buf = buf[:min(MAX_RW, len(buf))] + + enqueue(op, uring.read( + &op.l.ring, + u64(uintptr(op)), + op.read.handle, + buf, + u64(op.read.offset) + u64(op.read.read), + )) + link_timeout(op, op.read.expires) +} + +@(require_results) +read_callback :: proc(op: ^Operation, res: i32) -> bool { + if res < 0 { + errno := linux.Errno(-res) + op.read.err = FS_Error(errno) + return true + } else if res == 0 { + if op.read.read == 0 { + op.read.err = .EOF + } + return true + } + + op.read.read += int(res) + + if op.read.all && op.read.read < len(op.read.buf) { + read_exec(op) + return false + } + + return true +} + +poll_exec :: proc(op: ^Operation) { + assert(op.type == .Poll) + + events: linux.Fd_Poll_Events + switch op.poll.event { + case .Receive: events = { .IN } + case .Send: events = { .OUT } + } + + fd: linux.Fd + switch sock in op.poll.socket { + case TCP_Socket: fd = linux.Fd(sock) + case UDP_Socket: fd = linux.Fd(sock) + } + + enqueue(op, uring.poll_add( + &op.l.ring, + u64(uintptr(op)), + fd, + events, + {}, + )) + link_timeout(op, op.poll.expires) +} + +poll_callback :: proc(op: ^Operation, res: i32) { + if res < 0 { + errno := linux.Errno(-res) + #partial switch errno { + case .NONE: // no-op + case .ECANCELED: + op.poll.result = .Timeout + case .EINVAL, .EFAULT, .EBADF: + op.poll.result = .Invalid_Argument + case: + op.poll.result = .Error + } + + return + } + + op.poll.result = .Ready +} + +/* +`sendfile` is implemented with 2 splices over a pipe. + +Splice A: from file to pipe +Splice B: from pipe to socket (optionally linked to a timeout) + +The splices are hard-linked which means A completes before B. +B could get an `EWOULDBLOCK`, which is when the remote end has not read enough of the socket data yet. +In that case we enqueue a poll on the socket and continue when that completes. +A shouldn't get `EWOULDBLOCK`, but as a cautionary measure we handle it. + +The timeout is either linked to the splice B op, or the poll op, either of these is also always in progress in the kernel. +*/ +sendfile_exec :: proc(op: ^Operation, splice := true) { + assert(op.type == .Send_File) + + splice_done := !splice + if splice_op := op.sendfile._impl.splice; splice && splice_op != nil { + splice_done = splice_op._splice.written == splice_op._splice.len + } + + debug("sendfile_exec") + + if op.sendfile._impl.splice == nil { + // First stat for the file size. + if op.sendfile.nbytes == SEND_ENTIRE_FILE { + debug("sendfile SEND_ENTIRE_FILE, doing stat") + + stat_poly(op.sendfile.file, op, proc(stat_op: ^Operation, sendfile_op: ^Operation) { + if stat_op.stat.err != nil { + sendfile_op.sendfile.err = stat_op.stat.err + } else if stat_op.stat.type != .Regular { + sendfile_op.sendfile.err = FS_Error.Invalid_Argument + } else { + sendfile_op.sendfile.nbytes = int(i64(stat_op.stat.size) - i64(sendfile_op.sendfile.offset)) + if sendfile_op.sendfile.nbytes <= 0 { + sendfile_op.sendfile.err = FS_Error.Invalid_Argument + } + } + + if sendfile_op.sendfile.err != nil { + handle_completed(sendfile_op, 0) + return + } + + sendfile_exec(sendfile_op) + }) + return + } + + debug("sendfile setting up") + + rw: [2]linux.Fd + err := linux.pipe2(&rw, {.NONBLOCK, .CLOEXEC}) + if err != nil { + op.sendfile.err = FS_Error(err) + queue.push_back(&op.l.completed, op) + return + } + + splice_op := _prep(op.l, proc(_: ^Operation) { debug("sendfile splice helper callback") }, ._Splice) + splice_op._splice.sendfile = op + splice_op._splice.file = op.sendfile.file + splice_op._splice.pipe = rw[1] + splice_op._splice.off = op.sendfile.offset + splice_op._splice.len = op.sendfile.nbytes + + op.sendfile._impl.splice = splice_op + op.sendfile._impl.pipe = rw[0] + op.sendfile._impl.len = op.sendfile.nbytes + } + + splice_op: ^Operation + if !splice_done { + splice_op = op.sendfile._impl.splice + enqueue(splice_op, uring.splice( + &splice_op.l.ring, + u64(uintptr(splice_op)), + splice_op._splice.file, + i64(splice_op._splice.off) + i64(splice_op._splice.written), + splice_op._splice.pipe, + -1, + u32(min(splice_op._splice.len - splice_op._splice.written, MAX_RW)), + {.NONBLOCK}, + )) + } + + b, b_added := uring.splice( + &op.l.ring, + u64(uintptr(op)), + op.sendfile._impl.pipe, + -1, + linux.Fd(op.sendfile.socket), + -1, + u32(min(op.sendfile._impl.len - op.sendfile.sent, MAX_RW)), + {.NONBLOCK}, + ) + if !splice_done && b_added { + assert(splice_op._impl.sqe != nil) // if b was added successfully, a should've been too. + // Makes sure splice A (file to pipe) completes before splice B (pipe to socket). + splice_op._impl.sqe.flags += {.IO_HARDLINK} + } + enqueue(op, b, b_added) + + link_timeout(op, op.sendfile.expires) +} + +@(require_results) +splice_callback :: proc(op: ^Operation, res: i32) -> bool { + assert(op.type == ._Splice) + + if res < 0 { + errno := linux.Errno(-res) + #partial switch errno { + case .EAGAIN: + // Splice A (from file to pipe) would block, this means the buffer is full and it first needs + // to be sent over the socket by splice B (from pipe to socket). + // So we don't do anything here, once a splice B completes a new splice A will be created. + + case: + // Splice A (from file to pipe) error, we need to close the pipes, cancel the pending splice B, + // and call the callback with the error. + + debug("sendfile helper splice error, closing pipe") + + close(op._splice.pipe) + + // This is nil if this is a cancel originating from the sendfile. + // This is not nil if it is an actual error that happened on this splice. + sendfile_op := op._splice.sendfile + if sendfile_op != nil { + debug("sendfile helper splice error, cancelling main sendfile") + assert(sendfile_op.type == .Send_File) + + sendfile_op.sendfile._impl.splice = nil + sendfile_op.sendfile.err = FS_Error(errno) + } + } + + return true + } + + op._splice.written += int(res) + + sendfile_op := op._splice.sendfile + if sendfile_op != nil { + if op._splice.written < sendfile_op.sendfile.nbytes { + return false + } + + sendfile_op.sendfile._impl.splice = nil + } + + assert(op._splice.pipe > 0) + close(op._splice.pipe) + + debug("sendfile helper splice completely done") + return true +} + +@(require_results) +sendfile_callback :: proc(op: ^Operation, res: i32) -> bool { + assert(op.type == .Send_File) + + if op.sendfile.err == nil && res < 0 { + errno := linux.Errno(-res) + #partial switch errno { + case .EAGAIN: + // Splice B (from pipe to socket) would block. We are waiting on the remote to read more + // of our buffer before we can send more to it. + // We use a poll to find out when this is. + + debug("sendfile needs to poll") + + poll_op := poll_poly(op.sendfile.socket, .Send, op, proc(poll_op: ^Operation, sendfile_op: ^Operation) { + #partial switch poll_op.poll.result { + case .Ready: + // Do not enqueue a splice right away, we know there is at least one splice call worth of data in the kernel buffer. + sendfile_exec(sendfile_op, splice=false) + return + + case .Timeout: + sendfile_op.sendfile.err = TCP_Send_Error.Timeout + case: + sendfile_op.sendfile.err = TCP_Send_Error.Unknown + } + + debug("sendfile poll error") + handle_completed(sendfile_op, 0) + }) + + link_timeout(poll_op, op.sendfile.expires) + return false + + case .ECANCELED: + op.sendfile.err = TCP_Send_Error.Timeout + case: + op.sendfile.err = net._tcp_send_error(errno) + } + } + + if op.sendfile.err != nil { + debug("sendfile error") + + if op.sendfile._impl.pipe > 0 { + close(op.sendfile._impl.pipe) + } + + splice_op := op.sendfile._impl.splice + if splice_op != nil { + assert(splice_op.type == ._Splice) + splice_op._splice.sendfile = nil + _remove(splice_op) + } + + return true + } + + op.sendfile.sent += int(res) + if op.sendfile.sent < op.sendfile._impl.len { + debug("sendfile not completely done yet") + sendfile_exec(op) + if op.sendfile.progress_updates { op.cb(op) } + return false + } + + debug("sendfile completely done") + return true +} + +open_exec :: proc(op: ^Operation) { + assert(op.type == .Open) + + sys_flags := linux.Open_Flags{.NOCTTY, .CLOEXEC, .NONBLOCK} + + if .Write in op.open.mode { + if .Read in op.open.mode { + sys_flags += {.RDWR} + } else { + sys_flags += {.WRONLY} + } + } + + if .Append in op.open.mode { sys_flags += {.APPEND} } + if .Create in op.open.mode { sys_flags += {.CREAT} } + if .Excl in op.open.mode { sys_flags += {.EXCL} } + if .Sync in op.open.mode { sys_flags += {.DSYNC} } + if .Trunc in op.open.mode { sys_flags += {.TRUNC} } + // if .Inheritable in op.open.mode { sys_flags -= {.CLOEXEC} } + + cpath, err := strings.clone_to_cstring(op.open.path, op.l.allocator) + if err != nil { + op.open.err = .Allocation_Failed + queue.push_back(&op.l.completed, op) + return + } + op.open._impl.cpath = cpath + + enqueue(op, uring.openat( + &op.l.ring, + u64(uintptr(op)), + linux.Fd(op.open.dir), + op.open._impl.cpath, + transmute(linux.Mode)op.open.perm, + sys_flags, + )) +} + +open_callback :: proc(op: ^Operation, res: i32) { + assert(op.type == .Open) + + delete(op.open._impl.cpath, op.l.allocator) + + if res < 0 { + errno := linux.Errno(-res) + op.open.err = FS_Error(errno) + return + } + + op.open.handle = Handle(res) +} + +stat_exec :: proc(op: ^Operation) { + assert(op.type == .Stat) + + enqueue(op, uring.statx( + &op.l.ring, + u64(uintptr(op)), + op.stat.handle, + "", + {.EMPTY_PATH}, + {.TYPE, .SIZE}, + &op.stat._impl.buf, + )) +} + +stat_callback :: proc(op: ^Operation, res: i32) { + assert(op.type == .Stat) + + if res < 0 { + errno := linux.Errno(-res) + op.stat.err = FS_Error(errno) + return + } + + type := File_Type.Regular + switch op.stat._impl.buf.mode & linux.S_IFMT { + case linux.S_IFBLK, linux.S_IFCHR: type = .Device + case linux.S_IFDIR: type = .Directory + case linux.S_IFIFO: type = .Pipe_Or_Socket + case linux.S_IFLNK: type = .Symlink + case linux.S_IFREG: type = .Regular + case linux.S_IFSOCK: type = .Pipe_Or_Socket + } + + op.stat.type = type + op.stat.size = i64(op.stat._impl.buf.size) +} + +@(require_results) +sockaddr_storage_to_endpoint :: proc(addr: ^linux.Sock_Addr_Any) -> (ep: Endpoint) { + #partial switch addr.family { + case .INET: + return Endpoint { + address = IP4_Address(addr.sin_addr), + port = int(addr.sin_port), + } + case .INET6: + return Endpoint { + address = IP6_Address(transmute([8]u16be)addr.sin6_addr), + port = int(addr.sin6_port), + } + case: + return {} + } +} + +@(require_results) +endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: linux.Sock_Addr_Any) { + switch a in ep.address { + case IP4_Address: + sockaddr.sin_family = .INET + sockaddr.sin_port = u16be(ep.port) + sockaddr.sin_addr = cast([4]u8)a + return + case IP6_Address: + sockaddr.sin6_family = .INET6 + sockaddr.sin6_port = u16be(ep.port) + sockaddr.sin6_addr = transmute([16]u8)a + return + } + + unreachable() +} + +@(require_results) +ns_to_time_spec :: proc(nsec: i64) -> linux.Time_Spec { + NANOSECONDS_PER_SECOND :: 1e9 + return { + time_sec = uint(nsec / NANOSECONDS_PER_SECOND), + time_nsec = uint(nsec % NANOSECONDS_PER_SECOND), + } +} diff --git a/core/nbio/impl_others.odin b/core/nbio/impl_others.odin new file mode 100644 index 000000000..0a4564454 --- /dev/null +++ b/core/nbio/impl_others.odin @@ -0,0 +1,218 @@ +#+build !darwin +#+build !freebsd +#+build !openbsd +#+build !netbsd +#+build !linux +#+build !windows +#+private +package nbio + +import "core:container/avl" +import "core:container/pool" +import "core:container/queue" +import "core:mem" +import "core:slice" +import "core:time" + +_FULLY_SUPPORTED :: false + +_Event_Loop :: struct { + completed: queue.Queue(^Operation), + timeouts: avl.Tree(^Operation), +} + +_Handle :: uintptr + +_CWD :: Handle(-100) + +MAX_RW :: mem.Gigabyte + +_Operation :: struct { + removed: bool, +} + +_Accept :: struct {} + +_Close :: struct {} + +_Dial :: struct {} + +_Recv :: struct { + small_bufs: [1][]byte, +} + +_Send :: struct { + small_bufs: [1][]byte, +} + +_Read :: struct {} + +_Write :: struct {} + +_Timeout :: struct { + expires: time.Time, +} + +_Poll :: struct {} + +_Send_File :: struct {} + +_Open :: struct {} + +_Stat :: struct {} + +_Splice :: struct {} + +_Remove :: struct {} + +_Link_Timeout :: struct {} + +_init :: proc(l: ^Event_Loop, allocator: mem.Allocator) -> (rerr: General_Error) { + l.completed.data.allocator = allocator + + avl.init_cmp(&l.timeouts, timeouts_cmp, allocator) + + return nil + + timeouts_cmp :: #force_inline proc(a, b: ^Operation) -> slice.Ordering { + switch { + case a.timeout._impl.expires._nsec < b.timeout._impl.expires._nsec: + return .Less + case a.timeout._impl.expires._nsec > b.timeout._impl.expires._nsec: + return .Greater + case uintptr(a) < uintptr(b): + return .Less + case uintptr(a) > uintptr(b): + return .Greater + case: + assert(a == b) + return .Equal + } + } +} + +_destroy :: proc(l: ^Event_Loop) { + queue.destroy(&l.completed) + avl.destroy(&l.timeouts, false) +} + +__tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> General_Error { + l.now = time.now() + + for op in queue.pop_front_safe(&l.completed) { + if !op._impl.removed { + op.cb(op) + } + if !op.detached { + pool.put(&l.operation_pool, op) + } + } + + iter := avl.iterator(&l.timeouts, .Forward) + for node in avl.iterator_next(&iter) { + op := node.value + cexpires := time.diff(l.now, op.timeout._impl.expires) + + done := cexpires <= 0 + if done { + op.cb(op) + avl.remove_node(&l.timeouts, node) + if !op.detached { + pool.put(&l.operation_pool, op) + } + continue + } + + break + } + + return nil +} + +_create_socket :: proc(l: ^Event_Loop, family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) { + return nil, .Network_Unreachable +} + +_listen :: proc(socket: TCP_Socket, backlog := 1000) -> Listen_Error { + return .Network_Unreachable +} + +_exec :: proc(op: ^Operation) { + switch op.type { + case .Timeout: + _, _, err := avl.find_or_insert(&op.l.timeouts, op) + if err != nil { + panic("nbio: allocation failure") + } + return + case .Accept: + op.accept.err = .Network_Unreachable + case .Close: + op.close.err = .Unsupported + case .Dial: + op.dial.err = Dial_Error.Network_Unreachable + case .Recv: + switch _ in op.recv.socket { + case TCP_Socket: op.recv.err = TCP_Recv_Error.Network_Unreachable + case UDP_Socket: op.recv.err = UDP_Recv_Error.Network_Unreachable + case: op.recv.err = TCP_Recv_Error.Network_Unreachable + } + case .Send: + switch _ in op.send.socket { + case TCP_Socket: op.send.err = TCP_Send_Error.Network_Unreachable + case UDP_Socket: op.send.err = UDP_Send_Error.Network_Unreachable + case: op.send.err = TCP_Send_Error.Network_Unreachable + } + case .Send_File: + op.sendfile.err = .Network_Unreachable + case .Read: + op.read.err = .Unsupported + case .Write: + op.write.err = .Unsupported + case .Poll: + op.poll.result = .Error + case .Open: + op.open.err = .Unsupported + case .Stat: + op.stat.err = .Unsupported + case .None, ._Link_Timeout, ._Remove, ._Splice: + fallthrough + case: + unreachable() + } + + _, err := queue.push_back(&op.l.completed, op) + if err != nil { + panic("nbio: allocation failure") + } +} + +_remove :: proc(target: ^Operation) { + #partial switch target.type { + case .Timeout: + avl.remove_value(&target.l.timeouts, target) + if !target.detached { + pool.put(&target.l.operation_pool, target) + } + case: + target._impl.removed = true + } +} + +_open_sync :: proc(l: ^Event_Loop, path: string, dir: Handle, mode: File_Flags, perm: Permissions) -> (handle: Handle, err: FS_Error) { + return 0, FS_Error.Unsupported +} + +_associate_handle :: proc(handle: uintptr, l: ^Event_Loop) -> (Handle, Association_Error) { + return Handle(handle), nil +} + +_associate_socket :: proc(socket: Any_Socket, l: ^Event_Loop) -> Association_Error { + return nil +} + +_wake_up :: proc(l: ^Event_Loop) { +} + +_yield :: proc() { +} diff --git a/core/nbio/impl_posix.odin b/core/nbio/impl_posix.odin new file mode 100644 index 000000000..9b4863710 --- /dev/null +++ b/core/nbio/impl_posix.odin @@ -0,0 +1,1407 @@ +#+build darwin, freebsd, openbsd, netbsd +#+private file +package nbio + +import "core:c" +import "core:container/pool" +import "core:container/queue" +import "core:mem" +import "core:net" +import "core:slice" +import "core:strings" +import "core:sys/posix" +import "core:time" +import kq "core:sys/kqueue" +import sa "core:container/small_array" + +@(private="package") +_FULLY_SUPPORTED :: true + +@(private="package") +_Event_Loop :: struct { + // kqueue does not permit multiple {ident, filter} pairs in the kqueue. + // We have to keep record of what we currently have in the kqueue, and if we get an operation + // that would be the same (ident, filter) pair we need to bundle the operations under one kevent. + submitted: map[Queue_Identifier]^Operation, + // Holds all events we want to flush. Flushing is done each tick at which point this is emptied. + pending: sa.Small_Array(QUEUE_SIZE, kq.KEvent), + // Holds what should be in `pending` but didn't fit. + // When `pending`is flushed these are moved to `pending`. + overflow: queue.Queue(kq.KEvent), + // Contains all operations that were immediately completed in `exec`. + // These ops did not block so can call back next tick. + completed: queue.Queue(^Operation), + kqueue: kq.KQ, +} + +@(private="package") +_Handle :: posix.FD + +@(private="package") +_CWD :: posix.AT_FDCWD + +@(private="package") +MAX_RW :: mem.Gigabyte + +@(private="package") +_Operation :: struct { + // Linked list of operations that are bundled (same {ident, filter} pair) with this one. + next: ^Operation, + prev: ^Operation, + + flags: Operation_Flags, + result: i64, +} + +@(private="package") +_Accept :: struct {} + +@(private="package") +_Close :: struct {} + +@(private="package") +_Dial :: struct {} + +@(private="package") +_Recv :: struct { + small_bufs: [1][]byte, +} + +@(private="package") +_Send :: struct { + small_bufs: [1][]byte, +} + +@(private="package") +_Read :: struct {} + +@(private="package") +_Write :: struct {} + +@(private="package") +_Timeout :: struct {} + +@(private="package") +_Poll :: struct {} + +@(private="package") +_Send_File :: struct { + mapping: []byte, // `mmap`'d buffer (if native `sendfile` is not supported). +} + +@(private="package") +_Open :: struct {} + +@(private="package") +_Stat :: struct {} + +@(private="package") +_Splice :: struct {} + +@(private="package") +_Remove :: struct {} + +@(private="package") +_Link_Timeout :: struct {} + +@(private="package") +_init :: proc(l: ^Event_Loop, allocator: mem.Allocator) -> (rerr: General_Error) { + l.submitted.allocator = allocator + l.overflow.data.allocator = allocator + l.completed.data.allocator = allocator + + kqueue, err := kq.kqueue() + if err != nil { + return General_Error(posix.errno()) + } + + l.kqueue = kqueue + + sa.append(&l.pending, kq.KEvent{ + ident = IDENT_WAKE_UP, + filter = .User, + flags = {.Add, .Enable, .Clear}, + }) + + return nil +} + +@(private="package") +_destroy :: proc(l: ^Event_Loop) { + delete(l.submitted) + queue.destroy(&l.overflow) + queue.destroy(&l.completed) + posix.close(l.kqueue) +} + +@(private="package") +__tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> General_Error { + debug("tick") + + if n := queue.len(l.completed); n > 0 { + l.now = time.now() + debug("processing", n, "already completed") + + for _ in 0 ..< n { + op := queue.pop_front(&l.completed) + handle_completed(op) + } + + if pool.num_outstanding(&l.operation_pool) == 0 { return nil } + } + + if NBIO_DEBUG { + npending := sa.len(l.pending) + if npending > 0 { + debug("queueing", npending, "new events, there are", int(len(l.submitted)), "events pending") + } else { + debug("there are", int(len(l.submitted)), "events pending") + } + } + + ts_backing: posix.timespec + ts_pointer: ^posix.timespec // nil means forever. + if queue.len(l.completed) == 0 && len(l.submitted) > 0 { + if timeout >= 0 { + debug("timeout", timeout) + ts_backing = {tv_sec=posix.time_t(timeout/time.Second), tv_nsec=c.long(timeout%time.Second)} + ts_pointer = &ts_backing + } else { + debug("timeout forever") + } + } else { + debug("timeout 0, there is completed work pending") + ts_pointer = &ts_backing + } + + for { + results_buf: [128]kq.KEvent + results := kevent(l, results_buf[:], ts_pointer) or_return + + sa.clear(&l.pending) + for overflow in queue.pop_front_safe(&l.overflow) { + sa.append(&l.pending, overflow) or_break + } + + l.now = time.now() + + handle_results(l, results) + + if len(results) < len(results_buf) { + break + } + + debug("more events ready than our results buffer handles, getting more") + + // No timeout for the next call. + ts_backing = {} + ts_pointer = &ts_backing + } + + + return nil + + kevent :: proc(l: ^Event_Loop, buf: []kq.KEvent, ts: ^posix.timespec) -> ([]kq.KEvent, General_Error) { + for { + new_events, err := kq.kevent(l.kqueue, sa.slice(&l.pending), buf, ts) + #partial switch err { + case nil: + assert(new_events >= 0) + return buf[:new_events], nil + case .EINTR: + warn("kevent interrupted") + case: + warn("kevent error") + warn(string(posix.strerror(err))) + return nil, General_Error(err) + } + } + } + + is_internal_timeout :: proc(filter: kq.Filter, op: ^Operation) -> bool { + // A `.Timeout` that `.Has_Timeout` is a `remove()`'d timeout. + return filter == .Timer && (op.type != .Timeout || .Has_Timeout in op._impl.flags) + } + + handle_results :: proc(l: ^Event_Loop, results: []kq.KEvent) { + if len(results) > 0 { + debug(len(results), "events completed") + } + + // Mark all operations that have an event returned as not `.For_Kernel`. + // We have to do this right away, or we may process an operation as if we think the kernel is responsible. + for &event in results { + if ODIN_OS != .Darwin { + // On the BSDs, a `.Delete` that results in an `.Error` does not keep the `.Delete` flag in the result. + // We only have `udata == nil` when we do a delete, so we can add it back here to keep consistent. + if .Error in event.flags && event.udata == nil { + event.flags += {.Delete} + } + } + + if .Delete in event.flags { + continue + } + + if event.filter == .User && event.ident == IDENT_WAKE_UP { + continue + } + + op := cast(^Operation)event.udata + assert(op != nil) + assert(op.type != .None) + + if is_internal_timeout(event.filter, op) { + continue + } + + _, del := delete_key(&l.submitted, Queue_Identifier{ ident = event.ident, filter = event.filter }) + assert(del != nil) + + for next := op; next != nil; next = next._impl.next { + assert(.For_Kernel in next._impl.flags) + next._impl.flags -= {.For_Kernel} + } + } + + // If we get a timeout and an actual result, ignore the timeout. + // We have to do this after the previous loop so we know if the target op of a timeout was also completed. + // We have to do this before the next loop so we handle timeouts before their target ops. Otherwise the target could already be done. + for &event in results { + if .Delete in event.flags { + continue + } + + if event.filter == .User && event.ident == IDENT_WAKE_UP { + continue + } + + op := cast(^Operation)event.udata + if is_internal_timeout(event.filter, op) { + // If the actual event has also been returned this tick, we need to ignore the timeout to not get a uaf. + if .For_Kernel not_in op._impl.flags { + assert(.Has_Timeout in op._impl.flags) + op._impl.flags -= {.Has_Timeout} + + event.filter = kq.Filter(FILTER_IGNORE) + debug(op.type, "timed out but was also completed this tick, ignoring timeout") + } + + } + } + + for event in results { + if event.filter == kq.Filter(FILTER_IGNORE) { + // Previous loop told us to ignore. + continue + } + + if event.filter == .User && event.ident == IDENT_WAKE_UP { + debug("woken up") + continue + } + + if .Delete in event.flags { + assert(.Error in event.flags) + // Seems to happen when you delete at the same time or just after a close. + debug("delete error", int(event.data)) + if err := posix.Errno(event.data); err != .ENOENT && err != .EBADF { + warn("unexpected delete error") + warn(string(posix.strerror(err))) + } + continue + } + + op := cast(^Operation)event.udata + assert(op != nil) + assert(op.type != .None) + + // Timeout result that is a non-timeout op, meaning the operation timed out. + // Because of the previous loop we are sure that the target op is not also in this tick's results. + if is_internal_timeout(event.filter, op) { + debug("got timeout for", op.type) + + assert(.Error not_in event.flags) + + assert(.Has_Timeout in op._impl.flags) + op._impl.flags -= {.Has_Timeout} + + // Remove the actual operation. + timeout_and_delete(op) + handle_completed(op) + continue + } + + // Weird loop, but we need to get the next ptr before handle_completed(curr), curr is freed in handle_completed. + for curr, next := op, op._impl.next; curr != nil; curr, next = next, next == nil ? nil : next._impl.next { + if .Error in event.flags { curr._impl.flags += {.Error} } + if .EOF in event.flags { curr._impl.flags += {.EOF} } + curr._impl.result = event.data + handle_completed(curr) + } + } + } +} + +@(private="package") +_create_socket :: proc(l: ^Event_Loop, family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) { + socket = net.create_socket(family, protocol) or_return + + berr := net.set_blocking(socket, false) + // This shouldn't be able to fail. + assert(berr == nil) + + return +} + +@(private="package") +_listen :: proc(socket: TCP_Socket, backlog := 1000) -> Listen_Error { + if res := posix.listen(posix.FD(socket), i32(backlog)); res != .OK { + return posix_listen_error() + } + return nil +} + +@(private="package") +_exec :: proc(op: ^Operation) { + assert(op.l == &_tls_event_loop) + + debug("exec", op.type) + + result: Op_Result + switch op.type { + case .Accept: + result = accept_exec(op) + case .Close: + // no-op + case .Timeout: + result = timeout_exec(op) + case .Dial: + result = dial_exec(op) + case .Recv: + result = recv_exec(op) + case .Send: + result = send_exec(op) + case .Send_File: + result = sendfile_exec(op) + case .Read: + result = read_exec(op) + case .Write: + result = write_exec(op) + case .Poll: + result = poll_exec(op) + assert(result == .Pending) + case .Open: + open_exec(op) + case .Stat: + stat_exec(op) + case .None, ._Link_Timeout, ._Remove, ._Splice: + fallthrough + case: + unreachable() + } + + switch result { + case .Pending: + // no-op, in kernel. + debug(op.type, "pending") + case .Done: + debug(op.type, "done immediately") + op._impl.flags += {.Done} + _, err := queue.push_back(&op.l.completed, op) // Got result, handle it next tick. + ensure(err == nil, "allocation failure") + } +} + +@(private="package") +_remove :: proc(target: ^Operation) { + assert(target != nil) + + debug("remove", target.type) + + if .Removed in target._impl.flags { + debug("already removed") + return + } + + target._impl.flags += {.Removed, .Has_Timeout} + link_timeout(target, target.l.now) +} + +@(private="package") +_open_sync :: proc(l: ^Event_Loop, path: string, dir: Handle, mode: File_Flags, perm: Permissions) -> (handle: Handle, err: FS_Error) { + if path == "" { + err = .Invalid_Argument + return + } + + cpath, cerr := strings.clone_to_cstring(path, l.allocator) + if cerr != nil { + err = .Allocation_Failed + return + } + defer delete(cpath, l.allocator) + + sys_flags := posix.O_Flags{.NOCTTY, .CLOEXEC, .NONBLOCK} + + if .Write in mode { + if .Read in mode { + sys_flags += {.RDWR} + } else { + sys_flags += {.WRONLY} + } + } + + if .Append in mode { sys_flags += {.APPEND} } + if .Create in mode { sys_flags += {.CREAT} } + if .Excl in mode { sys_flags += {.EXCL} } + if .Sync in mode { sys_flags += {.DSYNC} } + if .Trunc in mode { sys_flags += {.TRUNC} } + + handle = posix.openat(dir, cpath, sys_flags, transmute(posix.mode_t)posix._mode_t(transmute(u32)perm)) + if handle < 0 { + err = FS_Error(posix.errno()) + } + + return +} + +@(private="package") +_associate_handle :: proc(handle: uintptr, l: ^Event_Loop) -> (Handle, Association_Error) { + flags_ := posix.fcntl(posix.FD(handle), .GETFL) + if flags_ < 0 { + #partial switch errno := posix.errno(); errno { + case .EBADF: return -1, .Invalid_Handle + case: return -1, Association_Error(errno) + } + } + flags := transmute(posix.O_Flags)(flags_) + + if .NONBLOCK in flags { + return Handle(handle), nil + } + + if posix.fcntl(posix.FD(handle), .SETFL, flags) < 0 { + #partial switch errno := posix.errno(); errno { + case .EBADF: return -1, .Invalid_Handle + case: return -1, Association_Error(errno) + } + } + + return Handle(handle), nil +} + +@(private="package") +_associate_socket :: proc(socket: Any_Socket, l: ^Event_Loop) -> Association_Error { + if err := net.set_blocking(socket, false); err != nil { + switch err { + case .None: unreachable() + case .Network_Unreachable: return .Network_Unreachable + case .Invalid_Argument: return .Invalid_Handle + case .Unknown: fallthrough + case: return Association_Error(net.last_platform_error()) + } + } + + return nil +} + +@(private="package") +_wake_up :: proc(l: ^Event_Loop) { + // TODO: only if we are sleeping (like Windows). + ev := [1]kq.KEvent{ + { + ident = IDENT_WAKE_UP, + filter = .User, + flags = {}, + fflags = { + user = {.Trigger}, + }, + }, + } + t: posix.timespec + n, err := kq.kevent(l.kqueue, ev[:], nil, &t) + assert(err == nil) + assert(n == 0) +} + +@(private="package") +_yield :: proc() { + posix.sched_yield() +} + +// Start file private. + +// Max operations that can be enqueued per tick. +QUEUE_SIZE :: #config(ODIN_NBIO_QUEUE_SIZE, 256) + +FILTER_IGNORE :: kq.Filter(max(kq._Filter_Backing)) + +IDENT_WAKE_UP :: 69 + +Op_Result :: enum { + Done, + Pending, +} + +Operation_Flag :: enum { + Done, + Removed, + Has_Timeout, + For_Kernel, + EOF, + Error, +} +Operation_Flags :: bit_set[Operation_Flag] + +// Operations in the kqueue are uniquely identified using these 2 fields. You may not have more +// than one operation with the same identity submitted. +// So we need to keep track of the operations we have submitted, and if we add another, link it to a previously +// added operation. +Queue_Identifier :: struct { + filter: kq.Filter, + ident: uintptr, +} + +handle_completed :: proc(op: ^Operation) { + debug("handling", op.type) + + result: Op_Result + #partial switch op.type { + case .Accept: + result = accept_exec(op) + case .Dial: + result = dial_exec(op) + case .Send: + if send_exec(op) == .Done { + maybe_callback(op) + bufs_destroy(op.send.bufs, op.l.allocator) + cleanup(op) + } + return + case .Recv: + if recv_exec(op) == .Done { + maybe_callback(op) + bufs_destroy(op.recv.bufs, op.l.allocator) + cleanup(op) + } + return + case .Send_File: + result = sendfile_exec(op) + case .Read: + result = read_exec(op) + case .Write: + result = write_exec(op) + case .Poll: + result = poll_exec(op) + case .Open: + open_exec(op) + case .Close: + close_exec(op) + case .Timeout, .Stat: + // no-op + case: + unimplemented() + } + + if result == .Done { + maybe_callback(op) + cleanup(op) + } + + maybe_callback :: proc(op: ^Operation) { + if .Removed not_in op._impl.flags { + debug("done", op.type, "calling back") + op.cb(op) + } else { + debug("done but removed", op.type) + } + } + + bufs_destroy :: proc(bufs: [][]byte, allocator: mem.Allocator) { + if len(bufs) > 1 { + delete(bufs, allocator) + } + } + + cleanup :: proc(op: ^Operation) { + if .Has_Timeout in op._impl.flags { + remove_link_timeout(op) + } + if !op.detached { + pool.put(&op.l.operation_pool, op) + } + } +} + +@(require_results) +accept_exec :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Accept) + + defer if op.accept.err != nil && op.accept.client > 0 { + posix.close(posix.FD(op.accept.client)) + } + + if op.accept.err != nil || .Done in op._impl.flags { + return .Done + } + + op.accept.client, op.accept.client_endpoint, op.accept.err = net.accept_tcp(op.accept.socket) + if op.accept.err != nil { + if op.accept.err == .Would_Block { + op.accept.err = nil + add_pending(op, .Read, uintptr(op.accept.socket)) + link_timeout(op, op.accept.expires) + return .Pending + } + + return .Done + } + + if err := net.set_blocking(op.accept.client, false); err != nil { + op.accept.err = posix_accept_error() + } + + return .Done +} + +@(require_results) +dial_exec :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Dial) + + defer if op.dial.err != nil && op.dial.socket > 0 { + posix.close(posix.FD(op.dial.socket)) + } + + if op.dial.err != nil || .Done in op._impl.flags { + return .Done + } + + if op.dial.socket > 0 { + // We have already called connect, retrieve potential error number only. + err: posix.Errno + size := posix.socklen_t(size_of(err)) + posix.getsockopt(posix.FD(op.dial.socket), posix.SOL_SOCKET, .ERROR, &err, &size) + if err != nil { + posix.errno(err) + op.dial.err = posix_dial_error() + } + return .Done + } + + if op.dial.endpoint.port == 0 { + op.dial.err = .Port_Required + return .Done + } + + family := family_from_endpoint(op.dial.endpoint) + osocket, socket_err := _create_socket(op.l, family, .TCP) + if socket_err != nil { + op.dial.err = socket_err + return .Done + } + + op.dial.socket = osocket.(TCP_Socket) + + sockaddr := endpoint_to_sockaddr(op.dial.endpoint) + if posix.connect(posix.FD(op.dial.socket), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK { + if posix.errno() == .EINPROGRESS { + add_pending(op, .Write, uintptr(op.dial.socket)) + link_timeout(op, op.dial.expires) + return .Pending + } + + op.dial.err = posix_dial_error() + return .Done + } + + return .Done +} + +@(require_results) +poll_exec :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Poll) + + if .Error in op._impl.flags { + #partial switch posix.Errno(op._impl.result) { + case .EBADF: op.poll.result = .Invalid_Argument + case: op.poll.result = .Error + } + return .Done + } + + if op._impl.result != 0 { + op.poll.result = .Ready + return .Done + } + + if op.poll.result != .Ready { + return .Done + } + + filter: kq.Filter + switch op.poll.event { + case .Receive: filter = .Read + case .Send: filter = .Write + } + + add_pending(op, filter, uintptr(net.any_socket_to_socket(op.poll.socket))) + link_timeout(op, op.poll.expires) + return .Pending +} + +close_exec :: proc(op: ^Operation) { + assert(op.type == .Close) + + if op.close.err != nil || op.close.subject == nil { + return + } + + fd: posix.FD + switch subject in op.close.subject { + case TCP_Socket: fd = posix.FD(subject) + case UDP_Socket: fd = posix.FD(subject) + case Handle: fd = posix.FD(subject) + case: op.close.err = .Invalid_Argument; return + } + + if posix.close(fd) != .OK { + op.close.err = FS_Error(posix.errno()) + } +} + +@(require_results) +send_exec :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Send) + + if op.send.err != nil || .Done in op._impl.flags { + return .Done + } + + total: int + bufs := slice.advance_slices(op.send.bufs, op.send.sent) + bufs, total = constraint_bufs_to_max_rw(op.send.bufs) + + sock, n := sendv(op.send.socket, bufs, op.send.endpoint) + if n < 0 { + if posix.errno() == .EWOULDBLOCK { + if !op.send.all && op.send.sent > 0 { + return .Done + } + + add_pending(op, .Write, uintptr(sock)) + link_timeout(op, op.send.expires) + return .Pending + } + + switch _ in op.send.socket { + case TCP_Socket: op.send.err = posix_tcp_send_error() + case UDP_Socket: op.send.err = posix_udp_send_error() + } + return .Done + } + + op.send.sent += n + + if op.send.sent < total { + return send_exec(op) + } + + return .Done + + sendv :: proc(socket: Any_Socket, bufs: [][]byte, to: net.Endpoint) -> (posix.FD, int) { + assert(len(bufs) < int(max(i32))) + + msg: posix.msghdr + msg.msg_iov = cast([^]posix.iovec)raw_data(bufs) + msg.msg_iovlen = i32(len(bufs)) + + toaddr: posix.sockaddr_storage + fd: posix.FD + switch sock in socket { + case TCP_Socket: + fd = posix.FD(sock) + case UDP_Socket: + fd = posix.FD(sock) + toaddr = endpoint_to_sockaddr(to) + msg.msg_name = &toaddr + msg.msg_namelen = posix.socklen_t(toaddr.ss_len) + } + + return fd, posix.sendmsg(fd, &msg, {.NOSIGNAL}) + } +} + +@(require_results) +recv_exec :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Recv) + + if op.recv.err != nil || .Done in op._impl.flags { + return .Done + } + + total: int + bufs := slice.advance_slices(op.recv.bufs, op.recv.received) + bufs, total = constraint_bufs_to_max_rw(op.recv.bufs) + + _, is_tcp := op.recv.socket.(net.TCP_Socket) + + sock, n := recvv(op.recv.socket, bufs, &op.recv.source) + if n < 0 { + if posix.errno() == .EWOULDBLOCK { + if is_tcp && !op.recv.all && op.recv.received > 0 { + return .Done + } + + add_pending(op, .Read, uintptr(sock)) + link_timeout(op, op.recv.expires) + return .Pending + } + + if is_tcp { + op.recv.err = posix_tcp_recv_error() + } else { + op.recv.err = posix_udp_recv_error() + } + + return .Done + } + + assert(is_tcp || op.recv.received == 0) + op.recv.received += n + + if is_tcp && n != 0 && op.recv.received < total { + return recv_exec(op) + } + + return .Done + + recvv :: proc(socket: Any_Socket, bufs: [][]byte, from: ^Endpoint) -> (fd: posix.FD, n: int) { + assert(len(bufs) < int(max(i32))) + + msg: posix.msghdr + msg.msg_iov = cast([^]posix.iovec)raw_data(bufs) + msg.msg_iovlen = i32(len(bufs)) + + udp: bool + fromaddr: posix.sockaddr_storage + switch sock in socket { + case TCP_Socket: + fd = posix.FD(sock) + case UDP_Socket: + fd = posix.FD(sock) + udp = true + msg.msg_name = &fromaddr + msg.msg_namelen = posix.socklen_t(size_of(fromaddr)) + } + + n = posix.recvmsg(fd, &msg, {.NOSIGNAL}) + if n >= 0 && udp { + from^ = sockaddr_to_endpoint(&fromaddr) + } + + return + } +} + +@(require_results) +sendfile_exec :: proc(op: ^Operation) -> (result: Op_Result) { + assert(op.type == .Send_File) + + defer if result == .Done && op.sendfile._impl.mapping != nil { + posix.munmap(raw_data(op.sendfile._impl.mapping), len(op.sendfile._impl.mapping)) + } + + if op.sendfile.err != nil || .Done in op._impl.flags { + return .Done + } + + when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + // Doesn't have `sendfile`, emulate it with `mmap` + normal `send`. + return sendfile_exec_emulated(op) + } else { + return sendfile_exec_native(op) + + @(require_results) + sendfile_exec_native :: proc(op: ^Operation) -> Op_Result { + nbytes := op.sendfile.nbytes + assert(nbytes != 0) + if nbytes == SEND_ENTIRE_FILE { + nbytes = 0 // special value for entire file. + + // If we want progress updates we need nbytes to be the actual size, or the user + // won't be able to check `sent < nbytes` to know if it's the final callback. + if op.sendfile.progress_updates { + stat: posix.stat_t + if posix.fstat(op.sendfile.file, &stat) != .OK { + op.sendfile.err = FS_Error(posix.errno()) + return .Done + } + op.sendfile.nbytes = int(stat.st_size - posix.off_t(op.sendfile.offset)) + } + } else { + nbytes -= op.sendfile.sent + } + + n, ok := posix_sendfile(op.sendfile.file, op.sendfile.socket, op.sendfile.offset + op.sendfile.sent, nbytes) + + assert(n >= 0) + op.sendfile.sent += n + + if !ok { + op.sendfile.err = posix_tcp_send_error() + if op.sendfile.err == .Would_Block { + op.sendfile.err = nil + if op.sendfile.progress_updates { op.cb(op) } + add_pending(op, .Write, uintptr(op.sendfile.socket)) + link_timeout(op, op.sendfile.expires) + return .Pending + } + + return .Done + } + + assert(op.sendfile.nbytes == SEND_ENTIRE_FILE || op.sendfile.sent == op.sendfile.nbytes) + return .Done + } + } + + @(require_results) + sendfile_exec_emulated :: proc(op: ^Operation) -> Op_Result { + if op.sendfile.nbytes == SEND_ENTIRE_FILE { + stat: posix.stat_t + if posix.fstat(op.sendfile.file, &stat) != .OK { + op.sendfile.err = FS_Error(posix.errno()) + return .Done + } + op.sendfile.nbytes = int(stat.st_size - posix.off_t(op.sendfile.offset)) + } + + if op.sendfile._impl.mapping == nil { + addr := posix.mmap(nil, uint(op.sendfile.nbytes), {.READ}, {}, op.sendfile.file, posix.off_t(op.sendfile.offset)) + if addr == posix.MAP_FAILED { + op.sendfile.err = FS_Error(posix.errno()) + return .Done + } + op.sendfile._impl.mapping = ([^]byte)(addr)[:op.sendfile.nbytes] + } + + n := posix.send( + posix.FD(op.sendfile.socket), + raw_data(op.sendfile._impl.mapping)[op.sendfile.sent:], + uint(min(MAX_RW, op.sendfile.nbytes - op.sendfile.sent)), + {.NOSIGNAL}, + ) + if n < 0 { + op.sendfile.err = posix_tcp_send_error() + if op.sendfile.err == .Would_Block { + op.sendfile.err = nil + add_pending(op, .Write, uintptr(op.sendfile.socket)) + link_timeout(op, op.sendfile.expires) + return .Pending + } + + return .Done + } + + op.sendfile.sent += n + + if op.sendfile.sent < op.sendfile.nbytes { + if op.sendfile.progress_updates { op.cb(op) } + return sendfile_exec_emulated(op) + } + + return .Done + } +} + +@(require_results) +read_exec :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Read) + + if op.read.err != nil || .Done in op._impl.flags { + return .Done + } + + to_read := op.read.buf[op.read.read:] + to_read = to_read[:min(MAX_RW, len(to_read))] + + res := posix.pread(op.read.handle, raw_data(to_read), len(to_read), posix.off_t(op.read.offset) + posix.off_t(op.read.read)) + if res < 0 { + errno := posix.errno() + if errno == .EWOULDBLOCK { + if !op.read.all && op.read.read > 0 { + return .Done + } + + add_pending(op, .Read, uintptr(op.read.handle)) + link_timeout(op, op.read.expires) + return .Pending + } + + op.read.err = FS_Error(errno) + return .Done + } else if res == 0 { + if op.read.read == 0 { + op.read.err = .EOF + } + return .Done + } + + op.read.read += res + + if op.read.read < len(op.read.buf) { + return read_exec(op) + } + + return .Done +} + +@(require_results) +write_exec :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Write) + + if op.write.err != nil || .Done in op._impl.flags { + return .Done + } + + to_write := op.write.buf[op.write.written:] + to_write = to_write[:min(MAX_RW, len(to_write))] + + res := posix.pwrite(op.write.handle, raw_data(to_write), len(to_write), posix.off_t(op.write.offset) + posix.off_t(op.write.written)) + if res < 0 { + errno := posix.errno() + if errno == .EWOULDBLOCK { + if !op.write.all && op.write.written > 0 { + return .Done + } + + add_pending(op, .Write, uintptr(op.write.handle)) + link_timeout(op, op.write.expires) + return .Pending + } + + op.write.err = FS_Error(errno) + return .Done + } + + op.write.written += res + + if op.write.written < len(op.write.buf) { + return write_exec(op) + } + + return .Done +} + +timeout_exec :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Timeout) + + if op.timeout.duration <= 0 { + return .Done + } + + op.l.submitted[Queue_Identifier{ ident = uintptr(op), filter = .Timer }] = op + + op._impl.flags += {.For_Kernel} + + append_pending(op.l, kq.KEvent { + ident = uintptr(op), + filter = .Timer, + flags = {.Add, .Enable, .One_Shot}, + fflags = { + timer = kq.TIMER_FLAGS_NSECONDS + {.Absolute}, + }, + data = op.l.now._nsec + i64(op.timeout.duration), + udata = op, + }) + return .Pending +} + +open_exec :: proc(op: ^Operation) { + assert(op.type == .Open) + + if op.open.err != nil && op.open.handle > 0 { + posix.close(op.open.handle) + return + } + + if .Done in op._impl.flags { + return + } + + op.open.handle, op.open.err = _open_sync(op.l, op.open.path, op.open.dir, op.open.mode, op.open.perm) +} + +stat_exec :: proc(op: ^Operation) { + assert(op.type == .Stat) + + stat: posix.stat_t + if posix.fstat(op.stat.handle, &stat) != .OK { + op.stat.err = FS_Error(posix.errno()) + return + } + + op.stat.type = .Undetermined + switch { + case posix.S_ISBLK(stat.st_mode) || posix.S_ISCHR(stat.st_mode): + op.stat.type = .Device + case posix.S_ISDIR(stat.st_mode): + op.stat.type = .Directory + case posix.S_ISFIFO(stat.st_mode) || posix.S_ISSOCK(stat.st_mode): + op.stat.type = .Pipe_Or_Socket + case posix.S_ISLNK(stat.st_mode): + op.stat.type = .Symlink + case posix.S_ISREG(stat.st_mode): + op.stat.type = .Regular + } + + op.stat.size = i64(stat.st_size) +} + +add_pending :: proc(op: ^Operation, filter: kq.Filter, ident: uintptr) { + debug("adding pending", op.type) + op._impl.flags += {.For_Kernel} + + _, val, just_inserted, err := map_entry(&op.l.submitted, Queue_Identifier{ ident = ident, filter = filter }) + ensure(err == nil, "allocation failure") + if just_inserted { + val^ = op + + append_pending(op.l, kq.KEvent { + filter = filter, + ident = ident, + flags = {.Add, .Enable, .One_Shot}, + udata = op, + }) + } else { + debug("already have this operation on the kqueue, bundling it") + + last := val^ + for last._impl.next != nil { + last = last._impl.next + } + last._impl.next = op + op._impl.prev = last + } +} + +append_pending :: #force_inline proc(l: ^Event_Loop, ev: kq.KEvent) { + if !sa.append(&l.pending, ev) { + warn("queue is full, adding to overflow, should QUEUE_SIZE be increased?") + _, err := queue.append(&l.overflow, ev) + ensure(err == nil, "allocation failure") + } +} + +link_timeout :: proc(op: ^Operation, expires: time.Time) { + if expires == {} { + return + } + + debug(op.type, "times out at", expires) + + op._impl.flags += {.Has_Timeout} + + append_pending(op.l, kq.KEvent { + ident = uintptr(op), + filter = .Timer, + flags = {.Add, .Enable, .One_Shot}, + fflags = { + timer = kq.TIMER_FLAGS_NSECONDS + {.Absolute}, + }, + data = expires._nsec, + udata = op, + }) +} + +remove_link_timeout :: proc(op: ^Operation) { + debug("removing timeout of", op.type) + assert(.Has_Timeout in op._impl.flags) + + append_pending(op.l, kq.KEvent { + ident = uintptr(op), + filter = .Timer, + flags = {.Delete, .Disable, .One_Shot}, + }) +} + +timeout_and_delete :: proc(target: ^Operation) { + filter: kq.Filter + ident: uintptr + switch target.type { + case .Accept: + target.accept.err = .Timeout + filter = .Read + ident = uintptr(target.accept.socket) + case .Dial: + target.dial.err = Dial_Error.Timeout + filter = .Write + ident = uintptr(target.dial.socket) + case .Read: + target.read.err = .Timeout + filter = .Read + ident = uintptr(target.read.handle) + case .Write: + target.write.err = .Timeout + filter = .Write + ident = uintptr(target.write.handle) + case .Recv: + switch sock in target.recv.socket { + case TCP_Socket: + target.recv.err = TCP_Recv_Error.Timeout + ident = uintptr(sock) + case UDP_Socket: + target.recv.err = UDP_Recv_Error.Timeout + ident = uintptr(sock) + } + filter = .Read + case .Send: + switch sock in target.send.socket { + case TCP_Socket: + target.send.err = TCP_Send_Error.Timeout + ident = uintptr(sock) + case UDP_Socket: + target.send.err = UDP_Send_Error.Timeout + ident = uintptr(sock) + } + filter = .Write + case .Send_File: + target.send.err = TCP_Send_Error.Timeout + filter = .Write + ident = uintptr(target.sendfile.socket) + case .Poll: + target.poll.result = .Timeout + ident = uintptr(net.any_socket_to_socket(target.poll.socket)) + + switch target.poll.event { + case .Receive: filter = .Read + case .Send: filter = .Write + } + + case .Timeout: + ident = uintptr(target) + filter = .Timer + + case .Close: + target.close.err = .Timeout + return + + case .Open: + target.open.err = .Timeout + return + + case .Stat: + target.stat.err = .Timeout + return + + case .None, ._Link_Timeout, ._Remove, ._Splice: + return + } + + // If there are other ops linked to this kevent, don't remove it. + if target._impl.next != nil || target._impl.prev != nil { + debug("removing target by pulling it out of the linked list, other ops depend on the kevent") + assert(filter != .Timer) + + if target._impl.next != nil { + target._impl.next._impl.prev = target._impl.prev + } + + if target._impl.prev != nil { + target._impl.prev._impl.next = target._impl.next + } else { + debug("target was the head of the list, updating map to point at new head") + + _, vp, _, err := map_entry(&target.l.submitted, Queue_Identifier{ ident = ident, filter = filter }) + ensure(err == nil, "allocation failure") + assert(vp^ == target) + vp^ = target._impl.next + + ev := kq.KEvent{ + filter = filter, + ident = ident, + flags = {.Add, .Enable, .One_Shot}, + udata = target._impl.next, + } + if !sa.append(&target.l.pending, ev) { + warn("just removed the head operation of a list of multiple, and the queue is full, have to force this update through inefficiently") + // This has to happen the next time we submit or we could have udata pointing wrong. + // Very inefficient but probably never hit. + + // Makes kevent return a receipt for our addition, so we don't take any new events from it. + // This forces .Error to be added and data being 0 means it's added. + ev.flags += {.Receipt} + + timeout: posix.timespec + n, err := kq.kevent(target.l.kqueue, ([^]kq.KEvent)(&ev)[:1], ([^]kq.KEvent)(&ev)[:1], &timeout) + assert(n == 1) + assert(err == nil) + + // The receipt flag makes this occur on the event. + assert(.Error in ev.flags) + assert(ev.data == 0) + } + } + + } else if .For_Kernel in target._impl.flags { + debug("adding delete event") + + _, dval := delete_key(&target.l.submitted, Queue_Identifier{ ident = ident, filter = filter }) + assert(dval != nil) + + append_pending(target.l, kq.KEvent{ + ident = ident, + filter = filter, + flags = {.Delete, .Disable, .One_Shot}, + }) + } else { + debug("remove without delete event, because target is not in kernel") + } +} + +@(require_results) +endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: posix.sockaddr_storage) { + switch a in ep.address { + case IP4_Address: + (^posix.sockaddr_in)(&sockaddr)^ = posix.sockaddr_in { + sin_port = u16be(ep.port), + sin_addr = transmute(posix.in_addr)a, + sin_family = .INET, + sin_len = size_of(posix.sockaddr_in), + } + return + case IP6_Address: + (^posix.sockaddr_in6)(&sockaddr)^ = posix.sockaddr_in6 { + sin6_port = u16be(ep.port), + sin6_addr = transmute(posix.in6_addr)a, + sin6_family = .INET6, + sin6_len = size_of(posix.sockaddr_in6), + } + return + } + unreachable() +} + +@(require_results) +sockaddr_to_endpoint :: proc(native_addr: ^posix.sockaddr_storage) -> (ep: Endpoint) { + #partial switch native_addr.ss_family { + case .INET: + addr := cast(^posix.sockaddr_in)native_addr + port := int(addr.sin_port) + ep = Endpoint { + address = IP4_Address(transmute([4]byte)addr.sin_addr), + port = port, + } + case .INET6: + addr := cast(^posix.sockaddr_in6)native_addr + port := int(addr.sin6_port) + ep = Endpoint { + address = IP6_Address(transmute([8]u16be)addr.sin6_addr), + port = port, + } + case: + panic("native_addr is neither IP4 or IP6 address") + } + return +} diff --git a/core/nbio/impl_posix_darwin.odin b/core/nbio/impl_posix_darwin.odin new file mode 100644 index 000000000..7ec64c2df --- /dev/null +++ b/core/nbio/impl_posix_darwin.odin @@ -0,0 +1,29 @@ +#+private +package nbio + +import "core:net" +import "core:sys/posix" + +foreign import lib "system:System" + +posix_sendfile :: proc(fd: Handle, s: TCP_Socket, offset, nbytes: int) -> (sent: int, ok := true) { + foreign lib { + @(link_name="sendfile") + _posix_sendfile :: proc (fd, s: posix.FD, offset: posix.off_t, len: ^posix.off_t, hdtr: rawptr, flags: i32) -> posix.result --- + } + + len := posix.off_t(nbytes) + if _posix_sendfile(posix.FD(fd), posix.FD(s), posix.off_t(offset), &len, nil, 0) != .OK { + ok = false + } + sent = int(len) + return +} + +posix_listen_error :: net._listen_error +posix_accept_error :: net._accept_error +posix_dial_error :: net._dial_error +posix_tcp_send_error :: net._tcp_send_error +posix_udp_send_error :: net._udp_send_error +posix_tcp_recv_error :: net._tcp_recv_error +posix_udp_recv_error :: net._udp_recv_error diff --git a/core/nbio/impl_posix_freebsd.odin b/core/nbio/impl_posix_freebsd.odin new file mode 100644 index 000000000..50089510b --- /dev/null +++ b/core/nbio/impl_posix_freebsd.odin @@ -0,0 +1,52 @@ +#+private +package nbio + +import "core:net" +import "core:sys/posix" +import "core:sys/freebsd" + +foreign import lib "system:c" + +// TODO: rewrite freebsd implementation to use `sys/freebsd` instead of `sys/posix`. + +posix_sendfile :: proc(fd: Handle, s: TCP_Socket, offset, nbytes: int) -> (sent: int, ok := true) { + foreign lib { + @(link_name="sendfile") + _posix_sendfile :: proc (fd, s: posix.FD, offset: posix.off_t, nbytes: uint, hdtr: rawptr, sbytes: ^posix.off_t, flags: i32) -> posix.result --- + } + + len: posix.off_t + if _posix_sendfile(posix.FD(fd), posix.FD(s), posix.off_t(offset), uint(nbytes), nil, &len, 0) != .OK { + ok = false + } + sent = int(len) + return +} + +posix_listen_error :: proc() -> Listen_Error { + return net._listen_error(freebsd.Errno(posix.errno())) +} + +posix_accept_error :: proc() -> Accept_Error { + return net._accept_error(freebsd.Errno(posix.errno())) +} + +posix_dial_error :: proc() -> Dial_Error { + return net._dial_error(freebsd.Errno(posix.errno())) +} + +posix_tcp_send_error :: proc() -> TCP_Send_Error { + return net._tcp_send_error(freebsd.Errno(posix.errno())) +} + +posix_udp_send_error :: proc() -> UDP_Send_Error { + return net._udp_send_error(freebsd.Errno(posix.errno())) +} + +posix_tcp_recv_error :: proc() -> TCP_Recv_Error { + return net._tcp_recv_error(freebsd.Errno(posix.errno())) +} + +posix_udp_recv_error :: proc() -> UDP_Recv_Error { + return net._udp_recv_error(freebsd.Errno(posix.errno())) +} diff --git a/core/nbio/impl_posix_netbsd.odin b/core/nbio/impl_posix_netbsd.odin new file mode 100644 index 000000000..a0e2652f4 --- /dev/null +++ b/core/nbio/impl_posix_netbsd.odin @@ -0,0 +1,12 @@ +#+private +package nbio + +import "core:net" + +posix_listen_error :: net._listen_error +posix_accept_error :: net._accept_error +posix_dial_error :: net._dial_error +posix_tcp_send_error :: net._tcp_send_error +posix_udp_send_error :: net._udp_send_error +posix_tcp_recv_error :: net._tcp_recv_error +posix_udp_recv_error :: net._udp_recv_error diff --git a/core/nbio/impl_posix_openbsd.odin b/core/nbio/impl_posix_openbsd.odin new file mode 100644 index 000000000..a0e2652f4 --- /dev/null +++ b/core/nbio/impl_posix_openbsd.odin @@ -0,0 +1,12 @@ +#+private +package nbio + +import "core:net" + +posix_listen_error :: net._listen_error +posix_accept_error :: net._accept_error +posix_dial_error :: net._dial_error +posix_tcp_send_error :: net._tcp_send_error +posix_udp_send_error :: net._udp_send_error +posix_tcp_recv_error :: net._tcp_recv_error +posix_udp_recv_error :: net._udp_recv_error diff --git a/core/nbio/impl_windows.odin b/core/nbio/impl_windows.odin new file mode 100644 index 000000000..f75193fe4 --- /dev/null +++ b/core/nbio/impl_windows.odin @@ -0,0 +1,1856 @@ +#+private file +package nbio + +import "base:intrinsics" + +import "core:container/avl" +import "core:container/pool" +import "core:container/queue" +import "core:mem" +import "core:net" +import "core:path/filepath" +import "core:slice" +import "core:strings" +import "core:sync" +import "core:time" + +import win "core:sys/windows" + +@(private="package") +_FULLY_SUPPORTED :: true + +@(private="package") +_Event_Loop :: struct { + timeouts: avl.Tree(^Operation), + thread: win.HANDLE, + completed: queue.Queue(^Operation), + completed_oob: Multi_Producer_Single_Consumer, + state: enum { + Working, + Waking, + Sleeping, + }, +} + +@(private="package") +_Handle :: distinct uintptr + +@(private="package") +_CWD :: ~_Handle(99) + +@(private="package") +MAX_RW :: mem.Gigabyte + +@(private="package") +_Operation :: struct { + over: win.OVERLAPPED, + timeout: ^Operation, +} + +@(private="package") +_Accept :: struct { + // Space that gets the local and remote address written into it. + addrs: [(size_of(win.sockaddr_in6)+16)*2]byte, +} + +@(private="package") +_Close :: struct {} + +@(private="package") +_Dial :: struct { + addr: win.SOCKADDR_STORAGE_LH, +} + +@(private="package") +_Read :: struct {} + +@(private="package") +_Write :: struct {} + +@(private="package") +_Send :: struct { + small_bufs: [1][]byte, +} + +@(private="package") +_Recv :: struct { + source: win.SOCKADDR_STORAGE_LH, + source_len: win.INT, + small_bufs: [1][]byte, + flags: win.DWORD, +} + +@(private="package") +_Timeout :: struct { + expires: time.Time, + target: ^Operation, +} + +@(private="package") +_Poll :: struct { + wait_handle: win.HANDLE, +} + +@(private="package") +_Send_File :: struct {} + +@(private="package") +_Remove :: struct {} + +@(private="package") +_Link_Timeout :: struct {} + +@(private="package") +_Splice :: struct {} + +@(private="package") +_Open :: struct {} + +@(private="package") +_Stat :: struct {} + +@(private="package") +_init :: proc(l: ^Event_Loop, alloc: mem.Allocator) -> (err: General_Error) { + l.allocator = alloc + + l.completed.data.allocator = l.allocator + + avl.init(&l.timeouts, timeouts_cmp, alloc) + + mpsc_init(&l.completed_oob, QUEUE_SIZE, l.allocator) + defer if err != nil { mpsc_destroy(&l.completed_oob, l.allocator) } + + dup_ok := win.DuplicateHandle( + win.GetCurrentProcess(), win.GetCurrentThread(), + win.GetCurrentProcess(), &l.thread, + 0, false, win.DUPLICATE_SAME_ACCESS, + ) + ensure(dup_ok == true) + defer if err != nil { win.CloseHandle(l.thread) } + + err = g_ref() + return +} + +@(private="package") +_destroy :: proc(l: ^Event_Loop) { + avl.destroy(&l.timeouts) + queue.destroy(&l.completed) + mpsc_destroy(&l.completed_oob, l.allocator) + win.CloseHandle(l.thread) + g_unref() +} + +@(private="package") +__tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> (err: General_Error) { + debug("tick") + + l.now = time.now() + next_timeout := check_timeouts(l) + + // Prevent infinite loop when callback adds to completed by storing length. + n := queue.len(l.completed) + if n > 0 { + for _ in 0 ..< n { + op := queue.pop_front(&l.completed) + handle_completed(op) + } + } + + for { + op := (^Operation)(mpsc_dequeue(&l.completed_oob)) + if op == nil { break } + handle_completed(op) + } + + if pool.num_outstanding(&l.operation_pool) == 0 { return nil } + + actual_timeout := win.INFINITE + if queue.len(l.completed) > 0 || mpsc_count(&l.completed_oob) > 0 { + actual_timeout = 0 + } else if timeout >= 0 { + actual_timeout = win.DWORD(timeout / time.Millisecond) + } + if nt, ok := next_timeout.?; ok { + actual_timeout = min(actual_timeout, win.DWORD(nt / time.Millisecond)) + } + + if actual_timeout > 0 { + sync.atomic_store_explicit(&l.state, .Sleeping, .Release) + + // There could be a race condition where we go sleeping at the same time as things get queued + // and a wakeup isn't done because the state is not .Sleeping yet. + // So after sleeping we first check our queues. + + for { + op := (^Operation)(mpsc_dequeue(&l.queue)) + if op == nil { break } + _exec(op) + } + + for { + op := (^Operation)(mpsc_dequeue(&l.completed_oob)) + if op == nil { break } + handle_completed(op) + } + } + + for { + events: [256]win.OVERLAPPED_ENTRY + entries_removed: win.ULONG + if !win.GetQueuedCompletionStatusEx(g.iocp, &events[0], len(events), &entries_removed, actual_timeout, true) { + winerr := win.GetLastError() + switch winerr { + case win.WAIT_TIMEOUT, win.WAIT_IO_COMPLETION: + entries_removed = 0 + case: + err = General_Error(winerr) + return + } + } + + sync.atomic_store_explicit(&l.state, .Working, .Relaxed) + + if actual_timeout > 0 { + // We may have just waited some time, lets update the current time. + l.now = time.now() + } + + if entries_removed > 0 { + debug(int(entries_removed), "operations were completed") + } + + for event in events[:entries_removed] { + assert(event.lpOverlapped != nil) + op := container_of(container_of(event.lpOverlapped, _Operation, "over"), Operation, "_impl") + + if op.l == l { + handle_completed(op) + } else { + op_l := op.l + for !mpsc_enqueue(&op.l.completed_oob, op) { + warn("oob queue filled up, QUEUE_SIZE may need increasing") + _wake_up(op_l) + win.SwitchToThread() + } + _wake_up(op_l) + } + } + + if entries_removed < len(events) { + break + } + + // `events` was filled up, get more. + debug("GetQueuedCompletionStatusEx filled entire events buffer, getting more") + actual_timeout = 0 + } + + return nil + + check_timeouts :: proc(l: ^Event_Loop) -> (expires: Maybe(time.Duration)) { + curr := l.now + + if avl.len(&l.timeouts) == 0 { + return + } + + debug(avl.len(&l.timeouts), "timeouts", "threshold", curr) + + iter := avl.iterator(&l.timeouts, .Forward) + for node in avl.iterator_next(&iter) { + op := node.value + cexpires := time.diff(curr, op.timeout._impl.expires) + + debug("expires after", cexpires) + + removed := op._impl.timeout == (^Operation)(REMOVED) + done := cexpires <= 0 + if removed { + debug("timeout removed!") + } else if done { + debug("timeout done!") + handle_completed(op) + } + if removed || done { + avl.remove_node(&l.timeouts, node) + continue + } + + break + } + + // Don't merge this with the previous iteration because the `handle_completed` in that one might queue + // more timeouts which we want to detect here. + // For example: `timeout(time.Second, proc(_: ^Operation) { timeout(time.Second, ...) })` + + first := avl.first(&l.timeouts) + if first != nil { + op := first.value + cexpires := time.diff(curr, op.timeout._impl.expires) + debug("first timeout in the future is at", op.timeout._impl.expires, "after", cexpires) + expires = cexpires + } + return + } + + handle_completed :: proc(op: ^Operation) { + debug("handling", op.type) + + if op._impl.timeout == (^Operation)(REMOVED) { + debug(op.type, "was removed") + + // Set an error, and call the internal callback. + // This way resources are cleaned up properly, for example the result socket for dial. + // If we just do nothing it will be leaked. + + if op._impl.over.Internal == nil { + // There is no error from the kernel, set one ourselves. + // This needs to be an NTSTATUS code, not a win32 error number. + STATUS_REQUEST_ABORTED :: 0xC023000C + op._impl.over.Internal = (^win.c_ulong)(uintptr(STATUS_REQUEST_ABORTED)) + } + } + + result := Op_Result.Done + switch op.type { + case .Read: + result = read_callback(op) + case .Recv: + result = recv_callback(op) + if result == .Done { + maybe_callback(op) + if len(op.recv.bufs) > 1 { + delete(op.recv.bufs, op.l.allocator) + } + cleanup(op) + return + } + case .Write: + result = write_callback(op) + case .Send: + result = send_callback(op) + if result == .Done { + maybe_callback(op) + if len(op.send.bufs) > 1 { + delete(op.send.bufs, op.l.allocator) + } + cleanup(op) + return + } + case .Send_File: + result = sendfile_callback(op) + case .Accept: + accept_callback(op) + case .Dial: + dial_callback(op) + case .Poll: + poll_callback(op) + case .Timeout, .Open, .Stat, .Close: + // no-op. + case .None, ._Link_Timeout, ._Remove, ._Splice: + fallthrough + case: + unreachable() + } + + if result == .Pending { + assert(op._impl.timeout != (^Operation)(REMOVED)) + debug(op.type, "pending") + return + } + + maybe_callback(op) + cleanup(op) + + maybe_callback :: proc(op: ^Operation) { + if op._impl.timeout == (^Operation)(REMOVED) { + debug(op.type, "done but removed") + } else { + debug(op.type, "done") + op.cb(op) + + if op._impl.timeout != nil { + debug("cancelling timeout of", op.type) + op._impl.timeout.timeout._impl.target = nil + _remove(op._impl.timeout) + } + } + } + + cleanup :: proc(op: ^Operation) { + if !op.detached { + pool.put(&op.l.operation_pool, op) + } + } + } + +} + +@(private="package") +_exec :: proc(op: ^Operation) { + assert(op.l == &_tls_event_loop) + + result: Op_Result + switch op.type { + case .Accept: result = accept_exec(op) + case .Close: close_exec(op); result = .Done + case .Dial: result = dial_exec(op) + case .Recv: result = recv_exec(op) + case .Send: result = send_exec(op) + case .Send_File: result = sendfile_exec(op) + case .Read: result = read_exec(op) + case .Write: result = write_exec(op) + case .Timeout: result = timeout_exec(op) + case .Poll: result = poll_exec(op) + case .Open: open_exec(op); result = .Done + case .Stat: stat_exec(op); result = .Done + case .None, ._Link_Timeout, ._Remove, ._Splice: + unreachable() + } + + switch result { + case .Pending: + // no-op, in kernel. + debug("exec", op.type, "pending") + case .Done: + debug("exec", op.type, "done immediately") + _, err := queue.append(&op.l.completed, op) // Got result, handle it next tick. + ensure(err == nil, "allocation failure") + } +} + +@(private="package") +_open_sync :: proc(l: ^Event_Loop, name: string, dir: Handle, mode: File_Flags, perm: Permissions) -> (handle: Handle, err: FS_Error) { + if len(name) == 0 { + err = .Invalid_Argument + return + } + + dir := dir + + is_abs := filepath.is_abs(name) + is_cwd: bool + cwd_path: win.wstring + if !is_abs && dir == CWD { + is_cwd = true + + cwd_len := win.GetCurrentDirectoryW(0, nil) + assert(cwd_len > 0) + cwd_buf, cwd_err := make([]u16, cwd_len, l.allocator) + if cwd_err != nil { return INVALID_HANDLE, .Allocation_Failed } + cwd_len = win.GetCurrentDirectoryW(cwd_len, raw_data(cwd_buf)) + assert(int(cwd_len) == len(cwd_buf)-1) + cwd_path = win.wstring(raw_data(cwd_buf)) + + dir = Handle(win.CreateFileW( + cwd_path, + win.GENERIC_READ, + win.FILE_SHARE_READ|win.FILE_SHARE_WRITE, + nil, + win.OPEN_EXISTING, + win.FILE_FLAG_BACKUP_SEMANTICS, + nil, + )) + if dir == INVALID_HANDLE { + err = FS_Error(win.GetLastError()) + return + } + } + defer if is_cwd { + delete(cwd_path, l.allocator) + win.CloseHandle(win.HANDLE(dir)) + } + + path, was_alloc := _normalize_path(name, l.allocator) + defer if was_alloc { delete(path, l.allocator) } + + wpath := win.utf8_to_utf16(path, l.allocator) + defer delete(wpath, l.allocator) + + if path == "" || wpath == nil { + return INVALID_HANDLE, .Allocation_Failed + } + + path_len := len(wpath) * 2 + if path_len > int(max(u16)) { + err = .Invalid_Argument + return + } + + access: u32 + switch mode & {.Read, .Write} { + case {.Read}: access = win.FILE_GENERIC_READ + case {.Write}: access = win.FILE_GENERIC_WRITE + case {.Read, .Write}: access = win.FILE_GENERIC_READ | win.FILE_GENERIC_WRITE + } + + if .Create in mode { + access |= win.FILE_GENERIC_WRITE + } + if .Append in mode { + access &~= win.FILE_GENERIC_WRITE + access |= win.FILE_APPEND_DATA + } + share_mode := u32(win.FILE_SHARE_READ | win.FILE_SHARE_WRITE) + + create_mode: u32 = win.FILE_OPEN + switch { + case mode & {.Create, .Excl} == {.Create, .Excl}: + create_mode = win.FILE_CREATE + case mode & {.Create, .Trunc} == {.Create, .Trunc}: + create_mode = win.FILE_OVERWRITE_IF + case mode & {.Create} == {.Create}: + create_mode = win.FILE_OPEN_IF + case mode & {.Trunc} == {.Trunc}: + create_mode = win.FILE_OVERWRITE + } + + attrs: u32 = win.FILE_ATTRIBUTE_NORMAL + + if .Write_User not_in perm { + attrs = win.FILE_ATTRIBUTE_READONLY + if create_mode == win.FILE_OVERWRITE_IF { + // NOTE(bill): Open has just asked to create a file in read-only mode. + // If the file already exists, to make it akin to a *nix open call, + // the call preserves the existing permissions. + + h: win.HANDLE + io_status: win.IO_STATUS_BLOCK + status := win.NtCreateFile( + &h, + access, + &{ + Length = size_of(win.OBJECT_ATTRIBUTES), + RootDirectory = is_abs ? nil : win.HANDLE(dir), + ObjectName = &{ + Length = u16(path_len), + MaximumLength = u16(path_len), + Buffer = raw_data(wpath), + }, + }, + &io_status, + nil, + win.FILE_ATTRIBUTE_NORMAL, + share_mode, + win.FILE_OVERWRITE, + 0, + nil, + 0, + ) + syserr := win.System_Error(win.RtlNtStatusToDosError(status)) + #partial switch syserr { + case .FILE_NOT_FOUND, .BAD_NETPATH, .PATH_NOT_FOUND: + // File does not exists, create the file + case .SUCCESS: + association_err: Association_Error + handle, association_err = _associate_handle(uintptr(h), l) + // This shouldn't fail, we just created this file, with correct flags. + assert(association_err != nil) + return + case: + err = FS_Error(syserr) + return + } + } + } + + h: win.HANDLE + io_status: win.IO_STATUS_BLOCK + status := win.NtCreateFile( + &h, + access, + &{ + Length = size_of(win.OBJECT_ATTRIBUTES), + RootDirectory = is_abs ? nil : win.HANDLE(dir), + ObjectName = &{ + Length = u16(path_len), + MaximumLength = u16(path_len), + Buffer = raw_data(wpath), + }, + }, + &io_status, + nil, + attrs, + share_mode, + create_mode, + 0, + nil, + 0, + ) + syserr := win.System_Error(win.RtlNtStatusToDosError(status)) + #partial switch syserr { + case .SUCCESS: + association_err: Association_Error + handle, association_err = _associate_handle(uintptr(h), l) + // This shouldn't fail, we just created this file, with correct flags. + assert(association_err == nil) + return + case: + err = FS_Error(syserr) + return + } + + @(require_results) + _normalize_path :: proc(path: string, allocator := context.allocator) -> (fixed: string, allocated: bool) { + // An UNC path or relative, just replace slashes. + if strings.has_prefix(path, `\\`) || !filepath.is_abs(path) { + return strings.replace_all(path, `/`, `\`) + } + + path_buf, err := make([]byte, len(PREFIX)+len(path)+1, allocator) + if err != nil { return } + defer if !allocated { delete(path_buf, allocator) } + + PREFIX :: `\??` + copy(path_buf, PREFIX) + n := len(path) + r, w := 0, len(PREFIX) + for r < n { + switch { + case filepath.is_separator(path[r]): + r += 1 + case path[r] == '.' && (r+1 == n || filepath.is_separator(path[r+1])): + // \.\ + r += 1 + case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || filepath.is_separator(path[r+2])): + // Skip \..\ paths + return path, false + case: + path_buf[w] = '\\' + w += 1 + for r < n && !filepath.is_separator(path[r]) { + path_buf[w] = path[r] + r += 1 + w += 1 + } + } + } + + // Root directories require a trailing \ + if w == len(`\\?\c:`) { + path_buf[w] = '\\' + w += 1 + } + + allocated = true + fixed = string(path_buf[:w]) + return + } +} + +@(private="package") +_listen :: proc(socket: TCP_Socket, backlog := 1000) -> (err: Listen_Error) { + if res := win.listen(win.SOCKET(socket), i32(backlog)); res == win.SOCKET_ERROR { + err = net._listen_error() + } + return +} + +@(private="package") +_create_socket :: proc( + l: ^Event_Loop, + family: Address_Family, + protocol: Socket_Protocol, +) -> ( + socket: Any_Socket, + err: Create_Socket_Error, +) { + socket = net.create_socket(family, protocol) or_return + + association_err := _associate_socket(socket, l) + // Network unreachable would've happened on creation too. + // Not possible to associate or invalid handle can't happen because we controlled creation. + assert(association_err == nil) + + return +} + +@(private="package") +_remove :: proc(target: ^Operation) { + debug("remove", target.type) + + if target._impl.timeout == (^Operation)(REMOVED) { + return + } + + if target._impl.timeout != nil { + _remove(target._impl.timeout) + } + + target._impl.timeout = (^Operation)(REMOVED) + + switch target.type { + case .Poll: + win.UnregisterWaitEx(target.poll._impl.wait_handle, nil) + target.poll._impl.wait_handle = nil + + ok := win.PostQueuedCompletionStatus( + g.iocp, + 0, + 0, + &target._impl.over, + ) + ensure(ok == true, "unexpected PostQueuedCompletionStatus error") + return + + case .Timeout: + if avl.remove_value(&target.l.timeouts, target) { + debug("removed timeout directly") + if !target.detached { + pool.put(&target.l.operation_pool, target) + } + } else { + debug("timeout is in completed queue, will be picked up there") + } + return + + case .Close, .Open, .Stat: + // Synchronous ops, picked up in handler. + return + + case .Accept, .Dial, .Read, .Recv, .Send, .Write, .Send_File: + if is_pending(target._impl.over) { + handle := operation_handle(target) + assert(handle != win.INVALID_HANDLE) + ok := win.CancelIoEx(handle, &target._impl.over) + if !ok { + err := win.System_Error(win.GetLastError()) + #partial switch err { + case .NOT_FOUND: + debug("Remove: Cancel", target.type, "NOT_FOUND") + case .INVALID_HANDLE: + debug("Remove: Cancel", target.type, "INVALID_HANDLE") // Likely closed already. + case: + assert(false, "unexpected CancelIoEx error") + } + } + } + + case ._Remove: + panic("can't remove a removal") + + case .None, ._Splice, ._Link_Timeout: + fallthrough + case: + unreachable() + } +} + +@(private="package") +_associate_handle :: proc(handle: uintptr, l: ^Event_Loop) -> (Handle, Association_Error) { + handle_iocp := win.CreateIoCompletionPort(win.HANDLE(handle), g.iocp, 0, 0) + if handle_iocp != g.iocp { + return INVALID_HANDLE, .Not_Possible_To_Associate + } + + cmode: byte + cmode |= win.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS + cmode |= win.FILE_SKIP_SET_EVENT_ON_HANDLE + ok := win.SetFileCompletionNotificationModes(win.HANDLE(handle), cmode) + + // This is an assertion because I don't believe this can happen when we just successfully + // called `CreateIoCompletionPort`. + assert(ok == true, "unexpected SetFileCompletionNotificationModes error") + + return Handle(handle), nil +} + +@(private="package") +_associate_socket :: proc(socket: Any_Socket, l: ^Event_Loop) -> Association_Error { + if err := net.set_blocking(socket, false); err != nil { + switch err { + case .None: unreachable() + case .Network_Unreachable: return .Network_Unreachable + case .Invalid_Argument: return .Invalid_Handle + case .Unknown: fallthrough + case: return Association_Error(net.last_platform_error()) + } + } + + _, err := _associate_handle(uintptr(net.any_socket_to_socket(socket)), l) + return err +} + +@(private="package") +_wake_up :: proc(l: ^Event_Loop) { + _, exchanged := sync.atomic_compare_exchange_strong(&l.state, .Sleeping, .Waking) + if exchanged { + win.QueueUserAPC(proc "system" (Parameter: win.ULONG_PTR) {}, l.thread, 0) + } +} + +@(private="package") +_yield :: proc() { + win.SwitchToThread() +} + +// Start file private. + +QUEUE_SIZE :: 128 + +REMOVED :: rawptr(max(uintptr)-1) + +INVALID_HANDLE :: Handle(win.INVALID_HANDLE) + +Op_Result :: enum { + Done, + Pending, +} + +/* +IOCP is designed to be used from multiple threads. +For best performance we need to adhere to that and have one single IOCP for the event loops to share. +*/ +g: struct{ + mu: sync.Mutex, + refs: int, + iocp: win.HANDLE, + err: General_Error, +} + +g_ref :: proc() -> General_Error { + sync.guard(&g.mu) + + if g.refs == 0 { + win.ensure_winsock_initialized() + + // NOTE: setting NumberOfConcurrentThreads to 0 which makes Windows use the amount of processors as a default. + // We may want to make this configurable somehow? + g.iocp = win.CreateIoCompletionPort(win.INVALID_HANDLE_VALUE, nil, 0, 0) + if g.iocp == nil { + g.err = General_Error(win.GetLastError()) + } + } + + sync.atomic_add(&g.refs, 1) + + return sync.atomic_load(&g.err) +} + +g_unref :: proc() { + sync.guard(&g.mu) + + if sync.atomic_sub(&g.refs, 1) == 1 { + win.CloseHandle(g.iocp) + g.err = nil + } +} + +operation_handle :: proc(op: ^Operation) -> win.HANDLE { + switch op.type { + case .Accept: return win.HANDLE(uintptr(op.accept.socket)) + case .Close: + switch fd in op.close.subject { + case TCP_Socket: return win.HANDLE(uintptr(fd)) + case UDP_Socket: return win.HANDLE(uintptr(fd)) + case Handle: return win.HANDLE(uintptr(fd)) + case: + unreachable() + } + case .Dial: return win.HANDLE(uintptr(op.dial.socket)) + case .Read: return win.HANDLE(op.read.handle) + case .Write: return win.HANDLE(op.write.handle) + case .Recv: return win.HANDLE(uintptr(net.any_socket_to_socket(op.recv.socket))) + case .Send: return win.HANDLE(uintptr(net.any_socket_to_socket(op.send.socket))) + case .Send_File: return win.HANDLE(uintptr(net.any_socket_to_socket(op.sendfile.socket))) + case .Poll: return win.HANDLE(uintptr(net.any_socket_to_socket(op.poll.socket))) + case .Stat: return win.HANDLE(uintptr(op.stat.handle)) + + case .Timeout, .Open, ._Splice, ._Link_Timeout, ._Remove, .None: + return win.INVALID_HANDLE + case: + unreachable() + } +} + +close_exec :: proc(op: ^Operation) { + assert(op.type == .Close) + + switch h in op.close.subject { + case Handle: + if !win.CloseHandle(win.HANDLE(h)) { + op.close.err = FS_Error(win.GetLastError()) + } + case TCP_Socket: + if win.closesocket(win.SOCKET(h)) != win.NO_ERROR { + op.close.err = FS_Error(win.WSAGetLastError()) + } + case UDP_Socket: + if win.closesocket(win.SOCKET(h)) != win.NO_ERROR { + op.close.err = FS_Error(win.WSAGetLastError()) + } + case: + op.close.err = .Invalid_Argument + return + } +} + +@(require_results) +accept_exec :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Accept) + assert(is_fresh(op._impl.over)) + + family := Address_Family.IP4 + { + ep, err := bound_endpoint(op.accept.socket) + if err != nil { + op.accept.err = net._accept_error() + return .Done + } + + if _, is_ip6 := ep.address.(IP6_Address); is_ip6 { + family = .IP6 + } + } + + client, err := _create_socket(op.l, family, .TCP) + if err != nil { + op.accept.err = net._accept_error() + return .Done + } + + + op.accept.client = client.(TCP_Socket) + + received: win.DWORD + if !win.AcceptEx( + win.SOCKET(op.accept.socket), + win.SOCKET(op.accept.client), + &op.accept._impl.addrs, + 0, + size_of(win.sockaddr_in6) + 16, + size_of(win.sockaddr_in6) + 16, + &received, + &op._impl.over, + ) { + if is_pending(op._impl.over) || (op._impl.over.Internal == nil && is_incomplete(win.System_Error(win.GetLastError()))) { + link_timeout(op, op.accept.expires) + return .Pending + } else if op._impl.over.Internal == nil { + op.accept.err = net._accept_error() + } + } + + return .Done +} + +accept_callback :: proc(op: ^Operation) { + assert(op.type == .Accept) + + defer if op.accept.err != nil { + win.closesocket(win.SOCKET(op.accept.client)) + } + + if op.accept.err != nil { + return + } + + _, err := wsa_get_result(win.SOCKET(op.accept.socket), op._impl.over) + #partial switch err { + case .SUCCESS: + local_addr: ^win.sockaddr + local_addr_len: win.INT + remote_addr: ^win.sockaddr + remote_addr_len: win.INT + win.GetAcceptExSockaddrs( + &op.accept._impl.addrs, + 0, + size_of(win.sockaddr_in6) + 16, + size_of(win.sockaddr_in6) + 16, + &local_addr, + &local_addr_len, + &remote_addr, + &remote_addr_len, + ) + + assert(remote_addr_len <= size_of(win.SOCKADDR_STORAGE_LH)) + op.accept.client_endpoint = sockaddr_to_endpoint((^win.SOCKADDR_STORAGE_LH)(remote_addr)) + + // enables getsockopt, setsockopt, getsockname, getpeername, etc. + win.setsockopt(win.SOCKET(op.accept.client), win.SOL_SOCKET, win.SO_UPDATE_ACCEPT_CONTEXT, nil, 0) + + case .OPERATION_ABORTED: + // This error could also happen when the user calls close on the socket. + if check_timed_out(op, op.accept.expires) { + op.accept.err = Accept_Error.Timeout + return + } + fallthrough + + case: + op.accept.err = net._accept_error() + } +} + +@(require_results) +dial_exec :: proc(op: ^Operation) -> (result: Op_Result) { + assert(op.type == .Dial) + assert(is_fresh(op._impl.over)) + + if op.dial.endpoint.port == 0 { + op.dial.err = .Port_Required + return .Done + } + + family := family_from_endpoint(op.dial.endpoint) + osocket, socket_err := _create_socket(op.l, family, .TCP) + if socket_err != nil { + op.dial.err = socket_err + return .Done + } + + op.dial.socket = osocket.(TCP_Socket) + + sockaddr := endpoint_to_sockaddr({IP6_Any if family == .IP6 else net.IP4_Any, 0}) + res := win.bind(win.SOCKET(op.dial.socket), &sockaddr, size_of(sockaddr)) + if res < 0 { + op.dial.err = net._bind_error() + win.closesocket(win.SOCKET(op.dial.socket)) + return .Done + } + + op.dial._impl.addr = endpoint_to_sockaddr(op.dial.endpoint) + + connect_ex: win.LPFN_CONNECTEX + load_socket_fn(win.SOCKET(op.dial.socket), win.WSAID_CONNECTEX, &connect_ex) + + transferred: win.DWORD + if !connect_ex( + win.SOCKET(op.dial.socket), + &op.dial._impl.addr, + size_of(op.dial._impl.addr), + nil, + 0, + &transferred, + &op._impl.over, + ) { + if is_pending(op._impl.over) || (op._impl.over.Internal == nil && is_incomplete(win.System_Error(win.GetLastError()))) { + link_timeout(op, op.dial.expires) + return .Pending + } else if op._impl.over.Internal == nil { + op.dial.err = net._dial_error() + } + } + + return .Done +} + +dial_callback :: proc(op: ^Operation) { + assert(op.type == .Dial) + + defer if op.dial.err != nil { + win.closesocket(win.SOCKET(op.dial.socket)) + } + + if op.dial.err != nil { + return + } + + _, err := wsa_get_result(win.SOCKET(op.dial.socket), op._impl.over) + #partial switch err { + case .SUCCESS: + // enables getsockopt, setsockopt, getsockname, getpeername, etc. + win.setsockopt(win.SOCKET(op.dial.socket), win.SOL_SOCKET, win.SO_UPDATE_CONNECT_CONTEXT, nil, 0) + + case .OPERATION_ABORTED: + op.dial.err = Dial_Error.Timeout + + case: + op.dial.err = net._dial_error() + } +} + +@(require_results) +read_exec :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Read) + op._impl.over = {} // Can be called multiple times. + + op._impl.over.OffsetFull = u64(op.read.offset) + u64(op.read.read) + + to_read := op.read.buf[op.read.read:] + + read: win.DWORD + if !win.ReadFile( + win.HANDLE(op.read.handle), + raw_data(to_read), + win.DWORD(min(len(to_read), MAX_RW)), + &read, + &op._impl.over, + ) { + assert(read == 0) + if is_pending(op._impl.over) { + link_timeout(op, op.read.expires) + return .Pending + } else if op._impl.over.Internal == nil { + err := win.GetLastError() + if is_incomplete(win.System_Error(err)) { + link_timeout(op, op.read.expires) + return .Pending + } + op.read.err = FS_Error(err) + } + } + + assert(uintptr(read) == uintptr(op._impl.over.InternalHigh)) + return .Done +} + +@(require_results) +read_callback :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Read) + + if op.read.err != nil { + return .Done + } + + n, err := get_result(op._impl.over) + #partial switch err { + case .SUCCESS: + case .OPERATION_ABORTED: + // This error could also happen when the user calls close on the handle. + if check_timed_out(op, op.read.expires) { + op.read.err = .Timeout + return .Done + } + fallthrough + case .HANDLE_EOF: + if op.read.read == 0 { + op.read.err = .EOF + return .Done + } + case: + op.read.err = FS_Error(err) + return .Done + } + + op.read.read += n + if op.read.all && op.read.read < len(op.read.buf) { + switch read_exec(op) { + case .Done: return read_callback(op) + case .Pending: return .Pending + } + } + + return .Done +} + +@(require_results) +write_exec :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Write) + op._impl.over = {} // Can be called multiple times. + + op._impl.over.OffsetFull = u64(op.write.offset) + u64(op.write.written) + + to_write := op.write.buf[op.write.written:] + + written: win.DWORD + if !win.WriteFile( + win.HANDLE(op.write.handle), + raw_data(to_write), + win.DWORD(min(len(to_write), MAX_RW)), + &written, + &op._impl.over, + ) { + assert(written == 0) + if is_pending(op._impl.over) { + link_timeout(op, op.write.expires) + return .Pending + } else if op._impl.over.Internal == nil { + err := win.GetLastError() + if is_incomplete(win.System_Error(err)) { + link_timeout(op, op.write.expires) + return .Pending + } + op.write.err = FS_Error(err) + } + } + + assert(uintptr(written) == uintptr(op._impl.over.InternalHigh)) + return .Done +} + +@(require_results) +write_callback :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Write) + + if op.write.err != nil { + return .Done + } + + n, err := get_result(op._impl.over) + #partial switch err { + case .SUCCESS: + case .OPERATION_ABORTED: + // This error could also happen when the user calls close on the handle. + if check_timed_out(op, op.write.expires) { + op.write.err = .Timeout + return .Done + } + fallthrough + case: + op.write.err = FS_Error(err) + return .Done + } + + op.write.written += n + if op.write.all && op.write.written < len(op.write.buf) { + switch write_exec(op) { + case .Done: return write_callback(op) + case .Pending: return .Pending + } + } + + return .Done +} + +@(require_results) +recv_exec :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Recv) + op._impl.over = {} // Can be called multiple times. + + if op.recv.err != nil { + return .Done + } + + bufs := slice.advance_slices(op.recv.bufs, op.recv.received) + bufs, _ = constraint_bufs_to_max_rw(op.recv.bufs) + + win_bufs := ([^]win.WSABUF)(intrinsics.alloca(size_of(win.WSABUF) * len(bufs), align_of(win.WSABUF))) + for buf, i in bufs { + assert(i64(len(buf)) < i64(max(u32))) + win_bufs[i] = {len=u32(len(buf)), buf=raw_data(buf)} + } + + status: win.c_int + switch sock in op.recv.socket { + case TCP_Socket: + status = win.WSARecv( + win.SOCKET(sock), + win_bufs, + u32(len(bufs)), + nil, + &op.recv._impl.flags, + win.LPWSAOVERLAPPED(&op._impl.over), + nil, + ) + case UDP_Socket: + op.recv._impl.source_len = size_of(op.recv._impl.source) + status = win.WSARecvFrom( + win.SOCKET(sock), + win_bufs, + u32(len(bufs)), + nil, + &op.recv._impl.flags, + (^win.sockaddr)(&op.recv._impl.source), + &op.recv._impl.source_len, + win.LPWSAOVERLAPPED(&op._impl.over), + nil, + ) + } + + if status == win.SOCKET_ERROR { + if is_pending(op._impl.over) || (op._impl.over.Internal == nil && is_incomplete(win.System_Error(win.GetLastError()))) { + link_timeout(op, op.recv.expires) + return .Pending + } else if op._impl.over.Internal == nil { + switch _ in op.recv.socket { + case TCP_Socket: op.recv.err = net._tcp_recv_error() + case UDP_Socket: op.recv.err = net._udp_recv_error() + } + } + } + + return .Done +} + +@(require_results) +recv_callback :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Recv) + + if op.recv.err != nil { + return .Done + } + + n, err := wsa_get_result(win.SOCKET((^net.Socket)(&op.recv.socket)^), op._impl.over) + #partial switch err { + case .SUCCESS: + case .OPERATION_ABORTED: + // This error could also happen when the user calls close on the socket. + if check_timed_out(op, op.recv.expires) { + switch _ in op.recv.socket { + case TCP_Socket: op.recv.err = net.TCP_Recv_Error.Timeout + case UDP_Socket: op.recv.err = net.UDP_Recv_Error.Timeout + } + return .Done + } + fallthrough + case: + switch _ in op.recv.socket { + case TCP_Socket: op.recv.err = net._tcp_recv_error() + case UDP_Socket: op.recv.err = net._udp_recv_error() + } + return .Done + } + + op.recv.received += n + + switch sock in op.recv.socket { + case TCP_Socket: + if n == 0 { + // Connection closed. + return .Done + } + + if op.recv.all { + total: int + for buf in op.recv.bufs { + total += len(buf) + } + + if op.recv.received < total { + switch recv_exec(op) { + case .Done: return recv_callback(op) + case .Pending: return .Pending + } + } + } + + case UDP_Socket: + assert(op.recv._impl.source_len > 0) + op.recv.source = sockaddr_to_endpoint(&op.recv._impl.source) + } + + return .Done +} + +@(require_results) +send_exec :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Send) + op._impl.over = {} // Can be called multiple times. + + if op.send.err != nil { + return .Done + } + + bufs := slice.advance_slices(op.send.bufs, op.send.sent) + bufs, _ = constraint_bufs_to_max_rw(op.send.bufs) + + win_bufs := ([^]win.WSABUF)(intrinsics.alloca(size_of(win.WSABUF) * len(bufs), align_of(win.WSABUF))) + for buf, i in bufs { + assert(i64(len(buf)) < i64(max(u32))) + win_bufs[i] = {len=u32(len(buf)), buf=raw_data(buf)} + } + + status: win.c_int + switch sock in op.send.socket { + case TCP_Socket: + status = win.WSASend( + win.SOCKET(sock), + win_bufs, + u32(len(bufs)), + nil, + 0, + win.LPWSAOVERLAPPED(&op._impl.over), + nil, + ) + case UDP_Socket: + addr := endpoint_to_sockaddr(op.send.endpoint) + status = win.WSASendTo( + win.SOCKET(sock), + win_bufs, + u32(len(bufs)), + nil, + 0, + (^win.sockaddr)(&addr), + size_of(addr), + win.LPWSAOVERLAPPED(&op._impl.over), + nil, + ) + } + + if status == win.SOCKET_ERROR { + if is_pending(op._impl.over) || (op._impl.over.Internal == nil && is_incomplete(win.System_Error(win.GetLastError()))) { + link_timeout(op, op.send.expires) + return .Pending + } else if op._impl.over.Internal == nil { + switch _ in op.send.socket { + case TCP_Socket: op.send.err = net._tcp_send_error() + case UDP_Socket: op.send.err = net._udp_send_error() + } + } + } + + return .Done +} + +@(require_results) +send_callback :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Send) + + if op.send.err != nil { + return .Done + } + + n, err := wsa_get_result(win.SOCKET((^net.Socket)(&op.send.socket)^), op._impl.over) + #partial switch err { + case .SUCCESS: + case .OPERATION_ABORTED: + // This error could also happen when the user calls close on the socket. + if check_timed_out(op, op.send.expires) { + switch _ in op.send.socket { + case TCP_Socket: op.send.err = net.TCP_Send_Error.Timeout + case UDP_Socket: op.send.err = net.UDP_Send_Error.Timeout + } + return .Done + } + fallthrough + case: + switch _ in op.send.socket { + case TCP_Socket: op.send.err = net._tcp_send_error() + case UDP_Socket: op.send.err = net._udp_send_error() + } + return .Done + } + + op.send.sent += n + + if op.send.all { + total: int + for buf in op.send.bufs { + total += len(buf) + } + + if op.send.sent < total { + switch send_exec(op) { + case .Done: return send_callback(op) + case .Pending: return .Pending + } + } + } + + return .Done +} + +@(require_results) +sendfile_exec :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Send_File) + op._impl.over = {} // Can be called multiple times. + + if op.sendfile.nbytes == SEND_ENTIRE_FILE { + type, size, stat_err := stat(op.sendfile.file) + if stat_err != nil { + op.sendfile.err = stat_err + return .Done + } + + op.sendfile.nbytes = int(size - i64(op.sendfile.offset)) + if type != .Regular || op.sendfile.nbytes <= 0 { + op.sendfile.err = FS_Error.Invalid_Argument + return .Done + } + } + + op._impl.over.OffsetFull = u64(op.sendfile.offset) + u64(op.sendfile.sent) + + if !win.TransmitFile( + win.SOCKET(op.sendfile.socket), + win.HANDLE(op.sendfile.file), + u32(min(op.sendfile.nbytes - op.sendfile.sent, MAX_RW)), + 0, + &op._impl.over, + nil, + 0, + ) { + if is_pending(op._impl.over) || (op._impl.over.Internal == nil && is_incomplete(win.System_Error(win.GetLastError()))) { + link_timeout(op, op.sendfile.expires) + return .Pending + } else if op._impl.over.Internal == nil { + op.sendfile.err = net._tcp_send_error() + } + } + + return .Done +} + +@(require_results) +sendfile_callback :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Send_File) + + if op.sendfile.err != nil { + return .Done + } + + n, err := wsa_get_result(win.SOCKET(op.sendfile.socket), op._impl.over) + #partial switch err { + case .SUCCESS: + case .OPERATION_ABORTED: + // This error could also happen when the user calls close on the socket. + if check_timed_out(op, op.sendfile.expires) { + op.sendfile.err = TCP_Send_Error.Timeout + return .Done + } + fallthrough + case: + op.sendfile.err = net._tcp_send_error() + return .Done + } + + op.sendfile.sent += n + if op.sendfile.sent < op.sendfile.nbytes { + switch sendfile_exec(op) { + case .Done: + return sendfile_callback(op) + case .Pending: + if op.sendfile.progress_updates { op.cb(op) } + return .Pending + } + } + + return .Done +} + +@(require_results) +poll_exec :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Poll) + + events: i32 = win.FD_CLOSE + switch op.poll.event { + case .Send: events |= win.FD_WRITE|win.FD_CONNECT + case .Receive: events |= win.FD_READ|win.FD_ACCEPT + case: + op.poll.result = .Invalid_Argument + return .Done + } + + op._impl.over.hEvent = win.WSACreateEvent() + if win.WSAEventSelect( + win.SOCKET(net.any_socket_to_socket(op.poll.socket)), + op._impl.over.hEvent, + events, + ) != 0 { + #partial switch win.System_Error(win.GetLastError()) { + case .WSAEINVAL, .WSAENOTSOCK: op.poll.result = .Invalid_Argument + case: op.poll.result = .Error + } + return .Done + } + + timeout := win.INFINITE + if op.poll.expires != {} { + diff := max(0, time.diff(op.l.now, op.poll.expires)) + timeout = win.DWORD(diff / time.Millisecond) + } + + ok := win.RegisterWaitForSingleObject( + &op.poll._impl.wait_handle, + op._impl.over.hEvent, + wait_callback, + op, + timeout, + win.WT_EXECUTEINWAITTHREAD|win.WT_EXECUTEONLYONCE, + ) + ensure(ok == true, "unexpected RegisterWaitForSingleObject error") + + return .Pending + + wait_callback :: proc "system" (lpParameter: win.PVOID, TimerOrWaitFired: win.BOOLEAN) { + op := (^Operation)(lpParameter) + assert_contextless(op.type == .Poll) + + if TimerOrWaitFired { + op.poll.result = .Timeout + } + + ok := win.PostQueuedCompletionStatus( + g.iocp, + 0, + 0, + &op._impl.over, + ) + ensure_contextless(ok == true, "unexpected PostQueuedCompletionStatus error") + } +} + +poll_callback :: proc(op: ^Operation) { + assert(op.type == .Poll) + + if op._impl.over.hEvent != nil { + win.WSACloseEvent(op._impl.over.hEvent) + } + + if op.poll._impl.wait_handle != nil { + win.UnregisterWaitEx(op.poll._impl.wait_handle, nil) + } + + if op.poll.result != nil { + return + } + + _, err := get_result(op._impl.over) + #partial switch err { + case .SUCCESS: + case: + op.poll.result = .Error + } +} + +open_exec :: proc(op: ^Operation) { + assert(op.type == .Open) + // No async way of doing this. + op.open.handle, op.open.err = _open_sync(op.l, op.open.path, op.open.dir, op.open.mode, op.open.perm) +} + +stat_exec :: proc(op: ^Operation) { + assert(op.type == .Stat) + // No async way of doing this. + op.stat.type, op.stat.size, op.stat.err = stat(op.stat.handle) +} + +@(require_results) +timeout_exec :: proc(op: ^Operation) -> Op_Result { + assert(op.type == .Timeout) + + if op.timeout.duration <= 0 { + return .Done + } else { + op.timeout._impl.expires = time.time_add(now(), op.timeout.duration) + node, inserted, alloc_err := avl.find_or_insert(&op.l.timeouts, op) + assert(alloc_err == nil) + assert(inserted) + assert(node != nil) + return .Pending + } +} + +link_timeout :: proc(op: ^Operation, expires: time.Time) { + if expires == {} { + return + } + + timeout_op := _prep(op.l, internal_timeout_callback, .Timeout) + timeout_op.timeout._impl.expires = expires + timeout_op.timeout._impl.target = op + op._impl.timeout = timeout_op + + node, inserted, alloc_err := avl.find_or_insert(&op.l.timeouts, timeout_op) + assert(alloc_err == nil) + assert(inserted) + assert(node != nil) +} + +internal_timeout_callback :: proc(op: ^Operation) { + assert(op.type == .Timeout) + + target := op.timeout._impl.target + assert(target != nil) + assert(target._impl.timeout == op) + target._impl.timeout = nil + + #partial switch target.type { + case .Poll: + target.poll.result = .Timeout + target.cb(target) + _remove(target) + return + } + + if is_pending(target._impl.over) { + handle := operation_handle(target) + assert(handle != win.INVALID_HANDLE) + ok := win.CancelIoEx(handle, &target._impl.over) + if !ok { + err := win.System_Error(win.GetLastError()) + #partial switch err { + case .NOT_FOUND: + debug("Timeout: Cancel", target.type, "NOT_FOUND") + case .INVALID_HANDLE: + debug("Timeout: Cancel", target.type, "INVALID_HANDLE") + case: + assert(false, "unexpected CancelIoEx error") + } + } + } +} + +stat :: proc(handle: Handle) -> (type: File_Type, size: i64, err: FS_Error) { + info: win.FILE_STANDARD_INFO + if !win.GetFileInformationByHandleEx(win.HANDLE(handle), .FileStandardInfo, &info, size_of(info)) { + err = FS_Error(win.GetLastError()) + return + } + + size = i64(info.EndOfFile) + + if info.Directory { + type = .Directory + return + } + + switch win.GetFileType(win.HANDLE(handle)) { + case win.FILE_TYPE_PIPE: + type = .Pipe_Or_Socket + return + case win.FILE_TYPE_CHAR: + type = .Device + return + case win.FILE_TYPE_DISK: + type = .Regular + // Don't return, might be a symlink. + case: + type = .Undetermined + return + } + + + tag_info: win.FILE_ATTRIBUTE_TAG_INFO + if !win.GetFileInformationByHandleEx(win.HANDLE(handle), .FileAttributeTagInfo, &tag_info, size_of(tag_info)) { + return + } + + if ( + (tag_info.FileAttributes & win.FILE_ATTRIBUTE_REPARSE_POINT != 0) && + ( + (tag_info.ReparseTag == win.IO_REPARSE_TAG_SYMLINK) || + (tag_info.ReparseTag == win.IO_REPARSE_TAG_MOUNT_POINT) + ) + ) { + type = .Symlink + } + + return +} + +STATUS_PENDING :: rawptr(uintptr(0x103)) + +is_pending :: proc(over: win.OVERLAPPED) -> bool { + return over.Internal == STATUS_PENDING +} + +is_fresh :: proc(over: win.OVERLAPPED) -> bool { + return over.Internal == nil && over.InternalHigh == nil +} + +get_result :: proc(over: win.OVERLAPPED) -> (n: int, err: win.System_Error) { + assert(!is_pending(over)) + + n = int(uintptr(over.InternalHigh)) + + if over.Internal != nil { + err = win.System_Error(win.RtlNtStatusToDosError(win.NTSTATUS(uintptr(over.Internal)))) + assert(!is_incomplete(err)) + } + return +} + +// `get_result` above translates NT status codes to errors through RtlNtStatsToDosError, +// this is context free and can cause weird mappings, thus for sockets we want to call `WSAGetOverlappedResult` +// which does context based mapping of error codes. +// See https://stackoverflow.com/questions/28925003/calling-wsagetlasterror-from-an-iocp-thread-return-incorrect-result +wsa_get_result :: proc(socket: win.SOCKET, over: win.OVERLAPPED) -> (n: int, err: win.System_Error) { + over := over + assert(!is_pending(over)) + + if over.Internal != nil { + flags: win.DWORD + _n: win.DWORD + res := win.WSAGetOverlappedResult(socket, &over, &_n, false, &flags) + assert(!res) + n = int(_n) // NOTE: It is possible that an amount of bytes is present when the operation was cancelled. + err = win.System_Error(win.WSAGetLastError()) + } + + n = int(uintptr(over.InternalHigh)) + return +} + +is_incomplete :: proc(err: win.System_Error) -> bool { + #partial switch err { + case .WSAEWOULDBLOCK, .IO_PENDING, .IO_INCOMPLETE, .WSAEALREADY: return true + case: return false + } +} + +endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: win.SOCKADDR_STORAGE_LH) { + switch a in ep.address { + case IP4_Address: + (^win.sockaddr_in)(&sockaddr)^ = win.sockaddr_in { + sin_port = u16be(win.USHORT(ep.port)), + sin_addr = transmute(win.in_addr)a, + sin_family = u16(win.AF_INET), + } + return + case IP6_Address: + (^win.sockaddr_in6)(&sockaddr)^ = win.sockaddr_in6 { + sin6_port = u16be(win.USHORT(ep.port)), + sin6_addr = transmute(win.in6_addr)a, + sin6_family = u16(win.AF_INET6), + } + return + } + unreachable() +} + +sockaddr_to_endpoint :: proc(native_addr: ^win.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) { + switch native_addr.ss_family { + case u16(win.AF_INET): + addr := cast(^win.sockaddr_in)native_addr + port := int(addr.sin_port) + ep = Endpoint { + address = IP4_Address(transmute([4]byte)addr.sin_addr), + port = port, + } + case u16(win.AF_INET6): + addr := cast(^win.sockaddr_in6)native_addr + port := int(addr.sin6_port) + ep = Endpoint { + address = IP6_Address(transmute([8]u16be)addr.sin6_addr), + port = port, + } + case: + panic("native_addr is neither IP4 or IP6 address") + } + return +} + +load_socket_fn :: proc(subject: win.SOCKET, guid: win.GUID, fn: ^$T) { + over: win.OVERLAPPED + + guid := guid + bytes: u32 + rc := win.WSAIoctl( + subject, + win.SIO_GET_EXTENSION_FUNCTION_POINTER, + &guid, + size_of(guid), + fn, + size_of(fn), + &bytes, + // NOTE: I don't think loading a socket fn ever blocks, + // but I would like to hit an assert if it does, so we do pass it. + &over, + nil, + ) + assert(rc != win.SOCKET_ERROR) + assert(bytes == size_of(fn^)) +} + +check_timed_out :: proc(op: ^Operation, expires: time.Time) -> bool { + return expires != {} && time.diff(op.l.now, expires) <= 0 +} + +timeouts_cmp :: #force_inline proc(a, b: ^Operation) -> slice.Ordering { + switch { + case a.timeout._impl.expires._nsec < b.timeout._impl.expires._nsec: + return .Less + case a.timeout._impl.expires._nsec > b.timeout._impl.expires._nsec: + return .Greater + case uintptr(a) < uintptr(b): + return .Less + case uintptr(a) > uintptr(b): + return .Greater + case: + assert(a == b) + return .Equal + } +}
\ No newline at end of file diff --git a/core/nbio/mpsc.odin b/core/nbio/mpsc.odin new file mode 100644 index 000000000..7f88829b4 --- /dev/null +++ b/core/nbio/mpsc.odin @@ -0,0 +1,63 @@ +#+private +package nbio + +import "base:runtime" + +import "core:sync" + +Multi_Producer_Single_Consumer :: struct { + count: int, + head: int, + tail: int, + buffer: []rawptr, + mask: int, +} + +mpsc_init :: proc(mpscq: ^Multi_Producer_Single_Consumer, cap: int, allocator: runtime.Allocator) -> runtime.Allocator_Error { + assert(runtime.is_power_of_two_int(cap), "cap must be a power of 2") + mpscq.buffer = make([]rawptr, cap, allocator) or_return + mpscq.mask = cap-1 + sync.atomic_thread_fence(.Release) + return nil +} + +mpsc_destroy :: proc(mpscq: ^Multi_Producer_Single_Consumer, allocator: runtime.Allocator) { + delete(mpscq.buffer, allocator) +} + +mpsc_enqueue :: proc(mpscq: ^Multi_Producer_Single_Consumer, obj: rawptr) -> bool { + count := sync.atomic_add_explicit(&mpscq.count, 1, .Acquire) + if count >= len(mpscq.buffer) { + sync.atomic_sub_explicit(&mpscq.count, 1, .Release) + return false + } + + head := sync.atomic_add_explicit(&mpscq.head, 1, .Acquire) + assert(mpscq.buffer[head & mpscq.mask] == nil) + rv := sync.atomic_exchange_explicit(&mpscq.buffer[head & mpscq.mask], obj, .Release) + assert(rv == nil) + return true +} + +mpsc_dequeue :: proc(mpscq: ^Multi_Producer_Single_Consumer) -> rawptr { + ret := sync.atomic_exchange_explicit(&mpscq.buffer[mpscq.tail], nil, .Acquire) + if ret == nil { + return nil + } + + mpscq.tail += 1 + if mpscq.tail >= len(mpscq.buffer) { + mpscq.tail = 0 + } + r := sync.atomic_sub_explicit(&mpscq.count, 1, .Release) + assert(r > 0) + return ret +} + +mpsc_count :: proc(mpscq: ^Multi_Producer_Single_Consumer) -> int { + return sync.atomic_load_explicit(&mpscq.count, .Relaxed) +} + +mpsc_cap :: proc(mpscq: ^Multi_Producer_Single_Consumer) -> int { + return len(mpscq.buffer) +}
\ No newline at end of file diff --git a/core/nbio/nbio.odin b/core/nbio/nbio.odin new file mode 100644 index 000000000..703a2b4d7 --- /dev/null +++ b/core/nbio/nbio.odin @@ -0,0 +1,431 @@ +package nbio + +import "base:intrinsics" +import "base:runtime" + +import "core:container/pool" +import "core:net" +import "core:time" + +/* +If the package is fully supported on the current target. If it is not it will compile but work +in a matter where things are unimplemented. + +Additionally if it is `FULLY_SUPPORTED` it may still return `.Unsupported` in `acquire_thread_event_loop` +If the target does not support the needed syscalls for operating the package. +*/ +FULLY_SUPPORTED :: _FULLY_SUPPORTED + +/* +An event loop, one per thread, consider the fields private. +Do not copy. +*/ +Event_Loop :: struct /* #no_copy */ { + using impl: _Event_Loop, + allocator: runtime.Allocator, + err: General_Error, + refs: int, + now: time.Time, + + // Queue that is used to queue operations from another thread to be executed on this thread. + queue: Multi_Producer_Single_Consumer, + + operation_pool: pool.Pool(Operation), +} + +Handle :: _Handle + +// The maximum size of user arguments for an operation, can be increased at the cost of more RAM. +MAX_USER_ARGUMENTS :: #config(NBIO_MAX_USER_ARGUMENTS, 4) +#assert(MAX_USER_ARGUMENTS >= 4) + +Operation :: struct { + cb: Callback, + user_data: [MAX_USER_ARGUMENTS + 1]rawptr, + detached: bool, + type: Operation_Type, + using specifics: Specifics, + + _impl: _Operation `fmt:"-"`, + using _: struct #raw_union { + _pool_link: ^Operation, + l: ^Event_Loop, + }, +} + +Specifics :: struct #raw_union { + accept: Accept `raw_union_tag:"type=.Accept"`, + close: Close `raw_union_tag:"type=.Close"`, + dial: Dial `raw_union_tag:"type=.Dial"`, + read: Read `raw_union_tag:"type=.Read"`, + recv: Recv `raw_union_tag:"type=.Recv"`, + send: Send `raw_union_tag:"type=.Send"`, + write: Write `raw_union_tag:"type=.Write"`, + timeout: Timeout `raw_union_tag:"type=.Timeout"`, + poll: Poll `raw_union_tag:"type=.Poll"`, + sendfile: Send_File `raw_union_tag:"type=.Send_File"`, + open: Open `raw_union_tag:"type=.Open"`, + stat: Stat `raw_union_tag:"type=.Stat"`, + + _remove: _Remove `raw_union_tag:"type=._Remove"`, + _link_timeout: _Link_Timeout `raw_union_tag:"type=._Link_Timeout"`, + _splice: _Splice `raw_union_tag:"type=._Splice"`, +} + +Operation_Type :: enum i32 { + None, + Accept, + Close, + Dial, + Read, + Recv, + Send, + Write, + Timeout, + Poll, + Send_File, + Open, + Stat, + + _Link_Timeout, + _Remove, + _Splice, +} + +Callback :: #type proc(op: ^Operation) + +/* +Initialize or increment the reference counted event loop for the current thread. +*/ +acquire_thread_event_loop :: proc() -> General_Error { + return _acquire_thread_event_loop() +} + +/* +Destroy or decrease the reference counted event loop for the current thread. +*/ +release_thread_event_loop :: proc() { + _release_thread_event_loop() +} + +current_thread_event_loop :: proc(loc := #caller_location) -> ^Event_Loop { + return _current_thread_event_loop(loc) +} + +/* +Each time you call this the implementation checks its state +and calls any callbacks which are ready. You would typically call this in a loop. + +Blocks for up-to timeout waiting for events if there is nothing to do. +*/ +tick :: proc(timeout: time.Duration = NO_TIMEOUT) -> General_Error { + l := &_tls_event_loop + if l.refs == 0 { return nil } + return _tick(l, timeout) +} + +/* +Runs the event loop by ticking in a loop until there is no more work to be done. +*/ +run :: proc() -> General_Error { + l := &_tls_event_loop + if l.refs == 0 { return nil } + + acquire_thread_event_loop() + defer release_thread_event_loop() + + for num_waiting() > 0 { + if errno := _tick(l, NO_TIMEOUT); errno != nil { + return errno + } + } + return nil +} + +/* +Runs the event loop by ticking in a loop until there is no more work to be done, or the flag `done` is `true`. +*/ +run_until :: proc(done: ^bool) -> General_Error { + l := &_tls_event_loop + if l.refs == 0 { return nil } + + acquire_thread_event_loop() + defer release_thread_event_loop() + + for num_waiting() > 0 && !intrinsics.volatile_load(done) { + if errno := _tick(l, NO_TIMEOUT); errno != nil { + return errno + } + } + return nil +} + +/* +Returns the number of in-progress operations to be completed on the event loop. +*/ +num_waiting :: proc(l: Maybe(^Event_Loop) = nil) -> int { + l_ := l.? or_else &_tls_event_loop + if l_.refs == 0 { return 0 } + return pool.num_outstanding(&l_.operation_pool) +} + +/* +Returns the current time (cached at most at the beginning of the current tick). +*/ +now :: proc() -> time.Time { + if _tls_event_loop.now == {} { + return time.now() + } + return _tls_event_loop.now +} + +/* +Remove the given operation from the event loop. The callback of it won't be called and resources are freed. + +Calling `remove`: +- Cancels the operation if it has not yet completed +- Prevents the callback from being called + +Cancellation via `remove` is *final* and silent: +- The callback will never be invoked +- No error is delivered +- The operation must be considered dead after removal + +WARN: the operation could have already been (partially or completely) completed. + A send with `all` set to true could have sent a portion already. + But also, a send that could be completed without blocking could have been completed. + You just won't get a callback. + +WARN: once an operation's callback is called it can not be removed anymore (use after free). + +WARN: needs to be called from the thread of the event loop the target belongs to. + +Common use would be to cancel a timeout, remove a polling, or remove an `accept` before calling `close` on it's socket. +*/ +remove :: proc(target: ^Operation) { + if target == nil { + return + } + + assert(target.type != .None) + + if target.l != &_tls_event_loop { + panic("nbio.remove called on different thread") + } + + _remove(target) +} + +/* +Creates a socket for use in `nbio` and relates it to the given event loop. + +Inputs: +- family: Should this be an IP4 or IP6 socket +- protocol: The type of socket (TCP or UDP) +- l: The event loop to associate it with, defaults to the current thread's loop + +Returns: +- socket: The created socket, consider `create_{udp|tcp}_socket` for a typed socket instead of the union +- err: A network error (`Create_Socket_Error`, or `Set_Blocking_Error`) which happened while opening +*/ +create_socket :: proc( + family: Address_Family, + protocol: Socket_Protocol, + l: ^Event_Loop = nil, + loc := #caller_location, +) -> ( + socket: Any_Socket, + err: Create_Socket_Error, +) { + return _create_socket(l if l != nil else _current_thread_event_loop(loc), family, protocol) +} + +/* +Creates a UDP socket for use in `nbio` and relates it to the given event loop. + +Inputs: +- family: Should this be an IP4 or IP6 socket +- l: The event loop to associate it with, defaults to the current thread's loop + +Returns: +- socket: The created UDP socket +- err: A network error (`Create_Socket_Error`, or `Set_Blocking_Error`) which happened while opening +*/ +create_udp_socket :: proc(family: Address_Family, l: ^Event_Loop = nil, loc := #caller_location) -> (net.UDP_Socket, Create_Socket_Error) { + socket, err := create_socket(family, .UDP, l, loc) + if err != nil { + return -1, err + } + + return socket.(UDP_Socket), nil +} + +/* +Creates a TCP socket for use in `nbio` and relates it to the given event loop. + +Inputs: +- family: Should this be an IP4 or IP6 socket +- l: The event loop to associate it with, defaults to the current thread's loop + +Returns: +- socket: The created TCP socket +- err: A network error (`Create_Socket_Error`, or `Set_Blocking_Error`) which happened while opening +*/ +create_tcp_socket :: proc(family: Address_Family, l: ^Event_Loop = nil, loc := #caller_location) -> (net.TCP_Socket, Create_Socket_Error) { + socket, err := create_socket(family, .TCP, l, loc) + if err != nil { + return -1, err + } + + return socket.(TCP_Socket), nil +} + +/* +Creates a socket, sets non blocking mode, relates it to the given IO, binds the socket to the given endpoint and starts listening. + +Inputs: +- endpoint: Where to bind the socket to +- backlog: The maximum length to which the queue of pending connections may grow, before refusing connections +- l: The event loop to associate the socket with, defaults to the current thread's loop + +Returns: +- socket: The opened, bound and listening socket +- err: A network error (`Create_Socket_Error`, `Bind_Error`, or `Listen_Error`) that has happened +*/ +listen_tcp :: proc(endpoint: Endpoint, backlog := 1000, l: ^Event_Loop = nil, loc := #caller_location) -> (socket: TCP_Socket, err: net.Network_Error) { + assert(backlog > 0 && backlog < int(max(i32))) + return _listen_tcp(l if l != nil else _current_thread_event_loop(loc), endpoint, backlog) +} + +/* +Opens a file and associates it with the event loop. + +Inputs: +- path: path to the file, if not absolute: relative from `dir` +- dir: directory that `path` is relative from (if it is relative), defaults to the current working directory +- mode: open mode, defaults to read-only +- perm: permissions to use when creating a file, defaults to read+write for everybody +- l: event loop to associate the file with, defaults to the current thread's + +Returns: +- handle: The file handle +- err: An error if it occurred +*/ +open_sync :: proc(path: string, dir: Handle = CWD, mode: File_Flags = {.Read}, perm := Permissions_Default_File, l: ^Event_Loop = nil, loc := #caller_location) -> (handle: Handle, err: FS_Error) { + return _open_sync(l if l != nil else _current_thread_event_loop(loc), path, dir, mode, perm) +} + +Association_Error :: enum { + None, + // The given file/handle/socket was not opened in a mode that it can be made non-blocking afterwards. + // + // On Windows, this can happen when a file is not opened with the `FILE_FLAG_OVERLAPPED` flag. + // If using `core:os`, that is set when you specify the `O_NONBLOCK` flag. + // There is no way to add that after the fact. + Not_Possible_To_Associate, + // The given handle is not a valid handle. + Invalid_Handle, + // No network connection, or the network stack is not initialized. + Network_Unreachable, +} + +/* +Associate the given OS handle, not opened through this package, with the event loop. + +Consider using this package's `open` or `open_sync` directly instead. + +The handle returned is for convenience, it is actually still the same handle as given. +Thus you should not close the given handle. + +On Windows, this can error when a file is not opened with the `FILE_FLAG_OVERLAPPED` flag. +If using `core:os`, that is set when you specify the `O_NONBLOCK` flag. +There is no way to add that after the fact. +*/ +associate_handle :: proc(handle: uintptr, l: ^Event_Loop = nil, loc := #caller_location) -> (Handle, Association_Error) { + return _associate_handle(handle, l if l != nil else _current_thread_event_loop(loc)) +} + +/* +Associate the given socket, not created through this package, with the event loop. + +Consider using this package's `create_socket` directly instead. +*/ +associate_socket :: proc(socket: Any_Socket, l: ^Event_Loop = nil, loc := #caller_location) -> Association_Error { + return _associate_socket(socket, l if l != nil else _current_thread_event_loop(loc)) +} + +Read_Entire_File_Error :: struct { + operation: Operation_Type, + value: FS_Error, +} + +Read_Entire_File_Callback :: #type proc(user_data: rawptr, data: []byte, err: Read_Entire_File_Error) + +/* +Combines multiple operations (open, stat, read, close) into one that reads an entire regular file. + +The error contains the `operation` that the error happened on. + +Inputs: +- path: path to the file, if not absolute: relative from `dir` +- user_data: a pointer passed through into the callback +- cb: the callback to call once completed, called with the user data, file data, and an optional error +- allocator: the allocator to allocate the file's contents onto +- dir: directory that `path` is relative from (if it is relative), defaults to the current working directory +- l: event loop to execute the operation on +*/ +read_entire_file :: proc(path: string, user_data: rawptr, cb: Read_Entire_File_Callback, allocator := context.allocator, dir := CWD, l: ^Event_Loop = nil, loc := #caller_location) { + _read_entire_file(l if l != nil else _current_thread_event_loop(loc), path, user_data, cb, allocator, dir) +} + +/* +Detach an operation from the package's lifetime management. + +By default the operation's lifetime is managed by the package and freed after a callback is called. +Calling this function detaches the operation from this lifetime. +You are expected to call `reattach` to give the package back this operation. +*/ +detach :: proc(op: ^Operation) { + op.detached = true +} + +/* +Reattach an operation to the package's lifetime management. +*/ +reattach :: proc(op: ^Operation) { + pool.put(&op.l.operation_pool, op) +} + +/* +Execute an operation. + +If the operation is attached to another thread's event loop, it is queued to be executed on that event loop, +optionally waking that loop up (from a blocking `tick`) with `trigger_wake_up`. +*/ +exec :: proc(op: ^Operation, trigger_wake_up := true) { + if op.l == &_tls_event_loop { + _exec(op) + } else { + for !mpsc_enqueue(&op.l.queue, op) { + warn("operation queue on event loop filled up") + wake_up(op.l) + _yield() + } + if trigger_wake_up { + wake_up(op.l) + } + } +} + +/* +Wake up an event loop on another thread which may be blocking for completed operations. + +Commonly used with `exec` from a worker thread to have the event loop pick up that work. +Note that by default `exec` already calls this procedure. +*/ +wake_up :: proc(l: ^Event_Loop) { + if l == &_tls_event_loop { + return + } + _wake_up(l) +} diff --git a/core/nbio/net.odin b/core/nbio/net.odin new file mode 100644 index 000000000..d584639a7 --- /dev/null +++ b/core/nbio/net.odin @@ -0,0 +1,39 @@ +package nbio + +import "core:net" + +Network_Error :: net.Network_Error +Accept_Error :: net.Accept_Error +Dial_Error :: net.Dial_Error +Send_Error :: net.Send_Error +TCP_Send_Error :: net.TCP_Send_Error +UDP_Send_Error :: net.UDP_Send_Error +Recv_Error :: net.Recv_Error +TCP_Recv_Error :: net.TCP_Recv_Error +UDP_Recv_Error :: net.UDP_Recv_Error +Listen_Error :: net.Listen_Error +Create_Socket_Error :: net.Create_Socket_Error + +Address_Family :: net.Address_Family +Socket_Protocol :: net.Socket_Protocol + +Address :: net.Address +IP4_Address :: net.IP4_Address +IP6_Address :: net.IP6_Address + +Endpoint :: net.Endpoint + +TCP_Socket :: net.TCP_Socket +UDP_Socket :: net.UDP_Socket +Any_Socket :: net.Any_Socket + +IP4_Any :: net.IP4_Any +IP6_Any :: net.IP6_Any +IP4_Loopback :: net.IP4_Loopback +IP6_Loopback :: net.IP6_Loopback + +family_from_endpoint :: net.family_from_endpoint +bind :: net.bind +bound_endpoint :: net.bound_endpoint +parse_endpoint :: net.parse_endpoint +endpoint_to_string :: net.endpoint_to_string diff --git a/core/nbio/ops.odin b/core/nbio/ops.odin new file mode 100644 index 000000000..e028c4a76 --- /dev/null +++ b/core/nbio/ops.odin @@ -0,0 +1,2473 @@ +package nbio + +import "base:intrinsics" + +import "core:container/pool" +import "core:time" +import "core:slice" +import "core:mem" + +NO_TIMEOUT: time.Duration: -1 + +Accept :: struct { + // Socket to accept an incoming connection on. + socket: TCP_Socket, + // When this operation expires and should be timed out. + expires: time.Time, + + // The connection that was accepted. + client: TCP_Socket, + // The connection's remote origin. + client_endpoint: Endpoint, + // An error, if it occurred. + err: Accept_Error, + + // Implementation specifics, private. + _impl: _Accept `fmt:"-"`, +} + +/* +Retrieves and preps an operation to do an accept without executing it. + +Executing can then be done with the `exec` procedure. + +The timeout is calculated from the time when this procedure was called, not from when it's executed. + +Any user data can be set on the returned operation's `user_data` field. + +Inputs: +- socket: A bound and listening socket *associated with the event loop* +- cb: The callback to be called when the operation finishes, `Operation.accept` will contain results +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +prep_accept :: #force_inline proc( + socket: TCP_Socket, + cb: Callback, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation { + op := _prep(l, cb, .Accept) + op.accept.socket = socket + if timeout > 0 { + op.accept.expires = time.time_add(now(), timeout) + } + return op +} + +/* +Using the given socket, accepts the next incoming connection, calling the callback when that happens. + +Any user data can be set on the returned operation's `user_data` field. +Polymorphic variants for type safe user data are available under `accept_poly`, `accept_poly2`, and `accept_poly3`. + +Inputs: +- socket: A bound and listening socket *associated with the event loop* +- cb: The callback to be called when the operation finishes, `Operation.accept` will contain results +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +accept :: #force_inline proc( + socket: TCP_Socket, + cb: Callback, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation { + res := prep_accept(socket, cb, timeout, l) + exec(res) + return res +} + +/* +Using the given socket, accepts the next incoming connection, calling the callback when that happens. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- socket: A bound and listening socket *associated with the event loop* +- p: User data, the callback will receive this as it's second argument +- cb: The callback to be called when the operation finishes, `Operation.accept` will contain results +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +accept_poly :: #force_inline proc( + socket: TCP_Socket, + p: $T, + cb: $C/proc(op: ^Operation, p: T), + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil +) -> ^Operation where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_accept(socket, _poly_cb(C, T), timeout, l) + _put_user_data(op, cb, p) + exec(op) + return op +} + +/* +Using the given socket, accepts the next incoming connection, calling the callback when that happens. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- socket: A bound and listening socket *associated with the event loop* +- p: User data, the callback will receive this as it's second argument +- p2: User data, the callback will receive this as it's third argument +- cb: The callback to be called when the operation finishes, `Operation.accept` will contain results +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +accept_poly2 :: #force_inline proc( + socket: TCP_Socket, + p: $T, p2: $T2, + cb: $C/proc(op: ^Operation, p: T, p2: T2), + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_accept(socket, _poly_cb2(C, T, T2), timeout, l) + _put_user_data2(op, cb, p, p2) + exec(op) + return op +} + +/* +Using the given socket, accepts the next incoming connection, calling the callback when that happens. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- socket: A bound and listening socket *associated with the event loop* +- p: User data, the callback will receive this as it's second argument +- p2: User data, the callback will receive this as it's third argument +- p3: User data, the callback will receive this as it's fourth argument +- cb: The callback to be called when the operation finishes, `Operation.accept` will contain results +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +accept_poly3 :: #force_inline proc( + socket: TCP_Socket, + p: $T, p2: $T2, p3: $T3, + cb: $C/proc(op: ^Operation, p: T, p2: T2, p3: T3), + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_accept(socket, _poly_cb3(C, T, T2, T3), timeout, l) + _put_user_data3(op, cb, p, p2, p3) + exec(op) + return op +} + +/* +A union of closable types that can be passed to `close`. +*/ +Closable :: union { + TCP_Socket, + UDP_Socket, + Handle, +} + +Close :: struct { + // The subject to close. + subject: Closable, + + // An error, if it occurred. + err: FS_Error, + + // Implementation specifics, private. + _impl: _Close `fmt:"-"`, +} + +@(private) +empty_callback :: proc(_: ^Operation) {} + +/* +Retrieves and preps an operation to do a close without executing it. + +Executing can then be done with the `exec` procedure. + +Closing something that has IO in progress may or may not cancel it, and may or may not call the callback. +For consistent behavior first call `remove` on in progress IO. + +Any user data can be set on the returned operation's `user_data` field. + +Inputs: +- subject: The subject (socket or file) to close +- cb: The optional callback to be called when the operation finishes, `Operation.close` will contain results +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +prep_close :: #force_inline proc(subject: Closable, cb: Callback = empty_callback, l: ^Event_Loop = nil) -> ^Operation { + op := _prep(l, cb, .Close) + op.close.subject = subject + return op +} + +/* +Closes the given subject (file or socket). + +Closing something that has IO in progress may or may not cancel it, and may or may not call the callback. +For consistent behavior first call `remove` on in progress IO. + +Any user data can be set on the returned operation's `user_data` field. +Polymorphic variants for type safe user data are available under `close_poly`, `close_poly2`, and `close_poly3`. + +Inputs: +- subject: The subject (socket or file) to close +- cb: The optional callback to be called when the operation finishes, `Operation.close` will contain results +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +close :: #force_inline proc(subject: Closable, cb: Callback = empty_callback, l: ^Event_Loop = nil) -> ^Operation { + op := prep_close(subject, cb, l) + exec(op) + return op +} + +/* +Closes the given subject (file or socket). + +Closing something that has IO in progress may or may not cancel it, and may or may not call the callback. +For consistent behavior first call `remove` on in progress IO. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- subject: The subject (socket or file) to close +- p: User data, the callback will receive this as it's second argument +- cb: The optional callback to be called when the operation finishes, `Operation.close` will contain results +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +close_poly :: #force_inline proc(subject: Closable, p: $T, cb: $C/proc(op: ^Operation, p: T), l: ^Event_Loop = nil) -> ^Operation +where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_close(subject, _poly_cb(C, T), l) + _put_user_data(op, cb, p) + exec(op) + return op +} + +/* +Closes the given subject (file or socket). + +Closing something that has IO in progress may or may not cancel it, and may or may not call the callback. +For consistent behavior first call `remove` on in progress IO. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- subject: The subject (socket or file) to close +- p: User data, the callback will receive this as it's second argument +- p2: User data, the callback will receive this as it's third argument +- cb: The optional callback to be called when the operation finishes, `Operation.close` will contain results +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +close_poly2 :: #force_inline proc(subject: Closable, p: $T, p2: $T2, cb: $C/proc(op: ^Operation, p: T, p2: T2), l: ^Event_Loop = nil) -> ^Operation +where size_of(T) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_close(subject, _poly_cb2(C, T, T2), l) + _put_user_data2(op, cb, p, p2) + exec(op) + return op +} + +/* +Closes the given subject (file or socket). + +Closing something that has IO in progress may or may not cancel it, and may or may not call the callback. +For consistent behavior first call `remove` on in progress IO. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- subject: The subject (socket or file) to close +- p: User data, the callback will receive this as it's second argument +- p2: User data, the callback will receive this as it's third argument +- p3: User data, the callback will receive this as it's fourth argument +- cb: The optional callback to be called when the operation finishes, `Operation.close` will contain results +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +close_poly3 :: #force_inline proc(subject: Closable, p: $T, p2: $T2, p3: $T3, cb: $C/proc(op: ^Operation, p: T, p2: T2, p3: T3), l: ^Event_Loop = nil) -> ^Operation +where size_of(T) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_close(subject, _poly_cb3(C, T, T2, T3), l) + _put_user_data3(op, cb, p, p2, p3) + exec(op) + return op +} + +Dial :: struct { + // The endpoint to connect to. + endpoint: Endpoint, + // When this operation expires and should be timed out. + expires: time.Time, + + // Errors that can be returned: `Create_Socket_Error`, or `Dial_Error`. + err: Network_Error, + // The socket to communicate with the connected server. + socket: TCP_Socket, + + // Implementation specifics, private. + _impl: _Dial `fmt:"-"`, +} + +/* +Retrieves and preps an operation to do a dial operation without executing it. + +Executing can then be done with the `exec` procedure. + +The timeout is calculated from the time when this procedure was called, not from when it's executed. + +Any user data can be set on the returned operation's `user_data` field. + +Inputs: +- endpoint: The endpoint to connect to +- cb: The callback to be called when the operation finishes, `Operation.dial` will contain results +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +prep_dial :: #force_inline proc( + endpoint: Endpoint, + cb: Callback, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation { + op := _prep(l, cb, .Dial) + if timeout > 0 { + op.dial.expires = time.time_add(now(), timeout) + } + op.dial.endpoint = endpoint + return op +} + +/* +Dials the given endpoint. + +Any user data can be set on the returned operation's `user_data` field. +Polymorphic variants for type safe user data are available under `dial_poly`, `dial_poly2`, and `dial_poly3`. + +Inputs: +- endpoint: The endpoint to connect to +- cb: The callback to be called when the operation finishes, `Operation.dial` will contain results +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +dial :: #force_inline proc( + endpoint: Endpoint, + cb: Callback, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation { + res := prep_dial(endpoint, cb, timeout, l) + exec(res) + return res +} + +/* +Dials the given endpoint. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- endpoint: The endpoint to connect to +- p: User data, the callback will receive this as it's second argument +- cb: The callback to be called when the operation finishes, `Operation.dial` will contain results +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +dial_poly :: #force_inline proc( + endpoint: Endpoint, + p: $T, + cb: $C/proc(op: ^Operation, p: T), + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_dial(endpoint, _poly_cb(C, T), timeout, l) + _put_user_data(op, cb, p) + exec(op) + + return op +} + +/* +Dials the given endpoint. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- endpoint: The endpoint to connect to +- p: User data, the callback will receive this as it's second argument +- p2: User data, the callback will receive this as it's third argument +- cb: The callback to be called when the operation finishes, `Operation.dial` will contain results +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +dial_poly2 :: #force_inline proc( + endpoint: Endpoint, + p: $T, p2: $T2, + cb: $C/proc(op: ^Operation, p: T, p2: T2), + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_dial(endpoint, _poly_cb2(C, T, T2), timeout, l) + _put_user_data2(op, cb, p, p2) + exec(op) + + return op +} + +/* +Dials the given endpoint. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- endpoint: The endpoint to connect to +- p: User data, the callback will receive this as it's second argument +- p2: User data, the callback will receive this as it's third argument +- p3: User data, the callback will receive this as it's fourth argument +- cb: The callback to be called when the operation finishes, `Operation.dial` will contain results +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +dial_poly3 :: #force_inline proc( + endpoint: Endpoint, + p: $T, p2: $T2, p3: $T3, + cb: $C/proc(op: ^Operation, p: T, p2: T2, p3: T3), + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_dial(endpoint, _poly_cb3(C, T, T2, T3), timeout, l) + _put_user_data3(op, cb, p, p2, p3) + exec(op) + + return op +} + +Recv :: struct { + // The socket to receive from. + socket: Any_Socket, + // The buffers to receive data into. + // The outer slice is copied internally, but the backing arrays must remain alive. + // It is safe to access `bufs` during the callback. + bufs: [][]byte, + // If true, the operation waits until all buffers are filled (TCP only). + all: bool, + // When this operation expires and should be timed out. + expires: time.Time, + + // The source endpoint data was received from (UDP only). + source: Endpoint, + + // An error, if it occurred. + // If `received == 0` and `err == nil`, the connection was closed by the peer. + err: Recv_Error, + // The number of bytes received. + received: int, + + // Implementation specifics, private. + _impl: _Recv `fmt:"-"`, +} + +/* +Retrieves and preps an operation to do a receive without executing it. + +Executing can then be done with the `exec` procedure. + +To avoid ambiguity between a closed connection and a 0-byte read, the provided buffers must have a total capacity greater than 0. + +The `bufs` slice itself is copied into the operation, so it can be temporary (e.g. on the stack), but the underlying memory of the buffers must remain valid until the callback is fired. + +The timeout is calculated from the time when this procedure was called, not from when it's executed. + +Any user data can be set on the returned operation's `user_data` field. + +Inputs: +- socket: The socket to receive from +- bufs: Buffers to fill with received data +- cb: The callback to be called when the operation finishes, `Operation.recv` will contain results +- all: If true, waits until all buffers are full before completing (TCP only, ignored for UDP) +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +prep_recv :: #force_inline proc( + socket: Any_Socket, + bufs: [][]byte, + cb: Callback, + all := false, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation { + assert(socket != nil) + + // If we accepted `bufs` that total 0 it would be ambiguous if the result of `received == 0 && err == nil` means connection closed or received 0 bytes. + assert(len(bufs) > 0) + assert(slice.any_of_proc(bufs, proc(buf: []byte) -> bool { return len(buf) > 0 })) + + op := _prep(l, cb, .Recv) + op.recv.socket = socket + op.recv.bufs = bufs + op.recv.all = all + if timeout > 0 { + op.recv.expires = time.time_add(now(), timeout) + } + + if len(op.recv.bufs) == 1 { + op.recv._impl.small_bufs = {op.recv.bufs[0]} + op.recv.bufs = op.recv._impl.small_bufs[:] + } else { + err: mem.Allocator_Error + if op.recv.bufs, err = slice.clone(op.recv.bufs, op.l.allocator); err != nil { + switch _ in op.recv.socket { + case TCP_Socket: op.recv.err = TCP_Recv_Error.Insufficient_Resources + case UDP_Socket: op.recv.err = UDP_Recv_Error.Insufficient_Resources + case: unreachable() + } + } + } + + return op +} + +/* +Receives data from the socket. + +If the operation completes with 0 bytes received and no error, it indicates the connection was closed by the peer. + +The `bufs` slice itself is copied into the operation, so it can be temporary (e.g. on the stack), but the underlying memory of the buffers must remain valid until the callback is fired. + +Any user data can be set on the returned operation's `user_data` field. +Polymorphic variants for type safe user data are available under `recv_poly`, `recv_poly2`, and `recv_poly3`. + +Inputs: +- socket: The socket to receive from +- bufs: Buffers to fill with received data +- cb: The callback to be called when the operation finishes, `Operation.recv` will contain results +- all: If true, waits until all buffers are full before completing (TCP only, ignored for UDP) +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +recv :: #force_inline proc( + socket: Any_Socket, + bufs: [][]byte, + cb: Callback, + all := false, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil +) -> ^Operation { + op := prep_recv(socket, bufs, cb, all, timeout, l) + exec(op) + return op +} + +/* +Receives data from the socket. + +If the operation completes with 0 bytes received and no error, it indicates the connection was closed by the peer. + +The `bufs` slice itself is copied into the operation, so it can be temporary (e.g. on the stack), but the underlying memory of the buffers must remain valid until the callback is fired. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- socket: The socket to receive from +- bufs: Buffers to fill with received data +- p: User data, the callback will receive this as it's second argument +- cb: The callback to be called when the operation finishes, `Operation.recv` will contain results +- all: If true, waits until all buffers are full before completing (TCP only, ignored for UDP) +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +recv_poly :: #force_inline proc( + socket: Any_Socket, + bufs: [][]byte, + p: $T, + cb: $C/proc(op: ^Operation, p: T), + all := false, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_recv(socket, bufs, _poly_cb(C, T), all, timeout, l) + _put_user_data(op, cb, p) + exec(op) + + return op +} + +/* +Receives data from the socket. + +If the operation completes with 0 bytes received and no error, it indicates the connection was closed by the peer. + +The `bufs` slice itself is copied into the operation, so it can be temporary (e.g. on the stack), but the underlying memory of the buffers must remain valid until the callback is fired. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- socket: The socket to receive from +- bufs: Buffers to fill with received data +- p: User data, the callback will receive this as it's second argument +- p2: User data, the callback will receive this as it's third argument +- cb: The callback to be called when the operation finishes, `Operation.recv` will contain results +- all: If true, waits until all buffers are full before completing (TCP only, ignored for UDP) +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +recv_poly2 :: #force_inline proc( + socket: Any_Socket, + bufs: [][]byte, + p: $T, p2: $T2, + cb: $C/proc(op: ^Operation, p: T, p2: T2), + all := false, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_recv(socket, bufs, _poly_cb2(C, T, T2), all, timeout, l) + _put_user_data2(op, cb, p, p2) + exec(op) + + return op +} + +/* +Receives data from the socket. + +If the operation completes with 0 bytes received and no error, it indicates the connection was closed by the peer. + +The `bufs` slice itself is copied into the operation, so it can be temporary (e.g. on the stack), but the underlying memory of the buffers must remain valid until the callback is fired. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- socket: The socket to receive from +- bufs: Buffers to fill with received data +- p: User data, the callback will receive this as it's second argument +- p2: User data, the callback will receive this as it's third argument +- p3: User data, the callback will receive this as it's fourth argument +- cb: The callback to be called when the operation finishes, `Operation.recv` will contain results +- all: If true, waits until all buffers are full before completing (TCP only, ignored for UDP) +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +recv_poly3 :: #force_inline proc( + socket: Any_Socket, + bufs: [][]byte, + p: $T, p2: $T2, p3: $T3, + cb: $C/proc(op: ^Operation, p: T, p2: T2, p3: T3), + all := false, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_recv(socket, bufs, _poly_cb3(C, T, T2, T3), all, timeout, l) + _put_user_data3(op, cb, p, p2, p3) + exec(op) + + return op +} + +Send :: struct { + // The socket to send to. + socket: Any_Socket, + // The buffers to send. + // The outer slice is copied internally, but the backing arrays must remain alive. + bufs: [][]byte `fmt:"-"`, + // The destination endpoint to send to (UDP only). + endpoint: Endpoint, + // If true, the operation ensures all data is sent before completing. + all: bool, + // When this operation expires and should be timed out. + expires: time.Time, + + // An error, if it occurred. + err: Send_Error, + // The number of bytes sent. + sent: int, + + // Implementation specifics, private. + _impl: _Send `fmt:"-"`, +} + +/* +Retrieves and preps an operation to do a send without executing it. + +Executing can then be done with the `exec` procedure. + +The `bufs` slice itself is copied into the operation, so it can be temporary (e.g. on the stack), but the underlying memory of the buffers must remain valid until the callback is fired. + +The timeout is calculated from the time when this procedure was called, not from when it's executed. + +Any user data can be set on the returned operation's `user_data` field. + +Inputs: +- socket: The socket to send to +- bufs: Buffers containing the data to send +- cb: The callback to be called when the operation finishes, `Operation.send` will contain results +- endpoint: The destination endpoint (UDP only, ignored for TCP) +- all: If true, the operation ensures all data is sent before completing +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +prep_send :: proc( + socket: Any_Socket, + bufs: [][]byte, + cb: Callback, + endpoint: Endpoint = {}, + all := true, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation { + assert(socket != nil) + op := _prep(l, cb, .Send) + op.send.socket = socket + op.send.bufs = bufs + op.send.endpoint = endpoint + op.send.all = all + if timeout > 0 { + op.send.expires = time.time_add(now(), timeout) + } + + if len(op.send.bufs) == 1 { + op.send._impl.small_bufs = {op.send.bufs[0]} + op.send.bufs = op.send._impl.small_bufs[:] + } else { + err: mem.Allocator_Error + if op.send.bufs, err = slice.clone(op.send.bufs, op.l.allocator); err != nil { + switch _ in op.send.socket { + case TCP_Socket: op.send.err = TCP_Send_Error.Insufficient_Resources + case UDP_Socket: op.send.err = UDP_Send_Error.Insufficient_Resources + case: unreachable() + } + } + } + + return op +} + +/* +Sends data to the socket. + +The `bufs` slice itself is copied into the operation, so it can be temporary (e.g. on the stack), but the underlying memory of the buffers must remain valid until the callback is fired. + +Any user data can be set on the returned operation's `user_data` field. +Polymorphic variants for type safe user data are available under `send_poly`, `send_poly2`, and `send_poly3`. + +Inputs: +- socket: The socket to send to +- bufs: Buffers containing the data to send +- cb: The callback to be called when the operation finishes, `Operation.send` will contain results +- endpoint: The destination endpoint (UDP only, ignored for TCP) +- all: If true, the operation ensures all data is sent before completing +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +send :: #force_inline proc( + socket: Any_Socket, + bufs: [][]byte, + cb: Callback, + endpoint: Endpoint = {}, + all := true, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation { + op := prep_send(socket, bufs, cb, endpoint, all, timeout, l) + exec(op) + return op +} + +/* +Sends data to the socket. + +The `bufs` slice itself is copied into the operation, so it can be temporary (e.g. on the stack), but the underlying memory of the buffers must remain valid until the callback is fired. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- socket: The socket to send to +- bufs: Buffers containing the data to send +- p: User data, the callback will receive this as it's second argument +- cb: The callback to be called when the operation finishes, `Operation.send` will contain results +- endpoint: The destination endpoint (UDP only, ignored for TCP) +- all: If true, the operation ensures all data is sent before completing +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +send_poly :: #force_inline proc( + socket: Any_Socket, + bufs: [][]byte, + p: $T, + cb: $C/proc(op: ^Operation, p: T), + endpoint: Endpoint = {}, + all := true, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_send(socket, bufs, _poly_cb(C, T), endpoint, all, timeout, l) + _put_user_data(op, cb, p) + exec(op) + + return op +} + +/* +Sends data to the socket. + +The `bufs` slice itself is copied into the operation, so it can be temporary (e.g. on the stack), but the underlying memory of the buffers must remain valid until the callback is fired. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- socket: The socket to send to +- bufs: Buffers containing the data to send +- p: User data, the callback will receive this as it's second argument +- p2: User data, the callback will receive this as it's third argument +- cb: The callback to be called when the operation finishes, `Operation.send` will contain results +- endpoint: The destination endpoint (UDP only, ignored for TCP) +- all: If true, the operation ensures all data is sent before completing +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +send_poly2 :: #force_inline proc( + socket: Any_Socket, + bufs: [][]byte, + p: $T, p2: $T2, + cb: $C/proc(op: ^Operation, p: T, p2: T2), + endpoint: Endpoint = {}, + all := true, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_send(socket, bufs, _poly_cb2(C, T, T2), endpoint, all, timeout, l) + _put_user_data2(op, cb, p, p2) + exec(op) + + return op +} + +/* +Sends data to the socket. + +The `bufs` slice itself is copied into the operation, so it can be temporary (e.g. on the stack), but the underlying memory of the buffers must remain valid until the callback is fired. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- socket: The socket to send to +- bufs: Buffers containing the data to send +- p: User data, the callback will receive this as it's second argument +- p2: User data, the callback will receive this as it's third argument +- p3: User data, the callback will receive this as it's fourth argument +- cb: The callback to be called when the operation finishes, `Operation.send` will contain results +- endpoint: The destination endpoint (UDP only, ignored for TCP) +- all: If true, the operation ensures all data is sent before completing +- timeout: Optional timeout for the operation, the callback will get a `.Timeout` error after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +send_poly3 :: #force_inline proc( + socket: Any_Socket, + bufs: [][]byte, + p: $T, p2: $T2, p3: $T3, + cb: $C/proc(op: ^Operation, p: T, p2: T2, p3: T3), + endpoint: Endpoint = {}, + all := true, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_send(socket, bufs, _poly_cb3(C, T, T2, T3), endpoint, all, timeout, l) + _put_user_data3(op, cb, p, p2, p3) + exec(op) + + return op +} + +Read :: struct { + // Handle to read from. + handle: Handle, + // Buffer to read data into. + buf: []byte `fmt:"v,read"`, + // Offset to read from. + offset: int, + // Whether to read until the buffer is full or an error occurs. + all: bool, + // When this operation expires and should be timed out. + expires: time.Time, + + // Error, if it occurred. + err: FS_Error, + // Number of bytes read. + read: int, + + // Implementation specifics, private. + _impl: _Read `fmt:"-"`, +} + +/* +Retrieves and preps a positional read operation without executing it. + +This is a pread-style operation: the read starts at the given offset and does +not modify the handle's current file position. + +Executing can then be done with the `exec` procedure. + +The timeout is calculated from the time when this procedure was called, +not from when it's executed. + +Any user data can be set on the returned operation's `user_data` field. + +Inputs: +- handle: Handle to read from +- offset: Offset to read from +- buf: Buffer to read data into (must not be empty) +- cb: The callback to be called when the operation finishes, `Operation.read` will contain results +- all: Whether to read until the buffer is full or an error occurs +- timeout: Optional timeout for the operation +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +prep_read :: #force_inline proc( + handle: Handle, + offset: int, + buf: []byte, + cb: Callback, + all := false, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation { + assert(len(buf) > 0) + op := _prep(l, cb, .Read) + op.read.handle = handle + op.read.buf = buf + op.read.offset = offset + op.read.all = all + if timeout > 0 { + op.read.expires = time.time_add(now(), timeout) + } + return op +} + +/* +Reads data from a handle at a specific offset. + +This is a pread-style operation: the read starts at the given offset and does +not modify the handle's current file position. + +Any user data can be set on the returned operation's `user_data` field. +Polymorphic variants for type safe user data are available under `read_poly`, `read_poly2`, and `read_poly3`. + +Inputs: +- handle: Handle to read from +- offset: Offset to read from +- buf: Buffer to read data into (must not be empty) +- cb: The callback to be called when the operation finishes, `Operation.read` will contain results +- all: Whether to read until the buffer is full or an error occurs +- timeout: Optional timeout for the operation +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +read :: #force_inline proc( + handle: Handle, + offset: int, + buf: []byte, + cb: Callback, + all := false, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation { + op := prep_read(handle, offset, buf, cb, all, timeout, l) + exec(op) + return op +} + +/* +Reads data from a handle at a specific offset. + +This is a pread-style operation: the read starts at the given offset and does +not modify the handle's current file position. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- handle: Handle to read from +- offset: Offset to read from +- buf: Buffer to read data into (must not be empty) +- p: User data, the callback will receive this as its second argument +- cb: The callback to be called when the operation finishes, `Operation.read` will contain results +- all: Whether to read until the buffer is full or an error occurs +- timeout: Optional timeout for the operation +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +read_poly :: #force_inline proc( + handle: Handle, + offset: int, + buf: []byte, + p: $T, + cb: $C/proc(op: ^Operation, p: T), + all := false, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil +) -> ^Operation where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_read(handle, offset, buf, _poly_cb(C, T), all=all, timeout=timeout, l=l) + _put_user_data(op, cb, p) + exec(op) + + return op +} + +/* +Reads data from a handle at a specific offset. + +This is a pread-style operation: the read starts at the given offset and does +not modify the handle's current file position. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- handle: Handle to read from +- offset: Offset to read from +- buf: Buffer to read data into (must not be empty) +- p: User data, the callback will receive this as its second argument +- p2: User data, the callback will receive this as its third argument +- cb: The callback to be called when the operation finishes, `Operation.read` will contain results +- all: Whether to read until the buffer is full or an error occurs +- timeout: Optional timeout for the operation +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +read_poly2 :: #force_inline proc( + handle: Handle, + offset: int, + buf: []byte, + p: $T, p2: $T2, + cb: $C/proc(op: ^Operation, p: T, p2: T2), + all := false, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil +) -> ^Operation where size_of(T) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_read(handle, offset, buf, _poly_cb2(C, T, T2), all, timeout, l) + _put_user_data2(op, cb, p, p2) + exec(op) + + return op +} + +/* +Reads data from a handle at a specific offset. + +This is a pread-style operation: the read starts at the given offset and does +not modify the handle's current file position. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- handle: Handle to read from +- offset: Offset to read from +- buf: Buffer to read data into (must not be empty) +- p: User data, the callback will receive this as its second argument +- p2: User data, the callback will receive this as its third argument +- p3: User data, the callback will receive this as its fourth argument +- cb: The callback to be called when the operation finishes, `Operation.read` will contain results +- all: Whether to read until the buffer is full or an error occurs +- timeout: Optional timeout for the operation +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +read_poly3 :: #force_inline proc( + handle: Handle, + offset: int, + buf: []byte, + p: $T, p2: $T2, p3: $T3, + cb: $C/proc(op: ^Operation, p: T, p2: T2, p3: T3), + all := false, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil +) -> ^Operation where size_of(T) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_read(handle, offset, buf, _poly_cb3(C, T, T2, T3), all, timeout, l) + _put_user_data3(op, cb, p, p2, p3) + exec(op) + + return op +} + +Write :: struct { + // Handle to write to. + handle: Handle, + // Buffer containing data to write. + buf: []byte, + // Offset to write to. + offset: int, + // Whether to write until the buffer is fully written or an error occurs. + all: bool, + // When this operation expires and should be timed out. + expires: time.Time, + + // Error, if it occurred. + err: FS_Error, + // Number of bytes written. + written: int, + + // Implementation specifics, private. + _impl: _Write `fmt:"-"`, +} + +/* +Retrieves and preps a positional write operation without executing it. + +This is a pwrite-style operation: the write starts at the given offset and does +not modify the handle's current file position. + +Executing can then be done with the `exec` procedure. + +The timeout is calculated from the time when this procedure was called, +not from when it's executed. + +Any user data can be set on the returned operation's `user_data` field. + +Inputs: +- handle: Handle to write to +- offset: Offset to write to +- buf: Buffer containing data to write (must not be empty) +- cb: The callback to be called when the operation finishes, `Operation.write` will contain results +- all: Whether to write until the entire buffer is written or an error occurs +- timeout: Optional timeout for the operation +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +prep_write :: #force_inline proc( + handle: Handle, + offset: int, + buf: []byte, + cb: Callback, + all := true, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation { + assert(len(buf) > 0) + op := _prep(l, cb, .Write) + op.write.handle = handle + op.write.buf = buf + op.write.offset = offset + op.write.all = all + if timeout > 0 { + op.write.expires = time.time_add(now(), timeout) + } + return op +} + +/* +Writes data to a handle at a specific offset. + +This is a pwrite-style operation: the write starts at the given offset and does +not modify the handle's current file position. + +Any user data can be set on the returned operation's `user_data` field. +Polymorphic variants for type safe user data are available under `write_poly`, `write_poly2`, and `write_poly3`. + +Inputs: +- handle: Handle to write to +- offset: Offset to write to +- buf: Buffer containing data to write (must not be empty) +- cb: The callback to be called when the operation finishes, `Operation.write` will contain results +- all: Whether to write until the entire buffer is written or an error occurs +- timeout: Optional timeout for the operation +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +write :: #force_inline proc( + handle: Handle, + offset: int, + buf: []byte, + cb: Callback, + all := true, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation { + op := prep_write(handle, offset, buf, cb, all, timeout, l) + exec(op) + return op +} + +/* +Writes data to a handle at a specific offset. + +This is a pwrite-style operation: the write starts at the given offset and does +not modify the handle's current file position. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- handle: Handle to write to +- offset: Offset to write to +- buf: Buffer containing data to write (must not be empty) +- p: User data, the callback will receive this as its second argument +- cb: The callback to be called when the operation finishes, `Operation.write` will contain results +- all: Whether to write until the entire buffer is written or an error occurs +- timeout: Optional timeout for the operation +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +write_poly :: #force_inline proc( + handle: Handle, + offset: int, + buf: []byte, + p: $T, + cb: $C/proc(op: ^Operation, p: T), + all := true, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil +) -> ^Operation where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_write(handle, offset, buf, _poly_cb(C, T), all=all, timeout=timeout, l=l) + _put_user_data(op, cb, p) + exec(op) + + return op +} + +/* +Writes data to a handle at a specific offset. + +This is a pwrite-style operation: the write starts at the given offset and does +not modify the handle's current file position. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- handle: Handle to write to +- offset: Offset to write to +- buf: Buffer containing data to write (must not be empty) +- p: User data, the callback will receive this as its second argument +- p2: User data, the callback will receive this as its third argument +- cb: The callback to be called when the operation finishes, `Operation.write` will contain results +- all: Whether to write until the entire buffer is written or an error occurs +- timeout: Optional timeout for the operation +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +write_poly2 :: #force_inline proc( + handle: Handle, + offset: int, + buf: []byte, + p: $T, p2: $T2, + cb: $C/proc(op: ^Operation, p: T, p2: T2), + all := true, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil +) -> ^Operation where size_of(T) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_write(handle, offset, buf, _poly_cb2(C, T, T2), all, timeout, l) + _put_user_data2(op, cb, p, p2) + exec(op) + + return op +} + +/* +Writes data to a handle at a specific offset. + +This is a pwrite-style operation: the write starts at the given offset and does +not modify the handle's current file position. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- handle: Handle to write to +- offset: Offset to write to +- buf: Buffer containing data to write (must not be empty) +- p: User data, the callback will receive this as its second argument +- p2: User data, the callback will receive this as its third argument +- p3: User data, the callback will receive this as its fourth argument +- cb: The callback to be called when the operation finishes, `Operation.write` will contain results +- all: Whether to write until the entire buffer is written or an error occurs +- timeout: Optional timeout for the operation +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +write_poly3 :: #force_inline proc( + handle: Handle, + offset: int, + buf: []byte, + p: $T, p2: $T2, p3: $T3, + cb: $C/proc(op: ^Operation, p: T, p2: T2, p3: T3), + all := true, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil +) -> ^Operation where size_of(T) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_write(handle, offset, buf, _poly_cb3(C, T, T2, T3), all, timeout, l) + _put_user_data3(op, cb, p, p2, p3) + exec(op) + + return op +} + +Timeout :: struct { + // Duration after which the timeout expires. + duration: time.Duration, + + // Implementation specifics, private. + _impl: _Timeout `fmt:"-"`, +} + +/* +Retrieves and preps a timeout operation without executing it. + +Executing can then be done with the `exec` procedure. + +Any user data can be set on the returned operation's `user_data` field. + +Inputs: +- duration: Duration to wait before the operation completes +- cb: The callback to be called when the operation finishes +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +prep_timeout :: #force_inline proc( + duration: time.Duration, + cb: Callback, + l: ^Event_Loop = nil, +) -> ^Operation { + op := _prep(l, cb, .Timeout) + op.timeout.duration = duration + return op +} + +/* +Schedules a timeout that completes after the given duration. + +Any user data can be set on the returned operation's `user_data` field. +Polymorphic variants for type safe user data are available under `timeout_poly`, `timeout_poly2`, and `timeout_poly3`. + +Inputs: +- duration: Duration to wait before the operation completes +- cb: The callback to be called when the operation finishes +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +timeout :: #force_inline proc( + duration: time.Duration, + cb: Callback, + l: ^Event_Loop = nil, +) -> ^Operation { + op := prep_timeout(duration, cb, l) + exec(op) + return op +} + +/* +Schedules a timeout that completes after the given duration. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- dur: Duration to wait before the operation completes +- p: User data, the callback will receive this as its second argument +- cb: The callback to be called when the operation finishes +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +timeout_poly :: #force_inline proc( + dur: time.Duration, + p: $T, + cb: $C/proc(op: ^Operation, p: T), + l: ^Event_Loop = nil, +) -> ^Operation + where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_timeout(dur, _poly_cb(C, T), l) + _put_user_data(op, cb, p) + exec(op) + + return op +} + +/* +Schedules a timeout that completes after the given duration. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- dur: Duration to wait before the operation completes +- p: User data, the callback will receive this as its second argument +- p2: User data, the callback will receive this as its third argument +- cb: The callback to be called when the operation finishes +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +timeout_poly2 :: #force_inline proc( + dur: time.Duration, + p: $T, p2: $T2, + cb: $C/proc(op: ^Operation, p: T, p2: T2), + l: ^Event_Loop = nil, +) -> ^Operation + where size_of(T) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_timeout(dur, _poly_cb2(C, T, T2), l) + _put_user_data2(op, cb, p, p2) + exec(op) + + return op +} + +/* +Schedules a timeout that completes after the given duration. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- dur: Duration to wait before the operation completes +- p: User data, the callback will receive this as its second argument +- p2: User data, the callback will receive this as its third argument +- p3: User data, the callback will receive this as its fourth argument +- cb: The callback to be called when the operation finishes +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +timeout_poly3 :: #force_inline proc( + dur: time.Duration, + p: $T, p2: $T2, p3: $T3, + cb: $C/proc(op: ^Operation, p: T, p2: T2, p3: T3), + l: ^Event_Loop = nil, +) -> ^Operation + where size_of(T) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_timeout(dur, _poly_cb3(C, T, T2, T3), l) + _put_user_data3(op, cb, p, p2, p3) + exec(op) + + return op +} + +/* +Retrieves and preps an operation that completes on the next event loop tick. + +This is equivalent to `prep_timeout(0, ...)`. +*/ +prep_next_tick :: #force_inline proc(cb: Callback, l: ^Event_Loop = nil) -> ^Operation { + return prep_timeout(0, cb, l) +} + +/* +Schedules an operation that completes on the next event loop tick. + +This is equivalent to `timeout(0, ...)`. +*/ +next_tick :: #force_inline proc(cb: Callback, l: ^Event_Loop = nil) -> ^Operation { + return timeout(0, cb, l) +} + +/* +Schedules an operation that completes on the next event loop tick. + +This is equivalent to `timeout_poly(0, ...)`. +*/ +next_tick_poly :: #force_inline proc(p: $T, cb: $C/proc(op: ^Operation, p: T), l: ^Event_Loop = nil) -> ^Operation + where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + return timeout_poly(0, p, cb, l) +} + +/* +Schedules an operation that completes on the next event loop tick. + +This is equivalent to `timeout_poly2(0, ...)`. +*/ +next_tick_poly2 :: #force_inline proc(p: $T, p2: $T2, cb: $C/proc(op: ^Operation, p: T, p2: T2), l: ^Event_Loop = nil) -> ^Operation + where size_of(T) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + return timeout_poly2(0, p, p2, cb, l) +} + +/* +Schedules an operation that completes on the next event loop tick. + +This is equivalent to `timeout_poly3(0, ...)`. +*/ +next_tick_poly3 :: #force_inline proc(p: $T, p2: $T2, p3: $T3, cb: $C/proc(op: ^Operation, p: T, p2: T2, p3: T3), l: ^Event_Loop = nil) -> ^Operation + where size_of(T) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + return timeout_poly3(0, p, p2, p3, cb, l) +} + +Poll_Result :: enum i32 { + // The requested event is ready. + Ready, + // The operation timed out before the event became ready. + Timeout, + // The socket was invalid. + Invalid_Argument, + // An unspecified error occurred. + Error, +} + +Poll_Event :: enum { + // The subject is ready to be received from. + Receive, + // The subject is ready to be sent to. + Send, +} + +Poll :: struct { + // Socket to poll. + socket: Any_Socket, + // Event to poll for. + event: Poll_Event, + // When this operation expires and should be timed out. + expires: time.Time, + + // Result of the poll. + result: Poll_Result, + + // Implementation specifics, private. + _impl: _Poll `fmt:"-"`, +} + +/* +Retrieves and preps an operation to poll a socket without executing it. + +Executing can then be done with the `exec` procedure. + +The timeout is calculated from the time when this procedure was called, +not from when it's executed. + +Any user data can be set on the returned operation's `user_data` field. + +Inputs: +- socket: Socket to poll that is *associated with the event loop* +- event: Event to poll for +- cb: The callback to be called when the operation finishes, `Operation.poll` will contain results +- timeout: Optional timeout for the operation, the callback will receive a `.Timeout` result after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +prep_poll :: #force_inline proc( + socket: Any_Socket, + event: Poll_Event, + cb: Callback, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation { + op := _prep(l, cb, .Poll) + op.poll.socket = socket + op.poll.event = event + if timeout > 0 { + op.poll.expires = time.time_add(now(), timeout) + } + return op +} + +/* +Poll a socket for readiness. + +NOTE: this is provided to help with "legacy" APIs that require polling behavior. +If you can avoid it and use the other procs in this package, do so. + +Any user data can be set on the returned operation's `user_data` field. +Polymorphic variants for type safe user data are available under `poll_poly`, `poll_poly2`, and `poll_poly3`. + +Inputs: +- socket: Socket to poll that is *associated with the event loop* +- event: Event to poll for +- cb: The callback to be called when the operation finishes, `Operation.poll` will contain results +- timeout: Optional timeout for the operation, the callback will receive a `.Timeout` result after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +poll :: #force_inline proc( + socket: Any_Socket, + event: Poll_Event, + cb: Callback, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation { + op := prep_poll(socket, event, cb, timeout, l) + exec(op) + return op +} + +/* +Poll a socket for readiness. + +NOTE: this is provided to help with "legacy" APIs that require polling behavior. +If you can avoid it and use the other procs in this package, do so. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- socket: Socket to poll that is *associated with the event loop* +- event: Event to poll for +- p: User data, the callback will receive this as its second argument +- cb: The callback to be called when the operation finishes, `Operation.poll` will contain results +- timeout: Optional timeout for the operation, the callback will receive a `.Timeout` result after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +poll_poly :: #force_inline proc( + socket: Any_Socket, + event: Poll_Event, + p: $T, + cb: $C/proc(op: ^Operation, p: T), + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_poll(socket, event, _poly_cb(C, T), timeout, l) + _put_user_data(op, cb, p) + exec(op) + + return op +} + +/* +Poll a socket for readiness. + +NOTE: this is provided to help with "legacy" APIs that require polling behavior. +If you can avoid it and use the other procs in this package, do so. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- socket: Socket to poll that is *associated with the event loop* +- event: Event to poll for +- p: User data, the callback will receive this as its second argument +- p2: User data, the callback will receive this as its third argument +- cb: The callback to be called when the operation finishes, `Operation.poll` will contain results +- timeout: Optional timeout for the operation, the callback will receive a `.Timeout` result after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +poll_poly2 :: #force_inline proc( + socket: Any_Socket, + event: Poll_Event, + p: $T, p2: $T2, + cb: $C/proc(op: ^Operation, p: T, p2: T2), + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_poll(socket, event, _poly_cb2(C, T, T2), timeout, l) + _put_user_data2(op, cb, p, p2) + exec(op) + + return op +} + +/* +Poll a socket for readiness. + +NOTE: this is provided to help with "legacy" APIs that require polling behavior. +If you can avoid it and use the other procs in this package, do so. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- socket: Socket to poll that is *associated with the event loop* +- event: Event to poll for +- p: User data, the callback will receive this as its second argument +- p2: User data, the callback will receive this as its third argument +- p3: User data, the callback will receive this as its fourth argument +- cb: The callback to be called when the operation finishes, `Operation.poll` will contain results +- timeout: Optional timeout for the operation, the callback will receive a `.Timeout` result after that duration +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +poll_poly3 :: #force_inline proc( + socket: Any_Socket, + event: Poll_Event, + p: $T, p2: $T2, p3: $T3, + cb: $C/proc(op: ^Operation, p: T, p2: T2, p3: T3), + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_poll(socket, event, _poly_cb3(C, T, T2, T3), timeout, l) + _put_user_data3(op, cb, p, p2, p3) + exec(op) + + return op +} + +SEND_ENTIRE_FILE :: -1 + +Send_File_Error :: union #shared_nil { + FS_Error, + TCP_Send_Error, +} + +Send_File :: struct { + // The TCP socket to send the file over. + socket: TCP_Socket, + // The handle of the regular file to send. + file: Handle, + // When this operation expires and should be timed out. + expires: time.Time, + // The starting offset within the file. + offset: int, + // Number of bytes to send. If set to SEND_ENTIRE_FILE, the file size is retrieved + // automatically and this field is updated to reflect the full size. + nbytes: int, + // If true, the callback is triggered periodically as data is sent. + // The callback will continue to be called until `sent == nbytes` or an error occurs. + progress_updates: bool, + + // Total number of bytes (so far if `progress_updates` is true). + sent: int, + // An error, if it occurred. Can be a filesystem or networking error. + err: Send_File_Error, + + // Implementation specifics, private. + _impl: _Send_File `fmt:"-"`, +} + +/* +Retrieves and preps an operation to send a file over a socket without executing it. + +Executing can then be done with the `exec` procedure. + +This uses high-performance zero-copy system calls where available. +Note: This is emulated on NetBSD and OpenBSD (stat -> mmap -> send) as they lack a native sendfile implementation. + +Any user data can be set on the returned operation's `user_data` field. + +Inputs: +- socket: The destination TCP socket +- file: The source file handle +- cb: The callback to be called when data is sent (if `progress_updates` is true) or the operation completes +- offset: Byte offset to start reading from the file +- nbytes: Total bytes to send (use SEND_ENTIRE_FILE for the whole file) +- progress_updates: If true, the callback fires multiple times to report progress, `sent == nbytes` means te operation completed +- timeout: Optional timeout for the operation +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the final callback is called +*/ +prep_sendfile :: #force_inline proc( + socket: TCP_Socket, + file: Handle, + cb: Callback, + offset: int = 0, + nbytes: int = SEND_ENTIRE_FILE, + progress_updates := false, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation { + assert(offset >= 0) + assert(nbytes == SEND_ENTIRE_FILE || nbytes > 0) + op := _prep(l, cb, .Send_File) + op.sendfile.socket = socket + op.sendfile.file = file + if timeout > 0 { + op.sendfile.expires = time.time_add(now(), timeout) + } + op.sendfile.offset = offset + op.sendfile.nbytes = nbytes + op.sendfile.progress_updates = progress_updates + return op +} + +/* +Sends a file over a TCP socket. + +This uses high-performance zero-copy system calls where available. +Note: This is emulated on NetBSD and OpenBSD (stat -> mmap -> send) as they lack a native sendfile implementation. + +Any user data can be set on the returned operation's `user_data` field. +Polymorphic variants for type safe user data are available under `sendfile_poly`, `sendfile_poly2`, and `sendfile_poly3`. + +Inputs: +- socket: The destination TCP socket +- file: The source file handle +- cb: The callback to be called when data is sent (if `progress_updates` is true) or the operation completes +- offset: Byte offset to start reading from the file +- nbytes: Total bytes to send (use SEND_ENTIRE_FILE for the whole file) +- progress_updates: If true, the callback fires multiple times to report progress, `sent == nbytes` means te operation completed +- timeout: Optional timeout for the operation +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the final callback is called +*/ +sendfile :: #force_inline proc( + socket: TCP_Socket, + file: Handle, + cb: Callback, + offset: int = 0, + nbytes: int = SEND_ENTIRE_FILE, + progress_updates := false, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation { + op := prep_sendfile(socket, file, cb, offset, nbytes, progress_updates, timeout, l) + exec(op) + return op +} + +/* +Sends a file over a TCP socket. + +This uses high-performance zero-copy system calls where available. +Note: This is emulated on NetBSD and OpenBSD (stat -> mmap -> send) as they lack a native sendfile implementation. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- socket: The destination TCP socket +- file: The source file handle +- p: User data, the callback will receive this as it's second argument +- cb: The callback to be called when data is sent (if `progress_updates` is true) or the operation completes +- offset: Byte offset to start reading from the file +- nbytes: Total bytes to send (use SEND_ENTIRE_FILE for the whole file) +- progress_updates: If true, the callback fires multiple times to report progress, `sent == nbytes` means te operation completed +- timeout: Optional timeout for the operation +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the final callback is called +*/ +sendfile_poly :: #force_inline proc( + socket: TCP_Socket, + file: Handle, + p: $T, + cb: $C/proc(op: ^Operation, p: T), + offset: int = 0, + nbytes: int = SEND_ENTIRE_FILE, + progress_updates := false, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_sendfile(socket, file, _poly_cb(C, T), offset, nbytes, progress_updates, timeout, l) + _put_user_data(op, cb, p) + exec(op) + + return op +} + +/* +Sends a file over a TCP socket. + +This uses high-performance zero-copy system calls where available. +Note: This is emulated on NetBSD and OpenBSD (stat -> mmap -> send) as they lack a native sendfile implementation. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- socket: The destination TCP socket +- file: The source file handle +- p: User data, the callback will receive this as it's second argument +- p2: User data, the callback will receive this as it's third argument +- cb: The callback to be called when data is sent (if `progress_updates` is true) or the operation completes +- offset: Byte offset to start reading from the file +- nbytes: Total bytes to send (use SEND_ENTIRE_FILE for the whole file) +- progress_updates: If true, the callback fires multiple times to report progress, `sent == nbytes` means te operation completed +- timeout: Optional timeout for the operation +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the final callback is called +*/ +sendfile_poly2 :: #force_inline proc( + socket: TCP_Socket, + file: Handle, + p: $T, p2: $T2, + cb: $C/proc(op: ^Operation, p: T, p2: T2), + offset: int = 0, + nbytes: int = SEND_ENTIRE_FILE, + progress_updates := false, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_sendfile(socket, file, _poly_cb2(C, T, T2), offset, nbytes, progress_updates, timeout, l) + _put_user_data2(op, cb, p, p2) + exec(op) + + return op +} + +/* +Sends a file over a TCP socket. + +This uses high-performance zero-copy system calls where available. +Note: This is emulated on NetBSD and OpenBSD (stat -> mmap -> send) as they lack a native sendfile implementation. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- socket: The destination TCP socket +- file: The source file handle +- p: User data, the callback will receive this as it's second argument +- p2: User data, the callback will receive this as it's third argument +- p3: User data, the callback will receive this as it's fourth argument +- cb: The callback to be called when data is sent (if `progress_updates` is true) or the operation completes +- offset: Byte offset to start reading from the file +- nbytes: Total bytes to send (use SEND_ENTIRE_FILE for the whole file) +- progress_updates: If true, the callback fires multiple times to report progress, `sent == nbytes` means te operation completed +- timeout: Optional timeout for the operation +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the final callback is called +*/ +sendfile_poly3 :: #force_inline proc( + socket: TCP_Socket, + file: Handle, + p: $T, p2: $T2, p3: $T3, + cb: $C/proc(op: ^Operation, p: T, p2: T2, p3: T3), + offset: int = 0, + nbytes: int = SEND_ENTIRE_FILE, + progress_updates := false, + timeout: time.Duration = NO_TIMEOUT, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_sendfile(socket, file, _poly_cb3(C, T, T2, T3), offset, nbytes, progress_updates, timeout, l) + _put_user_data3(op, cb, p, p2, p3) + exec(op) + + return op +} + +/* +File permission bit-set. + +This type represents POSIX-style file permissions, split into user, group, +and other categories, each with read, write, and execute flags. +*/ +Permissions :: distinct bit_set[Permission_Flag; u32] + +Permission_Flag :: enum u32 { + Execute_Other = 0, + Write_Other = 1, + Read_Other = 2, + + Execute_Group = 3, + Write_Group = 4, + Read_Group = 5, + + Execute_User = 6, + Write_User = 7, + Read_User = 8, +} + +// Convenience permission sets. +Permissions_Execute_All :: Permissions{.Execute_User, .Execute_Group, .Execute_Other} +Permissions_Write_All :: Permissions{.Write_User, .Write_Group, .Write_Other} +Permissions_Read_All :: Permissions{.Read_User, .Read_Group, .Read_Other} + +// Read and write permissions for user, group, and others. +Permissions_Read_Write_All :: Permissions_Read_All + Permissions_Write_All + +// Read, write, and execute permissions for user, group, and others. +Permissions_All :: Permissions_Read_All + Permissions_Write_All + Permissions_Execute_All + +// Default permissions used when creating a file (read and write for everyone). +Permissions_Default_File :: Permissions_Read_All + Permissions_Write_All + +// Default permissions used when creating a directory (read, write, and execute for everyone). +Permissions_Default_Directory :: Permissions_Read_All + Permissions_Write_All + Permissions_Execute_All + +File_Flags :: bit_set[File_Flag; int] + +File_Flag :: enum { + // Open for reading. + Read, + // Open for writing. + Write, + // Append writes to the end of the file. + Append, + // Create the file if it does not exist. + Create, + // Fail if the file already exists (used with Create). + Excl, + Sync, + // Truncate the file on open. + Trunc, +} + +Open :: struct { + // Base directory the path is relative to. + dir: Handle, + // Path to the file. + path: string, + // File open mode flags. + mode: File_Flags, + // Permissions used if the file is created. + perm: Permissions, + + // The opened file handle. + handle: Handle, + // An error, if it occurred. + err: FS_Error, + + // Implementation specifics, private. + _impl: _Open `fmt:"-"`, +} + +// Sentinel handle representing the current/present working directory. +CWD :: _CWD + +/* +Retrieves and preps an operation to open a file without executing it. + +Executing can then be done with the `exec` procedure. + +Any user data can be set on the returned operation's `user_data` field. + +Inputs: +- path: Path to the file, if not absolute: relative from `dir` +- cb: The callback to be called when the operation finishes, `Operation.open` will contain results +- mode: File open mode flags, defaults to read-only +- perm: Permissions to use when creating a file, defaults to read+write for everybody +- dir: Directory that `path` is relative from (if it is relative), defaults to the current working directory +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +prep_open :: #force_inline proc( + path: string, + cb: Callback, + mode: File_Flags = {.Read}, + perm: Permissions = Permissions_Default_File, + dir: Handle = CWD, + l: ^Event_Loop = nil, +) -> ^Operation { + op := _prep(l, cb, .Open) + op.open.path = path + op.open.mode = mode + op.open.perm = perm + op.open.dir = dir + return op +} + +/* +Opens a file and associates it with the event loop. + +Any user data can be set on the returned operation's `user_data` field. +Polymorphic variants for type safe user data are available under `open_poly`, `open_poly2`, and `open_poly3`. + +Inputs: +- path: Path to the file, if not absolute: relative from `dir` +- cb: The callback to be called when the operation finishes, `Operation.open` will contain results +- mode: File open mode flags, defaults to read-only +- perm: Permissions to use when creating a file, defaults to read+write for everybody +- dir: Directory that `path` is relative from (if it is relative), defaults to the current working directory +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +open :: #force_inline proc( + path: string, + cb: Callback, + mode: File_Flags = {.Read}, + perm: Permissions = Permissions_Default_File, + dir: Handle = CWD, + l: ^Event_Loop = nil, +) -> ^Operation { + op := prep_open(path, cb, mode, perm, dir, l) + exec(op) + return op +} + +/* +Opens a file and associates it with the event loop. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- path: Path to the file, if not absolute: relative from `dir` +- p: User data, the callback will receive this as its second argument +- cb: The callback to be called when the operation finishes, `Operation.open` will contain results +- mode: File open mode flags, defaults to read-only +- perm: Permissions to use when creating a file, defaults to read+write for everybody +- dir: Directory that `path` is relative from (if it is relative), defaults to the current working directory +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +open_poly :: #force_inline proc( + path: string, + p: $T, + cb: $C/proc(op: ^Operation, p: T), + mode: File_Flags = {.Read}, + perm: Permissions = Permissions_Default_File, + dir: Handle = CWD, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_open(path, _poly_cb(C, T), mode, perm, dir, l) + _put_user_data(op, cb, p) + exec(op) + + return op +} + +/* +Opens a file and associates it with the event loop. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- path: Path to the file, if not absolute: relative from `dir` +- p: User data, the callback will receive this as its second argument +- p2: User data, the callback will receive this as its third argument +- cb: The callback to be called when the operation finishes, `Operation.open` will contain results +- mode: File open mode flags, defaults to read-only +- perm: Permissions to use when creating a file, defaults to read+write for everybody +- dir: Directory that `path` is relative from (if it is relative), defaults to the current working directory +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +open_poly2 :: #force_inline proc( + path: string, + p: $T, p2: $T2, + cb: $C/proc(op: ^Operation, p: T, p2: T2), + mode: File_Flags = {.Read}, + perm: Permissions = Permissions_Default_File, + dir: Handle = CWD, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_open(path, _poly_cb2(C, T, T2), mode, perm, dir, l) + _put_user_data2(op, cb, p, p2) + exec(op) + + return op +} + +/* +Asynchronously opens a file and associates it with the event loop. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- path: Path to the file, if not absolute: relative from `dir` +- p: User data, the callback will receive this as its second argument +- p2: User data, the callback will receive this as its third argument +- p3: User data, the callback will receive this as its fourth argument +- cb: The callback to be called when the operation finishes, `Operation.open` will contain results +- mode: File open mode flags, defaults to read-only +- perm: Permissions to use when creating a file, defaults to read+write for everybody +- dir: Directory that `path` is relative from (if it is relative), defaults to the current working directory +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +open_poly3 :: #force_inline proc( + path: string, + p: $T, p2: $T2, p3: $T3, + cb: $C/proc(op: ^Operation, p: T, p2: T2, p3: T3), + mode: File_Flags = {.Read}, + perm: Permissions = Permissions_Default_File, + dir: Handle = CWD, + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_open(path, _poly_cb3(C, T, T2, T3), mode, perm, dir, l) + _put_user_data3(op, cb, p, p2, p3) + exec(op) + + return op +} + +File_Type :: enum { + // File type could not be determined. + Undetermined, + // Regular file. + Regular, + // Directory. + Directory, + // Symbolic link. + Symlink, + // Pipe or socket. + Pipe_Or_Socket, + // Character or block device. + Device, +} + +Stat :: struct { + // Handle to stat. + handle: Handle, + + // The type of the file. + type: File_Type, + // Size of the file in bytes. + size: i64 `fmt:"M"`, + + // An error, if it occurred. + err: FS_Error, + + // Implementation specifics, private. + _impl: _Stat `fmt:"-"`, +} + +/* +Retrieves and preps an operation to stat a handle without executing it. + +Executing can then be done with the `exec` procedure. + +Any user data can be set on the returned operation's `user_data` field. + +Inputs: +- handle: Handle to retrieve stat +- cb: The callback to be called when the operation finishes, `Operation.stat` will contain results +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +prep_stat :: #force_inline proc( + handle: Handle, + cb: Callback, + l: ^Event_Loop = nil, +) -> ^Operation { + op := _prep(l, cb, .Stat) + op.stat.handle = handle + return op +} + +/* +Stats a handle. + +Any user data can be set on the returned operation's `user_data` field. +Polymorphic variants for type safe user data are available under `stat_poly`, `stat_poly2`, and `stat_poly3`. + +Inputs: +- handle: Handle to retrieve status information for +- cb: The callback to be called when the operation finishes, `Operation.stat` will contain results +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +stat :: #force_inline proc( + handle: Handle, + cb: Callback, + l: ^Event_Loop = nil, +) -> ^Operation { + op := prep_stat(handle, cb, l) + exec(op) + return op +} + +/* +Stats a handle. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- handle: Handle to retrieve status information for +- p: User data, the callback will receive this as its second argument +- cb: The callback to be called when the operation finishes, `Operation.stat` will contain results +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +stat_poly :: #force_inline proc( + handle: Handle, + p: $T, + cb: $C/proc(op: ^Operation, p: T), + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_stat(handle, _poly_cb(C, T), l) + _put_user_data(op, cb, p) + exec(op) + + return op +} + +/* +Stats a handle. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- handle: Handle to retrieve status information for +- p: User data, the callback will receive this as its second argument +- p2: User data, the callback will receive this as its third argument +- cb: The callback to be called when the operation finishes, `Operation.stat` will contain results +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +stat_poly2 :: #force_inline proc( + handle: Handle, + p: $T, p2: $T2, + cb: $C/proc(op: ^Operation, p: T, p2: T2), + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_stat(handle, _poly_cb2(C, T, T2), l) + _put_user_data2(op, cb, p, p2) + exec(op) + + return op +} + +/* +Stats a handle. + +This procedure uses polymorphism for type safe user data up to a certain size. + +Inputs: +- handle: Handle to retrieve status information for +- p: User data, the callback will receive this as its second argument +- p2: User data, the callback will receive this as its third argument +- p3: User data, the callback will receive this as its fourth argument +- cb: The callback to be called when the operation finishes, `Operation.stat` will contain results +- l: Event loop to associate the operation with, defaults to the current thread's loop + +Returns: A non-nil pointer to the operation, alive until the callback is called +*/ +stat_poly3 :: #force_inline proc( + handle: Handle, + p: $T, p2: $T2, p3: $T3, + cb: $C/proc(op: ^Operation, p: T, p2: T2, p3: T3), + l: ^Event_Loop = nil, +) -> ^Operation where size_of(T) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + + op := prep_stat(handle, _poly_cb3(C, T, T2, T3), l) + _put_user_data3(op, cb, p, p2, p3) + exec(op) + + return op +} + +_prep :: proc(l: ^Event_Loop, cb: Callback, type: Operation_Type) -> ^Operation { + assert(cb != nil) + assert(type != .None) + l := l + if l == nil { l = _current_thread_event_loop() } + operation := pool.get(&l.operation_pool) + operation.l = l + operation.type = type + operation.cb = cb + return operation +} + +_poly_cb :: #force_inline proc($C: typeid, $T: typeid) -> proc(^Operation) { + return proc(op: ^Operation) { + ptr := uintptr(&op.user_data) + cb := intrinsics.unaligned_load((^C)(rawptr(ptr))) + p := intrinsics.unaligned_load((^T)(rawptr(ptr + size_of(C)))) + cb(op, p) + } +} + +_poly_cb2 :: #force_inline proc($C: typeid, $T: typeid, $T2: typeid) -> proc(^Operation) { + return proc(op: ^Operation) { + ptr := uintptr(&op.user_data) + cb := intrinsics.unaligned_load((^C) (rawptr(ptr))) + p := intrinsics.unaligned_load((^T) (rawptr(ptr + size_of(C)))) + p2 := intrinsics.unaligned_load((^T2)(rawptr(ptr + size_of(C) + size_of(T)))) + cb(op, p, p2) + } +} + +_poly_cb3 :: #force_inline proc($C: typeid, $T: typeid, $T2: typeid, $T3: typeid) -> proc(^Operation) { + return proc(op: ^Operation) { + ptr := uintptr(&op.user_data) + cb := intrinsics.unaligned_load((^C) (rawptr(ptr))) + p := intrinsics.unaligned_load((^T) (rawptr(ptr + size_of(C)))) + p2 := intrinsics.unaligned_load((^T2)(rawptr(ptr + size_of(C) + size_of(T)))) + p3 := intrinsics.unaligned_load((^T3)(rawptr(ptr + size_of(C) + size_of(T) + size_of(T2)))) + cb(op, p, p2, p3) + } +} + +_put_user_data :: #force_inline proc(op: ^Operation, cb: $C, p: $T) { + ptr := uintptr(&op.user_data) + intrinsics.unaligned_store((^C)(rawptr(ptr)), cb) + intrinsics.unaligned_store((^T)(rawptr(ptr + size_of(cb))), p) +} + +_put_user_data2 :: #force_inline proc(op: ^Operation, cb: $C, p: $T, p2: $T2) { + ptr := uintptr(&op.user_data) + intrinsics.unaligned_store((^C) (rawptr(ptr)), cb) + intrinsics.unaligned_store((^T) (rawptr(ptr + size_of(cb))), p) + intrinsics.unaligned_store((^T2)(rawptr(ptr + size_of(cb) + size_of(p))), p2) +} + +_put_user_data3 :: #force_inline proc(op: ^Operation, cb: $C, p: $T, p2: $T2, p3: $T3) { + ptr := uintptr(&op.user_data) + intrinsics.unaligned_store((^C) (rawptr(ptr)), cb) + intrinsics.unaligned_store((^T) (rawptr(ptr + size_of(cb))), p) + intrinsics.unaligned_store((^T2)(rawptr(ptr + size_of(cb) + size_of(p))), p2) + intrinsics.unaligned_store((^T3)(rawptr(ptr + size_of(cb) + size_of(p) + size_of(p2))), p3) +} diff --git a/core/net/addr.odin b/core/net/addr.odin index 6e2881ac8..d29b46b65 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -1,4 +1,3 @@ -#+build windows, linux, darwin, freebsd package net /* @@ -22,7 +21,6 @@ package net import "core:strconv" import "core:strings" -import "core:fmt" /* Expects an IPv4 address with no leading or trailing whitespace: @@ -473,13 +471,20 @@ join_port :: proc(address_or_host: string, port: int, allocator := context.alloc addr := parse_address(addr_or_host) if addr == nil { // hostname - fmt.sbprintf(&b, "%v:%v", addr_or_host, port) + strings.write_string(&b, addr_or_host) + strings.write_string(&b, ":") + strings.write_int(&b, port) } else { switch _ in addr { case IP4_Address: - fmt.sbprintf(&b, "%v:%v", address_to_string(addr), port) + strings.write_string(&b, address_to_string(addr)) + strings.write_string(&b, ":") + strings.write_int(&b, port) case IP6_Address: - fmt.sbprintf(&b, "[%v]:%v", address_to_string(addr), port) + strings.write_string(&b, "[") + strings.write_string(&b, address_to_string(addr)) + strings.write_string(&b, "]:") + strings.write_int(&b, port) } } return strings.to_string(b) @@ -509,7 +514,13 @@ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> b := strings.builder_make(allocator) switch v in addr { case IP4_Address: - fmt.sbprintf(&b, "%v.%v.%v.%v", v[0], v[1], v[2], v[3]) + strings.write_uint(&b, uint(v[0])) + strings.write_byte(&b, '.') + strings.write_uint(&b, uint(v[1])) + strings.write_byte(&b, '.') + strings.write_uint(&b, uint(v[2])) + strings.write_byte(&b, '.') + strings.write_uint(&b, uint(v[3])) case IP6_Address: // First find the longest run of zeroes. Zero_Run :: struct { @@ -563,25 +574,33 @@ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> for val, i in v { if best.start == i || best.end == i { // For the left and right side of the best zero run, print a `:`. - fmt.sbprint(&b, ":") + strings.write_string(&b, ":") } else if i < best.start { /* If we haven't made it to the best run yet, print the digit. Make sure we only print a `:` after the digit if it's not immediately followed by the run's own leftmost `:`. */ - fmt.sbprintf(&b, "%x", val) + + buf: [32]byte + str := strconv.write_bits(buf[:], u64(val), 16, false, size_of(val), strconv.digits, {}) + strings.write_string(&b, str) + if i < best.start - 1 { - fmt.sbprintf(&b, ":") + strings.write_string(&b, ":") } } else if i > best.end { /* If there are any digits after the zero run, print them. But don't print the `:` at the end of the IP number. */ - fmt.sbprintf(&b, "%x", val) + + buf: [32]byte + str := strconv.write_bits(buf[:], u64(val), 16, false, size_of(val), strconv.digits, {}) + strings.write_string(&b, str) + if i != 7 { - fmt.sbprintf(&b, ":") + strings.write_string(&b, ":") } } } @@ -598,8 +617,15 @@ endpoint_to_string :: proc(ep: Endpoint, allocator := context.temp_allocator) -> s := address_to_string(ep.address, context.temp_allocator) b := strings.builder_make(allocator) switch a in ep.address { - case IP4_Address: fmt.sbprintf(&b, "%v:%v", s, ep.port) - case IP6_Address: fmt.sbprintf(&b, "[%v]:%v", s, ep.port) + case IP4_Address: + strings.write_string(&b, s) + strings.write_string(&b, ":") + strings.write_int(&b, ep.port) + case IP6_Address: + strings.write_string(&b, "[") + strings.write_string(&b, s) + strings.write_string(&b, "]:") + strings.write_int(&b, ep.port) } return strings.to_string(b) } diff --git a/core/net/common.odin b/core/net/common.odin index 70523050f..2758a7359 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -1,4 +1,3 @@ -#+build windows, linux, darwin, freebsd package net /* @@ -91,6 +90,7 @@ Parse_Endpoint_Error :: enum u32 { Resolve_Error :: enum u32 { None = 0, Unable_To_Resolve = 1, + Allocation_Failure, } DNS_Error :: enum u32 { @@ -144,11 +144,11 @@ Address :: union {IP4_Address, IP6_Address} IP4_Loopback :: IP4_Address{127, 0, 0, 1} IP6_Loopback :: IP6_Address{0, 0, 0, 0, 0, 0, 0, 1} -IP4_Any := IP4_Address{} -IP6_Any := IP6_Address{} +IP4_Any :: IP4_Address{} +IP6_Any :: IP6_Address{} -IP4_mDNS_Broadcast := Endpoint{address=IP4_Address{224, 0, 0, 251}, port=5353} -IP6_mDNS_Broadcast := Endpoint{address=IP6_Address{65282, 0, 0, 0, 0, 0, 0, 251}, port = 5353} +IP4_mDNS_Broadcast :: Endpoint{address=IP4_Address{224, 0, 0, 251}, port=5353} +IP6_mDNS_Broadcast :: Endpoint{address=IP6_Address{65282, 0, 0, 0, 0, 0, 0, 251}, port = 5353} Endpoint :: struct { address: Address, diff --git a/core/net/dns.odin b/core/net/dns.odin index 540991fe7..983f82681 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -1,4 +1,3 @@ -#+build windows, linux, darwin, freebsd package net /* @@ -22,13 +21,18 @@ package net Haesbaert: Security fixes */ -@(require) import "base:runtime" +@(require) +import "base:runtime" + +import "core:bufio" +import "core:io" +import "core:math/rand" import "core:mem" import "core:strings" import "core:time" -import "core:os" -import "core:math/rand" -@(require) import "core:sync" + +@(require) +import "core:sync" dns_config_initialized: sync.Once when ODIN_OS == .Windows { @@ -42,20 +46,12 @@ when ODIN_OS == .Windows { hosts_file = "/etc/hosts", } } else { - #panic("Please add a configuration for this OS.") + DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{} } -/* - Replaces environment placeholders in `dns_configuration`. Only necessary on Windows. - Is automatically called, once, by `get_dns_records_*`. -*/ -@(private) init_dns_configuration :: proc() { when ODIN_OS == .Windows { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - val := os.replace_environment_placeholders(dns_configuration.hosts_file, context.temp_allocator) - copy(dns_configuration.hosts_file_buf[:], val) - dns_configuration.hosts_file = string(dns_configuration.hosts_file_buf[:len(val)]) + _init_dns_configuration() } } @@ -178,9 +174,7 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net See `destroy_records`. */ get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { - when ODIN_OS == .Windows { - sync.once_do(&dns_config_initialized, init_dns_configuration) - } + init_dns_configuration() return _get_dns_records_os(hostname, type, allocator) } @@ -196,51 +190,14 @@ get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocat See `destroy_records`. */ get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { - when ODIN_OS == .Windows { - sync.once_do(&dns_config_initialized, init_dns_configuration) - } + init_dns_configuration() context.allocator = allocator - if type != .SRV { - // NOTE(tetra): 'hostname' can contain underscores when querying SRV records - ok := validate_hostname(hostname) - if !ok { - return nil, .Invalid_Hostname_Error - } - } - - hdr := DNS_Header{ - id = u16be(rand.uint32()), - is_response = false, - opcode = 0, - is_authoritative = false, - is_truncated = false, - is_recursion_desired = true, - is_recursion_available = false, - response_code = DNS_Response_Code.No_Error, - } + id := u16be(rand.uint32()) + dns_packet_buf: [DNS_PACKET_MIN_LEN]byte = --- + dns_packet := make_dns_packet(dns_packet_buf[:], id, hostname, type) or_return - id, bits := pack_dns_header(hdr) - dns_hdr := [6]u16be{} - dns_hdr[0] = id - dns_hdr[1] = bits - dns_hdr[2] = 1 - - dns_query := [2]u16be{ u16be(type), 1 } - - output := [(size_of(u16be) * 6) + NAME_MAX + (size_of(u16be) * 2)]u8{} - b := strings.builder_from_slice(output[:]) - - strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_hdr[:])) - ok := encode_hostname(&b, hostname) - if !ok { - return nil, .Invalid_Hostname_Error - } - strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_query[:])) - - dns_packet := output[:strings.builder_len(b)] - - dns_response_buf := [4096]u8{} + dns_response_buf: [4096]u8 = --- dns_response: []u8 for name_server in name_servers { conn, sock_err := make_unbound_udp_socket(family_from_endpoint(name_server)) @@ -283,6 +240,42 @@ get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type return } +DNS_PACKET_MIN_LEN :: (size_of(u16be) * 6) + NAME_MAX + (size_of(u16be) * 2) + +make_dns_packet :: proc(buf: []byte, id: u16be, hostname: string, type: DNS_Record_Type) -> (packet: []byte, err: DNS_Error) { + assert(len(buf) >= DNS_PACKET_MIN_LEN) + + hdr := DNS_Header{ + id = id, + is_response = false, + opcode = 0, + is_authoritative = false, + is_truncated = false, + is_recursion_desired = true, + is_recursion_available = false, + response_code = DNS_Response_Code.No_Error, + } + + _, bits := pack_dns_header(hdr) + dns_hdr := [6]u16be{} + dns_hdr[0] = id + dns_hdr[1] = bits + dns_hdr[2] = 1 + + dns_query := [2]u16be{ u16be(type), 1 } + + b := strings.builder_from_slice(buf[:]) + + strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_hdr[:])) + ok := encode_hostname(&b, hostname) + if !ok { + return nil, .Invalid_Hostname_Error + } + strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_query[:])) + + return buf[:strings.builder_len(b)], nil +} + // `records` slice is also destroyed. destroy_dns_records :: proc(records: []DNS_Record, allocator := context.allocator) { context.allocator = allocator @@ -364,13 +357,8 @@ unpack_dns_header :: proc(id: u16be, bits: u16be) -> (hdr: DNS_Header) { return hdr } -load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) { - context.allocator = allocator - - res := os.read_entire_file_from_filename(resolv_conf_path) or_return - defer delete(res) - resolv_str := string(res) - +parse_resolv_conf :: proc(resolv_str: string, allocator := context.allocator) -> (name_servers: []Endpoint) { + resolv_str := resolv_str id_str := "nameserver" id_len := len(id_str) @@ -401,41 +389,51 @@ load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocato append(&_name_servers, endpoint) } - return _name_servers[:], true + return _name_servers[:] } -load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) { - context.allocator = allocator +parse_hosts :: proc(stream: io.Stream, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) { + s := bufio.scanner_init(&{}, stream, allocator) + defer bufio.scanner_destroy(s) - res := os.read_entire_file_from_filename(hosts_file_path, allocator) or_return - defer delete(res) + resize(&s.buf, 256) - _hosts := make([dynamic]DNS_Host_Entry, 0, allocator) - hosts_str := string(res) - for line in strings.split_lines_iterator(&hosts_str) { - if len(line) == 0 || line[0] == '#' { - continue + _hosts: [dynamic]DNS_Host_Entry + _hosts.allocator = allocator + defer if !ok { + for host in _hosts { + delete(host.name, allocator) } + delete(_hosts) + } - splits := strings.fields(line) - defer delete(splits) + for bufio.scanner_scan(s) { + line := bufio.scanner_text(s) - (len(splits) >= 2) or_continue + line, _, _ = strings.partition(line, "#") + (len(line) > 0) or_continue + + ip_str := strings.fields_iterator(&line) or_continue - ip_str := splits[0] addr := parse_address(ip_str) - if addr == nil { - continue - } + (addr != nil) or_continue - for hostname in splits[1:] { - if len(hostname) != 0 { - append(&_hosts, DNS_Host_Entry{hostname, addr}) - } + for hostname in strings.fields_iterator(&line) { + (len(hostname) > 0) or_continue + + clone, alloc_err := strings.clone(hostname, allocator) + if alloc_err != nil { return } + + _, alloc_err = append(&_hosts, DNS_Host_Entry{clone, addr}) + if alloc_err != nil { return } } } - return _hosts[:], true + if bufio.scanner_error(s) != nil { return } + + hosts = _hosts[:] + ok = true + return } // www.google.com -> 3www6google3com0 @@ -594,7 +592,7 @@ decode_hostname :: proc(packet: []u8, start_idx: int, allocator := context.alloc // Uses RFC 952 & RFC 1123 validate_hostname :: proc(hostname: string) -> (ok: bool) { - if len(hostname) > 255 || len(hostname) == 0 { + if len(hostname) > NAME_MAX || len(hostname) == 0 { return } @@ -604,7 +602,7 @@ validate_hostname :: proc(hostname: string) -> (ok: bool) { _hostname := hostname for label in strings.split_iterator(&_hostname, ".") { - if len(label) > 63 || len(label) == 0 { + if len(label) > LABEL_MAX || len(label) == 0 { return } @@ -868,4 +866,4 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator xid = hdr.id return _records[:], xid, true -}
\ No newline at end of file +} diff --git a/core/net/dns_os.odin b/core/net/dns_os.odin new file mode 100644 index 000000000..19db0097a --- /dev/null +++ b/core/net/dns_os.odin @@ -0,0 +1,24 @@ +#+build darwin, freebsd, openbsd, netbsd, linux, windows, wasi +#+private +package net + +import "core:os" + +load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) { + context.allocator = allocator + + res := os.read_entire_file_from_filename(resolv_conf_path) or_return + defer delete(res) + resolv_str := string(res) + + return parse_resolv_conf(resolv_str), true +} + +load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) { + hosts_file, err := os.open(hosts_file_path) + if err != nil { return } + defer os.close(hosts_file) + + return parse_hosts(os.stream_from_handle(hosts_file), allocator) +} + diff --git a/core/net/dns_others.odin b/core/net/dns_others.odin new file mode 100644 index 000000000..842e833aa --- /dev/null +++ b/core/net/dns_others.odin @@ -0,0 +1,12 @@ +#+build !windows +#+build !linux +#+build !darwin +#+build !freebsd +#+build !netbsd +#+build !openbsd +package net + +@(private) +_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { + return +} diff --git a/core/net/dns_unix.odin b/core/net/dns_unix.odin index fbc1909cd..be95b8341 100644 --- a/core/net/dns_unix.odin +++ b/core/net/dns_unix.odin @@ -1,4 +1,4 @@ -#+build linux, darwin, freebsd +#+build linux, darwin, freebsd, openbsd, netbsd package net /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -42,14 +42,19 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator : } hosts, hosts_ok := load_hosts(dns_configuration.hosts_file) - defer delete(hosts) if !hosts_ok { return nil, .Invalid_Hosts_Config_Error } + defer { + for h in hosts { + delete(h.name) + } + delete(hosts) + } host_overrides := make([dynamic]DNS_Record) for host in hosts { - if strings.compare(host.name, hostname) != 0 { + if host.name != hostname { continue } @@ -79,4 +84,4 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator : } return get_dns_records_from_nameservers(hostname, type, name_servers, host_overrides[:]) -}
\ No newline at end of file +} diff --git a/core/net/dns_windows.odin b/core/net/dns_windows.odin index b1e7da97d..393df5fa7 100644 --- a/core/net/dns_windows.odin +++ b/core/net/dns_windows.odin @@ -20,11 +20,29 @@ package net Feoramund: FreeBSD platform code */ -import "core:strings" +import "base:runtime" + import "core:mem" +import "core:os" +import "core:strings" +import "core:sync" import win "core:sys/windows" +/* + Replaces environment placeholders in `dns_configuration`. Only necessary on Windows. + Is automatically called, once, by `get_dns_records_*`. +*/ +@(private) +_init_dns_configuration :: proc() { + sync.once_do(&dns_config_initialized, proc() { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + val := os.replace_environment_placeholders(dns_configuration.hosts_file, context.temp_allocator) + copy(dns_configuration.hosts_file_buf[:], val) + dns_configuration.hosts_file = string(dns_configuration.hosts_file_buf[:len(val)]) + }) +} + @(private) _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { context.allocator = allocator @@ -171,4 +189,4 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator : records = recs[:] return -}
\ No newline at end of file +} diff --git a/core/net/errors.odin b/core/net/errors.odin index de53640fc..28153375c 100644 --- a/core/net/errors.odin +++ b/core/net/errors.odin @@ -139,6 +139,11 @@ Accept_Error :: enum i32 { Unknown, } +Recv_Error :: union #shared_nil { + TCP_Recv_Error, + UDP_Recv_Error, +} + TCP_Recv_Error :: enum i32 { None, // No network connection, or the network stack is not initialized. @@ -187,6 +192,11 @@ UDP_Recv_Error :: enum i32 { Unknown, } +Send_Error :: union #shared_nil { + TCP_Send_Error, + UDP_Send_Error, +} + TCP_Send_Error :: enum i32 { None, // No network connection, or the network stack is not initialized. diff --git a/core/net/errors_others.odin b/core/net/errors_others.odin index b80ead79c..3a752d58e 100644 --- a/core/net/errors_others.odin +++ b/core/net/errors_others.odin @@ -2,6 +2,8 @@ #+build !linux #+build !freebsd #+build !windows +#+build !netbsd +#+build !openbsd package net @(private="file", thread_local) @@ -18,10 +20,3 @@ _last_platform_error_string :: proc() -> string { _set_last_platform_error :: proc(err: i32) { _last_error = err } - -Parse_Endpoint_Error :: enum u32 { - None = 0, - Bad_Port = 1, - Bad_Address, - Bad_Hostname, -}
\ No newline at end of file diff --git a/core/net/errors_darwin.odin b/core/net/errors_posix.odin index a35e96bc0..b59cbc30b 100644 --- a/core/net/errors_darwin.odin +++ b/core/net/errors_posix.odin @@ -1,4 +1,4 @@ -#+build darwin +#+build darwin, netbsd, openbsd package net /* diff --git a/core/net/errors_windows.odin b/core/net/errors_windows.odin index 83c45ee7f..6d3724c82 100644 --- a/core/net/errors_windows.odin +++ b/core/net/errors_windows.odin @@ -63,7 +63,7 @@ _dial_error :: proc() -> Dial_Error { return .Already_Connecting case .WSAEADDRNOTAVAIL, .WSAEAFNOSUPPORT, .WSAEFAULT, .WSAENOTSOCK, .WSAEINPROGRESS, .WSAEINVAL: return .Invalid_Argument - case .WSAECONNREFUSED: + case .WSAECONNREFUSED, .CONNECTION_REFUSED: return .Refused case .WSAEISCONN: return .Already_Connected @@ -122,7 +122,7 @@ _accept_error :: proc() -> Accept_Error { return .Aborted case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK: return .Invalid_Argument - case .WSAEINTR: + case .WSAEINTR, .OPERATION_ABORTED: return .Interrupted case .WSAEINVAL: return .Not_Listening diff --git a/core/net/interface_others.odin b/core/net/interface_others.odin new file mode 100644 index 000000000..9a8a141df --- /dev/null +++ b/core/net/interface_others.odin @@ -0,0 +1,11 @@ +#+build !darwin +#+build !linux +#+build !freebsd +#+build !windows +#+build !netbsd +#+build !openbsd +package net + +_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) { + return +} diff --git a/core/net/interface_darwin.odin b/core/net/interface_posix.odin index f18cff995..202951b29 100644 --- a/core/net/interface_darwin.odin +++ b/core/net/interface_posix.odin @@ -1,4 +1,4 @@ -#+build darwin +#+build darwin, openbsd, netbsd package net /* @@ -117,32 +117,47 @@ IF_Flag :: enum u32 { BROADCAST, DEBUG, LOOPBACK, - POINTTOPOINT, - NOTRAILERS, - RUNNING, - NOARP, - PROMISC, - ALLMULTI, - OACTIVE, - SIMPLEX, - LINK0, - LINK1, - LINK2, - MULTICAST, + // NOTE: different order on other BSDs but we don't even need these. + // POINTTOPOINT, + // NOTRAILERS, + // RUNNING, + // NOARP, + // PROMISC, + // ALLMULTI, + // OACTIVE, + // SIMPLEX, + // LINK0, + // LINK1, + // LINK2, + // MULTICAST, } @(private) IF_Flags :: bit_set[IF_Flag; u32] -@(private) -ifaddrs :: struct { - next: ^ifaddrs, - name: cstring, - flags: IF_Flags, - addr: ^posix.sockaddr, - netmask: ^posix.sockaddr, - dstaddr: ^posix.sockaddr, - data: rawptr, +when ODIN_OS == .Darwin || ODIN_OS == .OpenBSD { + @(private) + ifaddrs :: struct { + next: ^ifaddrs, + name: cstring, + flags: IF_Flags, + addr: ^posix.sockaddr, + netmask: ^posix.sockaddr, + dstaddr: ^posix.sockaddr, + data: rawptr, + } +} else when ODIN_OS == .NetBSD { + @(private) + ifaddrs :: struct { + next: ^ifaddrs, + name: cstring, + flags: IF_Flags, + addr: ^posix.sockaddr, + netmask: ^posix.sockaddr, + dstaddr: ^posix.sockaddr, + data: rawptr, + addrflags: u32, + } } @(private) diff --git a/core/net/socket.odin b/core/net/socket.odin index edb47cd0b..e2f96e2f3 100644 --- a/core/net/socket.odin +++ b/core/net/socket.odin @@ -1,4 +1,3 @@ -#+build windows, linux, darwin, freebsd package net /* @@ -20,6 +19,35 @@ package net Feoramund: FreeBSD platform code */ +Socket_Option :: enum i32 { + Broadcast = i32(_SOCKET_OPTION_BROADCAST), + Reuse_Address = i32(_SOCKET_OPTION_REUSE_ADDRESS), + Keep_Alive = i32(_SOCKET_OPTION_KEEP_ALIVE), + Out_Of_Bounds_Data_Inline = i32(_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE), + Linger = i32(_SOCKET_OPTION_LINGER), + Receive_Buffer_Size = i32(_SOCKET_OPTION_RECEIVE_BUFFER_SIZE), + Send_Buffer_Size = i32(_SOCKET_OPTION_SEND_BUFFER_SIZE), + Receive_Timeout = i32(_SOCKET_OPTION_RECEIVE_TIMEOUT), + Send_Timeout = i32(_SOCKET_OPTION_SEND_TIMEOUT), + + TCP_Nodelay = i32(_SOCKET_OPTION_TCP_NODELAY), + + Use_Loopback = i32(_SOCKET_OPTION_USE_LOOPBACK), + Reuse_Port = i32(_SOCKET_OPTION_REUSE_PORT), + No_SIGPIPE_From_EPIPE = i32(_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE), + Reuse_Port_Load_Balancing = i32(_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING), + + Exclusive_Addr_Use = i32(_SOCKET_OPTION_EXCLUSIVE_ADDR_USE), + Conditional_Accept = i32(_SOCKET_OPTION_CONDITIONAL_ACCEPT), + Dont_Linger = i32(_SOCKET_OPTION_DONT_LINGER), +} + +Shutdown_Manner :: enum i32 { + Receive = i32(_SHUTDOWN_MANNER_RECEIVE), + Send = i32(_SHUTDOWN_MANNER_SEND), + Both = i32(_SHUTDOWN_MANNER_BOTH), +} + any_socket_to_socket :: proc "contextless" (socket: Any_Socket) -> Socket { switch s in socket { case TCP_Socket: return Socket(s) diff --git a/core/net/socket_freebsd.odin b/core/net/socket_freebsd.odin index 504229e73..bd600fd99 100644 --- a/core/net/socket_freebsd.odin +++ b/core/net/socket_freebsd.odin @@ -20,45 +20,35 @@ package net Feoramund: FreeBSD platform code */ -import "core:c" import "core:sys/freebsd" import "core:time" Fd :: freebsd.Fd -Socket_Option :: enum c.int { - // TODO: Test and implement more socket options. - // DEBUG - Reuse_Address = cast(c.int)freebsd.Socket_Option.REUSEADDR, - Keep_Alive = cast(c.int)freebsd.Socket_Option.KEEPALIVE, - // DONTROUTE - Broadcast = cast(c.int)freebsd.Socket_Option.BROADCAST, - Use_Loopback = cast(c.int)freebsd.Socket_Option.USELOOPBACK, - Linger = cast(c.int)freebsd.Socket_Option.LINGER, - Out_Of_Bounds_Data_Inline = cast(c.int)freebsd.Socket_Option.OOBINLINE, - Reuse_Port = cast(c.int)freebsd.Socket_Option.REUSEPORT, - // TIMESTAMP - No_SIGPIPE_From_EPIPE = cast(c.int)freebsd.Socket_Option.NOSIGPIPE, - // ACCEPTFILTER - // BINTIME - // NO_OFFLOAD - // NO_DDP - Reuse_Port_Load_Balancing = cast(c.int)freebsd.Socket_Option.REUSEPORT_LB, - // RERROR - - Send_Buffer_Size = cast(c.int)freebsd.Socket_Option.SNDBUF, - Receive_Buffer_Size = cast(c.int)freebsd.Socket_Option.RCVBUF, - // SNDLOWAT - // RCVLOWAT - Send_Timeout = cast(c.int)freebsd.Socket_Option.SNDTIMEO, - Receive_Timeout = cast(c.int)freebsd.Socket_Option.RCVTIMEO, -} +_SOCKET_OPTION_BROADCAST :: freebsd.Socket_Option.BROADCAST +_SOCKET_OPTION_REUSE_ADDRESS :: freebsd.Socket_Option.REUSEADDR +_SOCKET_OPTION_KEEP_ALIVE :: freebsd.Socket_Option.KEEPALIVE +_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE :: freebsd.Socket_Option.OOBINLINE +_SOCKET_OPTION_LINGER :: freebsd.Socket_Option.LINGER +_SOCKET_OPTION_RECEIVE_BUFFER_SIZE :: freebsd.Socket_Option.RCVBUF +_SOCKET_OPTION_SEND_BUFFER_SIZE :: freebsd.Socket_Option.SNDBUF +_SOCKET_OPTION_RECEIVE_TIMEOUT :: freebsd.Socket_Option.RCVTIMEO +_SOCKET_OPTION_SEND_TIMEOUT :: freebsd.Socket_Option.SNDTIMEO -Shutdown_Manner :: enum c.int { - Receive = cast(c.int)freebsd.Shutdown_Method.RD, - Send = cast(c.int)freebsd.Shutdown_Method.WR, - Both = cast(c.int)freebsd.Shutdown_Method.RDWR, -} +_SOCKET_OPTION_TCP_NODELAY :: -1 + +_SOCKET_OPTION_USE_LOOPBACK :: freebsd.Socket_Option.USELOOPBACK +_SOCKET_OPTION_REUSE_PORT :: freebsd.Socket_Option.REUSEPORT +_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE :: freebsd.Socket_Option.NOSIGPIPE +_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING :: freebsd.Socket_Option.REUSEPORT_LB + +_SOCKET_OPTION_EXCLUSIVE_ADDR_USE :: -1 +_SOCKET_OPTION_CONDITIONAL_ACCEPT :: -1 +_SOCKET_OPTION_DONT_LINGER :: -1 + +_SHUTDOWN_MANNER_RECEIVE :: freebsd.Shutdown_Method.RD +_SHUTDOWN_MANNER_SEND :: freebsd.Shutdown_Method.WR +_SHUTDOWN_MANNER_BOTH :: freebsd.Shutdown_Method.RDWR @(private) _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) { @@ -272,7 +262,7 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc : ptr: rawptr len: freebsd.socklen_t - switch option { + #partial switch option { case .Reuse_Address, .Keep_Alive, @@ -344,7 +334,7 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc : ptr = &int_value len = size_of(int_value) case: - unimplemented("set_option() option not yet implemented", loc) + return .Invalid_Option } real_socket := any_socket_to_socket(socket) @@ -391,7 +381,7 @@ _endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: freebsd.Socket_Address } case IP6_Address: (cast(^freebsd.Socket_Address_Internet6)(&sockaddr))^ = { - len = size_of(freebsd.Socket_Address_Internet), + len = size_of(freebsd.Socket_Address_Internet6), family = .INET6, port = cast(freebsd.in_port_t)ep.port, addr = transmute(freebsd.IP6_Address)addr, diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index 9719ff61b..8348ce114 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -21,28 +21,33 @@ package net Feoramund: FreeBSD platform code */ -import "core:c" import "core:time" import "core:sys/linux" -Socket_Option :: enum c.int { - Reuse_Address = c.int(linux.Socket_Option.REUSEADDR), - Keep_Alive = c.int(linux.Socket_Option.KEEPALIVE), - Out_Of_Bounds_Data_Inline = c.int(linux.Socket_Option.OOBINLINE), - TCP_Nodelay = c.int(linux.Socket_TCP_Option.NODELAY), - Linger = c.int(linux.Socket_Option.LINGER), - Receive_Buffer_Size = c.int(linux.Socket_Option.RCVBUF), - Send_Buffer_Size = c.int(linux.Socket_Option.SNDBUF), - Receive_Timeout = c.int(linux.Socket_Option.RCVTIMEO), - Send_Timeout = c.int(linux.Socket_Option.SNDTIMEO), - Broadcast = c.int(linux.Socket_Option.BROADCAST), -} +_SOCKET_OPTION_BROADCAST :: linux.Socket_Option.BROADCAST +_SOCKET_OPTION_REUSE_ADDRESS :: linux.Socket_Option.REUSEADDR +_SOCKET_OPTION_KEEP_ALIVE :: linux.Socket_Option.KEEPALIVE +_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE :: linux.Socket_Option.OOBINLINE +_SOCKET_OPTION_LINGER :: linux.Socket_Option.LINGER +_SOCKET_OPTION_RECEIVE_BUFFER_SIZE :: linux.Socket_Option.RCVBUF +_SOCKET_OPTION_SEND_BUFFER_SIZE :: linux.Socket_Option.SNDBUF +_SOCKET_OPTION_RECEIVE_TIMEOUT :: linux.Socket_Option.RCVTIMEO +_SOCKET_OPTION_SEND_TIMEOUT :: linux.Socket_Option.SNDTIMEO -Shutdown_Manner :: enum c.int { - Receive = c.int(linux.Shutdown_How.RD), - Send = c.int(linux.Shutdown_How.WR), - Both = c.int(linux.Shutdown_How.RDWR), -} +_SOCKET_OPTION_TCP_NODELAY :: linux.Socket_TCP_Option.NODELAY + +_SOCKET_OPTION_USE_LOOPBACK :: -1 +_SOCKET_OPTION_REUSE_PORT :: -1 +_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE :: -1 +_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING :: -1 + +_SOCKET_OPTION_EXCLUSIVE_ADDR_USE :: -1 +_SOCKET_OPTION_CONDITIONAL_ACCEPT :: -1 +_SOCKET_OPTION_DONT_LINGER :: -1 + +_SHUTDOWN_MANNER_RECEIVE :: linux.Shutdown_How.RD +_SHUTDOWN_MANNER_SEND :: linux.Shutdown_How.WR +_SHUTDOWN_MANNER_BOTH :: linux.Shutdown_How.RDWR // Wrappers and unwrappers for system-native types @@ -347,7 +352,7 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := int_value: i32 timeval_value: linux.Time_Val errno: linux.Errno - switch option { + #partial switch option { case .Reuse_Address, .Keep_Alive, @@ -400,10 +405,14 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := panic("set_option() value must be an integer here", loc) } errno = linux.setsockopt(os_sock, level, int(option), &int_value) + case: + return .Invalid_Socket } + if errno != .NONE { return _socket_option_error(errno) } + return nil } diff --git a/core/net/socket_others.odin b/core/net/socket_others.odin new file mode 100644 index 000000000..61cf7240e --- /dev/null +++ b/core/net/socket_others.odin @@ -0,0 +1,105 @@ +#+build !darwin +#+build !linux +#+build !freebsd +#+build !windows +#+build !netbsd +#+build !openbsd +#+private +package net + +_SOCKET_OPTION_BROADCAST :: -1 +_SOCKET_OPTION_REUSE_ADDRESS :: -1 +_SOCKET_OPTION_KEEP_ALIVE :: -1 +_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE :: -1 +_SOCKET_OPTION_LINGER :: -1 +_SOCKET_OPTION_RECEIVE_BUFFER_SIZE :: -1 +_SOCKET_OPTION_SEND_BUFFER_SIZE :: -1 +_SOCKET_OPTION_RECEIVE_TIMEOUT :: -1 +_SOCKET_OPTION_SEND_TIMEOUT :: -1 + +_SOCKET_OPTION_TCP_NODELAY :: -1 + +_SOCKET_OPTION_USE_LOOPBACK :: -1 +_SOCKET_OPTION_REUSE_PORT :: -1 +_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE :: -1 +_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING :: -1 + +_SOCKET_OPTION_EXCLUSIVE_ADDR_USE :: -1 +_SOCKET_OPTION_CONDITIONAL_ACCEPT :: -1 +_SOCKET_OPTION_DONT_LINGER :: -1 + +_SHUTDOWN_MANNER_RECEIVE :: -1 +_SHUTDOWN_MANNER_SEND :: -1 +_SHUTDOWN_MANNER_BOTH :: -1 + +_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (sock: TCP_Socket, err: Network_Error) { + err = Create_Socket_Error.Network_Unreachable + return +} + +_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (sock: Any_Socket, err: Create_Socket_Error) { + err = .Network_Unreachable + return +} + +_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Bind_Error) { + err = .Network_Unreachable + return +} + +_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { + err = Create_Socket_Error.Network_Unreachable + return +} + +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) { + err = .Network_Unreachable + return +} + +_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) { + err = .Network_Unreachable + return +} + +_accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) { + err = .Network_Unreachable + return +} + +_close :: proc(skt: Any_Socket) { +} + +_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) { + err = .Network_Unreachable + return +} + +_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) { + err = .Network_Unreachable + return +} + +_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) { + err = .Network_Unreachable + return +} + +_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) { + err = .Network_Unreachable + return +} + +_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) { + err = .Network_Unreachable + return +} + +_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error { + return .Network_Unreachable +} + +_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) { + err = .Network_Unreachable + return +} diff --git a/core/net/socket_darwin.odin b/core/net/socket_posix.odin index 8e01eb4a8..243b2e06f 100644 --- a/core/net/socket_darwin.odin +++ b/core/net/socket_posix.odin @@ -1,4 +1,4 @@ -#+build darwin +#+build darwin, netbsd, openbsd package net /* @@ -20,28 +20,33 @@ package net Feoramund: FreeBSD platform code */ -import "core:c" import "core:sys/posix" import "core:time" -Socket_Option :: enum c.int { - Broadcast = c.int(posix.Sock_Option.BROADCAST), - Reuse_Address = c.int(posix.Sock_Option.REUSEADDR), - Keep_Alive = c.int(posix.Sock_Option.KEEPALIVE), - Out_Of_Bounds_Data_Inline = c.int(posix.Sock_Option.OOBINLINE), - TCP_Nodelay = c.int(posix.TCP_NODELAY), - Linger = c.int(posix.Sock_Option.LINGER), - Receive_Buffer_Size = c.int(posix.Sock_Option.RCVBUF), - Send_Buffer_Size = c.int(posix.Sock_Option.SNDBUF), - Receive_Timeout = c.int(posix.Sock_Option.RCVTIMEO), - Send_Timeout = c.int(posix.Sock_Option.SNDTIMEO), -} +_SOCKET_OPTION_BROADCAST :: posix.Sock_Option.BROADCAST +_SOCKET_OPTION_REUSE_ADDRESS :: posix.Sock_Option.REUSEADDR +_SOCKET_OPTION_KEEP_ALIVE :: posix.Sock_Option.KEEPALIVE +_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE :: posix.Sock_Option.OOBINLINE +_SOCKET_OPTION_LINGER :: posix.Sock_Option.LINGER +_SOCKET_OPTION_RECEIVE_BUFFER_SIZE :: posix.Sock_Option.RCVBUF +_SOCKET_OPTION_SEND_BUFFER_SIZE :: posix.Sock_Option.SNDBUF +_SOCKET_OPTION_RECEIVE_TIMEOUT :: posix.Sock_Option.RCVTIMEO +_SOCKET_OPTION_SEND_TIMEOUT :: posix.Sock_Option.SNDTIMEO -Shutdown_Manner :: enum c.int { - Receive = c.int(posix.SHUT_RD), - Send = c.int(posix.SHUT_WR), - Both = c.int(posix.SHUT_RDWR), -} +_SOCKET_OPTION_TCP_NODELAY :: posix.TCP_NODELAY + +_SOCKET_OPTION_USE_LOOPBACK :: -1 +_SOCKET_OPTION_REUSE_PORT :: -1 +_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE :: -1 +_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING :: -1 + +_SOCKET_OPTION_EXCLUSIVE_ADDR_USE :: -1 +_SOCKET_OPTION_CONDITIONAL_ACCEPT :: -1 +_SOCKET_OPTION_DONT_LINGER :: -1 + +_SHUTDOWN_MANNER_RECEIVE :: posix.SHUT_RD +_SHUTDOWN_MANNER_SEND :: posix.SHUT_WR +_SHUTDOWN_MANNER_BOTH :: posix.SHUT_RDWR @(private) _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) { @@ -273,7 +278,7 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca ptr: rawptr len: posix.socklen_t - switch option { + #partial switch option { case .Broadcast, .Reuse_Address, @@ -327,6 +332,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca } ptr = &int_value len = size_of(int_value) + case: + return .Invalid_Option } skt := any_socket_to_socket(s) diff --git a/core/net/socket_windows.odin b/core/net/socket_windows.odin index 6dd2f0458..4eea0ea65 100644 --- a/core/net/socket_windows.odin +++ b/core/net/socket_windows.odin @@ -24,59 +24,30 @@ import "core:c" import win "core:sys/windows" import "core:time" -Socket_Option :: enum c.int { - // bool: Whether the address that this socket is bound to can be reused by other sockets. - // This allows you to bypass the cooldown period if a program dies while the socket is bound. - Reuse_Address = win.SO_REUSEADDR, - - // bool: Whether other programs will be inhibited from binding the same endpoint as this socket. - Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE, - - // bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding - Keep_Alive = win.SO_KEEPALIVE, - - // bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted. - Conditional_Accept = win.SO_CONDITIONAL_ACCEPT, - - // bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data. - Dont_Linger = win.SO_DONTLINGER, - - // bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data. - Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE, - - // bool: When true, disables send-coalescing, therefore reducing latency. - TCP_Nodelay = win.TCP_NODELAY, - - // win.LINGER: Customizes how long (if at all) the socket will remain open when there - // is some remaining data waiting to be sent, and net.close() is called. - Linger = win.SO_LINGER, - - // win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket. - Receive_Buffer_Size = win.SO_RCVBUF, - - // win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket. - Send_Buffer_Size = win.SO_SNDBUF, - - // win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout. - // For non-blocking sockets, ignored. - // Use a value of zero to potentially wait forever. - Receive_Timeout = win.SO_RCVTIMEO, - - // win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout. - // For non-blocking sockets, ignored. - // Use a value of zero to potentially wait forever. - Send_Timeout = win.SO_SNDTIMEO, - - // bool: Allow sending to, receiving from, and binding to, a broadcast address. - Broadcast = win.SO_BROADCAST, -} - - -Shutdown_Manner :: enum c.int { - Receive = win.SD_RECEIVE, - Send = win.SD_SEND, - Both = win.SD_BOTH, -} +_SOCKET_OPTION_BROADCAST :: win.SO_BROADCAST +_SOCKET_OPTION_REUSE_ADDRESS :: win.SO_REUSEADDR +_SOCKET_OPTION_KEEP_ALIVE :: win.SO_KEEPALIVE +_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE :: win.SO_OOBINLINE +_SOCKET_OPTION_LINGER :: win.SO_LINGER +_SOCKET_OPTION_RECEIVE_BUFFER_SIZE :: win.SO_RCVBUF +_SOCKET_OPTION_SEND_BUFFER_SIZE :: win.SO_SNDBUF +_SOCKET_OPTION_RECEIVE_TIMEOUT :: win.SO_RCVTIMEO +_SOCKET_OPTION_SEND_TIMEOUT :: win.SO_SNDTIMEO + +_SOCKET_OPTION_TCP_NODELAY :: win.TCP_NODELAY + +_SOCKET_OPTION_USE_LOOPBACK :: -1 +_SOCKET_OPTION_REUSE_PORT :: -1 +_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE :: -1 +_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING :: -1 + +_SOCKET_OPTION_EXCLUSIVE_ADDR_USE :: win.SO_EXCLUSIVEADDRUSE +_SOCKET_OPTION_CONDITIONAL_ACCEPT :: win.SO_CONDITIONAL_ACCEPT +_SOCKET_OPTION_DONT_LINGER :: win.SO_DONTLINGER + +_SHUTDOWN_MANNER_RECEIVE :: win.SD_RECEIVE +_SHUTDOWN_MANNER_SEND :: win.SD_SEND +_SHUTDOWN_MANNER_BOTH :: win.SD_BOTH @(init, private) ensure_winsock_initialized :: proc "contextless" () { @@ -322,7 +293,7 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca ptr: rawptr len: c.int - switch option { + #partial switch option { case .Reuse_Address, .Exclusive_Addr_Use, @@ -383,6 +354,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca } ptr = &int_value len = size_of(int_value) + case: + return .Invalid_Option } socket := any_socket_to_socket(s) diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index a8c198476..6755ca4d0 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -17,6 +17,11 @@ Proc_Inlining :: enum u32 { No_Inline = 2, } +Proc_Tailing :: enum u32 { + None = 0, + Must_Tail = 1, +} + Proc_Calling_Convention_Extra :: enum i32 { Foreign_Block_Default, } @@ -147,6 +152,7 @@ Proc_Lit :: struct { body: ^Stmt, // nil when it represents a foreign procedure tags: Proc_Tags, inlining: Proc_Inlining, + tailing: Proc_Tailing, where_token: tokenizer.Token, where_clauses: []^Expr, } @@ -243,6 +249,7 @@ Matrix_Index_Expr :: struct { Call_Expr :: struct { using node: Expr, inlining: Proc_Inlining, + tailing: Proc_Tailing, expr: ^Expr, open: tokenizer.Pos, args: []^Expr, diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index f4beddcbb..719cb0374 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -1528,8 +1528,8 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { es.expr = ce return es - case "force_inline", "force_no_inline": - expr := parse_inlining_operand(p, true, tag) + case "force_inline", "force_no_inline", "must_tail": + expr := parse_inlining_or_tailing_operand(p, true, tag) es := ast.new(ast.Expr_Stmt, expr.pos, expr) es.expr = expr return es @@ -2235,10 +2235,11 @@ parse_proc_type :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Proc_Type { return pt } -parse_inlining_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^ast.Expr { +parse_inlining_or_tailing_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^ast.Expr { expr := parse_unary_expr(p, lhs) pi := ast.Proc_Inlining.None + pt := ast.Proc_Tailing.None #partial switch tok.kind { case .Inline: pi = .Inline @@ -2250,6 +2251,8 @@ parse_inlining_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^ pi = .Inline case "force_no_inline": pi = .No_Inline + case "must_tail": + pt = .Must_Tail } } @@ -2259,13 +2262,19 @@ parse_inlining_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^ if e.inlining != .None && e.inlining != pi { error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure literal") } + if pt != .None { + error(p, expr.pos, "'#must_tail' can only be applied to a procedure call, not the procedure literal") + } + e.inlining = pi + e.tailing = pt return expr case ^ast.Call_Expr: if e.inlining != .None && e.inlining != pi { error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure call") } e.inlining = pi + e.tailing = pt return expr } } @@ -2451,7 +2460,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { return rt case "force_inline", "force_no_inline": - return parse_inlining_operand(p, lhs, name) + return parse_inlining_or_tailing_operand(p, lhs, name) case: expr := parse_expr(p, lhs) end := expr.pos if expr != nil else end_pos(tok) @@ -2464,7 +2473,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { case .Inline, .No_Inline: tok := advance_token(p) - return parse_inlining_operand(p, lhs, tok) + return parse_inlining_or_tailing_operand(p, lhs, tok) case .Proc: tok := expect_token(p, .Proc) diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 9e7788c31..33446726e 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -67,7 +67,7 @@ File_Flag :: enum { Trunc, Sparse, Inheritable, - + Non_Blocking, Unbuffered_IO, } diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index fb25ca411..924251dfc 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -82,6 +82,7 @@ _open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, if .Excl in flags { sys_flags += {.EXCL} } if .Sync in flags { sys_flags += {.DSYNC} } if .Trunc in flags { sys_flags += {.TRUNC} } + if .Non_Blocking in flags { sys_flags += {.NONBLOCK} } if .Inheritable in flags { sys_flags -= {.CLOEXEC} } fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)transmute(u32)perm) diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin index 874ec7c7d..cdc8e491a 100644 --- a/core/os/os2/file_posix.odin +++ b/core/os/os2/file_posix.odin @@ -61,12 +61,13 @@ _open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, } } - if .Append in flags { sys_flags += {.APPEND} } - if .Create in flags { sys_flags += {.CREAT} } - if .Excl in flags { sys_flags += {.EXCL} } - if .Sync in flags { sys_flags += {.DSYNC} } - if .Trunc in flags { sys_flags += {.TRUNC} } - if .Inheritable in flags { sys_flags -= {.CLOEXEC} } + if .Append in flags { sys_flags += {.APPEND} } + if .Create in flags { sys_flags += {.CREAT} } + if .Excl in flags { sys_flags += {.EXCL} } + if .Sync in flags { sys_flags += {.DSYNC} } + if .Trunc in flags { sys_flags += {.TRUNC} } + if .Non_Blocking in flags { sys_flags += {.NONBLOCK} } + if .Inheritable in flags { sys_flags -= {.CLOEXEC} } temp_allocator := TEMP_ALLOCATOR_GUARD({}) cname := clone_to_cstring(name, temp_allocator) or_return diff --git a/core/os/os2/file_wasi.odin b/core/os/os2/file_wasi.odin index b60cce4be..fc37ef9f2 100644 --- a/core/os/os2/file_wasi.odin +++ b/core/os/os2/file_wasi.odin @@ -185,8 +185,9 @@ _open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, if .Trunc in flags { oflags += {.TRUNC} } fdflags: wasi.fdflags_t - if .Append in flags { fdflags += {.APPEND} } - if .Sync in flags { fdflags += {.SYNC} } + if .Append in flags { fdflags += {.APPEND} } + if .Sync in flags { fdflags += {.SYNC} } + if .Non_Blocking in flags { fdflags += {.NONBLOCK} } // NOTE: rights are adjusted to what this package's functions might want to call. rights: wasi.rights_t diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 9a969f07e..4df9398cc 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -126,7 +126,11 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: Permissions) -> (h // NOTE(bill): Open has just asked to create a file in read-only mode. // If the file already exists, to make it akin to a *nix open call, // the call preserves the existing permissions. - h := win32.CreateFileW(path, access, share_mode, &sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil) + nix_attrs := win32.FILE_ATTRIBUTE_NORMAL + if .Non_Blocking in flags { + nix_attrs |= win32.FILE_FLAG_OVERLAPPED + } + h := win32.CreateFileW(path, access, share_mode, &sa, win32.TRUNCATE_EXISTING, nix_attrs, nil) if h == win32.INVALID_HANDLE { switch e := win32.GetLastError(); e { case win32.ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, win32.ERROR_PATH_NOT_FOUND: @@ -140,6 +144,10 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: Permissions) -> (h } } + if .Non_Blocking in flags { + attrs |= win32.FILE_FLAG_OVERLAPPED + } + h := win32.CreateFileW(path, access, share_mode, &sa, create_mode, attrs, nil) if h == win32.INVALID_HANDLE { return 0, _get_platform_error() diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index f0bc3788e..2c0236428 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -14,7 +14,7 @@ MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right? // // The caller must `close` the file once finished with. @(require_results) -create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) { +create_temp_file :: proc(dir, pattern: string, additional_flags: File_Flags = {}) -> (f: ^File, err: Error) { temp_allocator := TEMP_ALLOCATOR_GUARD({}) dir := dir if dir != "" else temp_directory(temp_allocator) or_return prefix, suffix := _prefix_and_suffix(pattern) or_return @@ -26,7 +26,7 @@ create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) { attempts := 0 for { name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix) - f, err = open(name, {.Read, .Write, .Create, .Excl}, Permissions_Read_Write_All) + f, err = open(name, {.Read, .Write, .Create, .Excl} + additional_flags, Permissions_Read_Write_All) if err == .Exist { close(f) attempts += 1 diff --git a/core/os/os_windows.odin b/core/os/os_windows.odin index 03c194596..cb7e42f67 100644 --- a/core/os/os_windows.odin +++ b/core/os/os_windows.odin @@ -333,8 +333,14 @@ open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Erro case: create_mode = win32.OPEN_EXISTING } + + attrs := win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS + if mode & (O_NONBLOCK) == O_NONBLOCK { + attrs |= win32.FILE_FLAG_OVERLAPPED + } + wide_path := win32.utf8_to_wstring(path) - handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS, nil)) + handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, attrs, nil)) if handle != INVALID_HANDLE { return handle, nil } @@ -862,4 +868,4 @@ pipe :: proc() -> (r, w: Handle, err: Error) { err = get_last_error() } return -}
\ No newline at end of file +} diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 14bf03f43..e2373d3e2 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -2207,7 +2207,7 @@ swizzle :: builtin.swizzle /* Extract the set of most-significant bits of a SIMD vector. -This procedure checks the the most-significant bit (MSB) for each lane of vector +This procedure checks the most-significant bit (MSB) for each lane of vector and returns the numbers of lanes with the most-significant bit set. This procedure can be used in conjuction with `lanes_eq` (and other similar procedures) to count the number of matched lanes by computing the cardinality of the resulting @@ -2253,7 +2253,7 @@ extract_msbs :: intrinsics.simd_extract_msbs /* Extract the set of least-significant bits of a SIMD vector. -This procedure checks the the least-significant bit (LSB) for each lane of vector +This procedure checks the least-significant bit (LSB) for each lane of vector and returns the numbers of lanes with the least-significant bit set. This procedure can be used in conjuction with `lanes_eq` (and other similar procedures) to count the number of matched lanes by computing the cardinality of the resulting diff --git a/core/slice/slice.odin b/core/slice/slice.odin index 58d35b9f0..12ebfce3b 100644 --- a/core/slice/slice.odin +++ b/core/slice/slice.odin @@ -924,3 +924,39 @@ bitset_to_enum_slice_with_make :: proc(bs: $T, $E: typeid, allocator := context. } bitset_to_enum_slice :: proc{bitset_to_enum_slice_with_make, bitset_to_enum_slice_with_buffer} + +/* +Removes the first n elements (`elems`) from a slice of slices, spanning inner slices and dropping empty ones. + +If `elems` is out of bounds (more than the total) this will trigger a bounds check. + +Example: + import "core:fmt" + import "core:slice" + + advance_slices_example :: proc() { + slices := [][]byte { + {1, 2, 3, 4}, + {5, 6, 7}, + } + + fmt.println(slice.advance_slices(slices, 4)) + } + +Output: + [[5, 6, 7]] +*/ +advance_slices :: proc(slices: $S/[][]$T, elems: int) -> S { + slices, elems := slices, elems + for elems > 0 { + slice := &slices[0] + take := builtin.min(elems, len(slice)) + slice^ = slice[take:] + if len(slice) == 0 { + slices = slices[1:] + } + elems -= take + } + + return slices +} diff --git a/core/slice/sort.odin b/core/slice/sort.odin index d438cfc1b..9f91a0f73 100644 --- a/core/slice/sort.odin +++ b/core/slice/sort.odin @@ -300,7 +300,9 @@ Example: import "core:slice" import "core:fmt" - main :: proc() { + stable_sort_by_example :: proc() { + Example :: struct { n: int, s: string } + arr := []Example { {2, "name"}, {3, "Bill"}, @@ -312,10 +314,9 @@ Example: }) for e in arr do fmt.printf("%s ", e.s) + fmt.println() } - Example :: struct { n: int, s: string } - Output: My name is Bill */ @@ -335,7 +336,9 @@ Example: import "core:slice" import "core:fmt" - main :: proc() { + stable_sort_by_cmp_example :: proc() { + Example :: struct { n: int, s: string } + arr := []Example { {2, "name"}, {3, "Bill"}, @@ -347,9 +350,9 @@ Example: }) for e in arr do fmt.printf("%s ", e.s) + fmt.println() } - Example :: struct { n: int, s: string } Output: My name is Bill */ diff --git a/core/strings/strings.odin b/core/strings/strings.odin index beaa8bda1..bad0ac26a 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -438,7 +438,7 @@ equal_fold :: proc(u, v: string) -> (res: bool) { r := unicode.simple_fold(sr) for r != sr && r < tr { - r = unicode.simple_fold(sr) + r = unicode.simple_fold(r) } if r == tr { continue loop diff --git a/core/sync/chan/chan.odin b/core/sync/chan/chan.odin index 05312e5a2..17618763f 100644 --- a/core/sync/chan/chan.odin +++ b/core/sync/chan/chan.odin @@ -1166,7 +1166,7 @@ Example: import "core:sync/chan" import "core:fmt" - select_raw_example :: proc() { + try_select_raw_example :: proc() { c, err := chan.create(chan.Chan(int), 1, context.allocator) assert(err == .None) defer chan.destroy(c) @@ -1198,11 +1198,11 @@ Example: Output: - SELECT: 0 true + SELECT: 0 Send RECEIVED VALUE 0 - SELECT: 0 true + SELECT: 0 Recv RECEIVED VALUE 1 - SELECT: 0 false + SELECT: -1 None */ @(require_results) @@ -1219,7 +1219,7 @@ try_select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs count := 0 for c, i in recvs { - if can_recv(c) { + if !c.closed && can_recv(c) { candidates[count] = { is_recv = true, idx = i, @@ -1232,7 +1232,7 @@ try_select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs if i > builtin.len(send_msgs)-1 || send_msgs[i] == nil { continue } - if can_send(c) { + if !c.closed && can_send(c) { candidates[count] = { is_recv = false, idx = i, diff --git a/core/sys/darwin/Foundation/NSScreen.odin b/core/sys/darwin/Foundation/NSScreen.odin index 79ab00fbe..8d52b9e1c 100644 --- a/core/sys/darwin/Foundation/NSScreen.odin +++ b/core/sys/darwin/Foundation/NSScreen.odin @@ -3,18 +3,22 @@ package objc_Foundation @(objc_class="NSScreen") Screen :: struct {using _: Object} -@(objc_type=Screen, objc_name="mainScreen") +@(objc_type=Screen, objc_name="mainScreen", objc_is_class_method=true) Screen_mainScreen :: proc "c" () -> ^Screen { return msgSend(^Screen, Screen, "mainScreen") } -@(objc_type=Screen, objc_name="deepestScreen") +@(objc_type=Screen, objc_name="deepestScreen", objc_is_class_method=true) Screen_deepestScreen :: proc "c" () -> ^Screen { return msgSend(^Screen, Screen, "deepestScreen") } -@(objc_type=Screen, objc_name="screens") +@(objc_type=Screen, objc_name="screens", objc_is_class_method=true) Screen_screens :: proc "c" () -> ^Array { return msgSend(^Array, Screen, "screens") } +@(objc_type=Screen, objc_name="screensHaveSeparateSpaces", objc_is_class_method=true) +Screen_screensHaveSeparateSpaces :: proc "c" () -> BOOL { + return msgSend(BOOL, Screen, "screensHaveSeparateSpaces") +} @(objc_type=Screen, objc_name="frame") Screen_frame :: proc "c" (self: ^Screen) -> Rect { return msgSend(Rect, self, "frame") diff --git a/core/sys/darwin/Foundation/NSWindow.odin b/core/sys/darwin/Foundation/NSWindow.odin index f39faca0a..11f8b187e 100644 --- a/core/sys/darwin/Foundation/NSWindow.odin +++ b/core/sys/darwin/Foundation/NSWindow.odin @@ -968,3 +968,23 @@ Window_setTabbingMode :: proc "c" (self: ^Window, mode: WindowTabbingMode) { Window_toggleFullScreen :: proc "c" (self: ^Window, sender: id) { msgSend(nil, self, "toggleFullScreen:", sender) } +@(objc_type = Window, objc_name = "contentRectForFrameRect", objc_is_class_method=true) +Window_contentRectForFrameRectType :: proc "c" (frameRect: Rect, styleMask: WindowStyleMask) -> Rect { + return msgSend(Rect, Window, "contentRectForFrameRect:styleMask:", frameRect, styleMask) +} +@(objc_type = Window, objc_name = "frameRectForContentRect", objc_is_class_method=true) +Window_frameRectForContentRectType :: proc "c" (contentRect: Rect, styleMask: WindowStyleMask) -> Rect { + return msgSend(Rect, Window, "frameRectForContentRect:styleMask:", contentRect, styleMask) +} +@(objc_type = Window, objc_name = "minFrameWidthWithTitle", objc_is_class_method=true) +Window_minFrameWidthWithTitle :: proc "c" (title: ^String, styleMask: WindowStyleMask) -> Float { + return msgSend(Float, Window, "minFrameWidthWithTitle:styleMask:", title, styleMask) +} +@(objc_type = Window, objc_name = "contentRectForFrameRect") +Window_contentRectForFrameRectInstance :: proc "c" (self: ^Window, frameRect: Rect) -> Rect { + return msgSend(Rect, self, "contentRectForFrameRect:", frameRect) +} +@(objc_type = Window, objc_name = "frameRectForContentRect") +Window_frameRectForContentRectInstance :: proc "c" (self: ^Window, contentRect: Rect) -> Rect { + return msgSend(Rect, self, "frameRectForContentRect:", contentRect) +} diff --git a/core/sys/kqueue/kqueue.odin b/core/sys/kqueue/kqueue.odin index 25ee9bdce..b51f3426f 100644 --- a/core/sys/kqueue/kqueue.odin +++ b/core/sys/kqueue/kqueue.odin @@ -32,6 +32,7 @@ kevent :: proc(kq: KQ, change_list: []KEvent, event_list: []KEvent, timeout: ^po timeout, ) if n_events == -1 { + n_events = 0 err = posix.errno() } return @@ -60,6 +61,7 @@ Filter :: enum _Filter_Backing { Proc = _FILTER_PROC, // Check for changes to the subject process. Signal = _FILTER_SIGNAL, // Check for signals delivered to the process. Timer = _FILTER_TIMER, // Timers. + User = _FILTER_USER, // User events. } RW_Flag :: enum u32 { @@ -82,18 +84,30 @@ Proc_Flag :: enum u32 { Exit = log2(0x80000000), // Process exited. Fork = log2(0x40000000), // Process forked. Exec = log2(0x20000000), // Process exec'd. - Signal = log2(0x08000000), // Shared with `Filter.Signal`. } Proc_Flags :: bit_set[Proc_Flag; u32] Timer_Flag :: enum u32 { Seconds = log2(0x00000001), // Data is seconds. - USeconds = log2(0x00000002), // Data is microseconds. - NSeconds = log2(_NOTE_NSECONDS), // Data is nanoseconds. + USeconds = log2(_NOTE_USECONDS), // Data is microseconds. Absolute = log2(_NOTE_ABSOLUTE), // Absolute timeout. } Timer_Flags :: bit_set[Timer_Flag; u32] +User_Flag :: enum u32 { + Trigger = log2(0x01000000), + FFAnd = log2(0x40000000), + FFOr = log2(0x80000000), +} +User_Flags :: bit_set[User_Flag; u32] + +USER_FLAGS_COPY :: User_Flags{.FFOr, .FFAnd} +USER_FLAGS_CONTROL_MASK :: transmute(User_Flags)u32(0xc0000000) +USER_FLAGS_MASK :: transmute(User_Flags)u32(0x00FFFFFF) + +// Data is nanoseconds. +TIMER_FLAGS_NSECONDS :: _TIMER_FLAGS_NSECONDS + when ODIN_OS == .Darwin { _Filter_Backing :: distinct i16 @@ -106,10 +120,14 @@ when ODIN_OS == .Darwin { _FILTER_PROC :: -5 _FILTER_SIGNAL :: -6 _FILTER_TIMER :: -7 + _FILTER_USER :: -10 + _NOTE_USECONDS :: 0x00000002 _NOTE_NSECONDS :: 0x00000004 _NOTE_ABSOLUTE :: 0x00000008 + _TIMER_FLAGS_NSECONDS :: Timer_Flags{Timer_Flag(log2(_NOTE_NSECONDS))} + KEvent :: struct #align(4) { // Value used to identify this event. The exact interpretation is determined by the attached filter. ident: uintptr, @@ -119,11 +137,12 @@ when ODIN_OS == .Darwin { flags: Flags, // Filter specific flags. fflags: struct #raw_union { - rw: RW_Flags, - vnode: VNode_Flags, - fproc: Proc_Flags, + rw: RW_Flags `raw_union_tag:"filter=.Read, filter=.Write"`, + vnode: VNode_Flags `raw_union_tag:"filter=.VNode"`, + fproc: Proc_Flags `raw_union_tag:"filter=.Proc"`, // vm: VM_Flags, - timer: Timer_Flags, + timer: Timer_Flags `raw_union_tag:"filter=.Timer"`, + user: User_Flags `raw_union_tag:"filter=.User"`, }, // Filter specific data. data: c.long /* intptr_t */, @@ -143,9 +162,13 @@ when ODIN_OS == .Darwin { _FILTER_PROC :: -5 _FILTER_SIGNAL :: -6 _FILTER_TIMER :: -7 + _FILTER_USER :: -11 - _NOTE_NSECONDS :: 0x00000004 - _NOTE_ABSOLUTE :: 0x00000008 + _NOTE_USECONDS :: 0x00000004 + _NOTE_NSECONDS :: 0x00000008 + _NOTE_ABSOLUTE :: 0x00000010 + + _TIMER_FLAGS_NSECONDS :: Timer_Flags{Timer_Flag(log2(_NOTE_NSECONDS))} KEvent :: struct { // Value used to identify this event. The exact interpretation is determined by the attached filter. @@ -156,11 +179,12 @@ when ODIN_OS == .Darwin { flags: Flags, // Filter specific flags. fflags: struct #raw_union { - rw: RW_Flags, - vnode: VNode_Flags, - fproc: Proc_Flags, + rw: RW_Flags `raw_union_tag:"filter=.Read, filter=.Write"`, + vnode: VNode_Flags `raw_union_tag:"filter=.VNode"`, + fproc: Proc_Flags `raw_union_tag:"filter=.Proc"`, // vm: VM_Flags, - timer: Timer_Flags, + timer: Timer_Flags `raw_union_tag:"filter=.Timer"`, + user: User_Flags `raw_union_tag:"filter=.User"`, }, // Filter specific data. data: i64, @@ -181,11 +205,14 @@ when ODIN_OS == .Darwin { _FILTER_PROC :: 4 _FILTER_SIGNAL :: 5 _FILTER_TIMER :: 6 + _FILTER_USER :: 8 - _NOTE_NSECONDS :: 0x00000003 + _NOTE_USECONDS :: 0x00000002 _NOTE_ABSOLUTE :: 0x00000010 - KEvent :: struct #align(4) { + _TIMER_FLAGS_NSECONDS :: Timer_Flags{.Seconds, .USeconds} + + KEvent :: struct { // Value used to identify this event. The exact interpretation is determined by the attached filter. ident: uintptr, // Filter for event. @@ -194,18 +221,17 @@ when ODIN_OS == .Darwin { flags: Flags, // Filter specific flags. fflags: struct #raw_union { - rw: RW_Flags, - vnode: VNode_Flags, - fproc: Proc_Flags, + rw: RW_Flags `raw_union_tag:"filter=.Read, filter=.Write"`, + vnode: VNode_Flags `raw_union_tag:"filter=.VNode"`, + fproc: Proc_Flags `raw_union_tag:"filter=.Proc"`, // vm: VM_Flags, - timer: Timer_Flags, + timer: Timer_Flags `raw_union_tag:"filter=.Timer"`, + user: User_Flags `raw_union_tag:"filter=.User"`, }, // Filter specific data. data: i64, // Opaque user data passed through the kernel unchanged. udata: rawptr, - // Extensions. - ext: [4]u64, } } else when ODIN_OS == .OpenBSD { @@ -219,10 +245,14 @@ when ODIN_OS == .Darwin { _FILTER_PROC :: -5 _FILTER_SIGNAL :: -6 _FILTER_TIMER :: -7 + _FILTER_USER :: -10 - _NOTE_NSECONDS :: 0x00000003 + _NOTE_USECONDS :: 0x00000002 + _NOTE_NSECONDS :: 0x00000004 _NOTE_ABSOLUTE :: 0x00000010 + _TIMER_FLAGS_NSECONDS :: Timer_Flags{Timer_Flag(log2(_NOTE_NSECONDS))} + KEvent :: struct #align(4) { // Value used to identify this event. The exact interpretation is determined by the attached filter. ident: uintptr, @@ -232,11 +262,12 @@ when ODIN_OS == .Darwin { flags: Flags, // Filter specific flags. fflags: struct #raw_union { - rw: RW_Flags, - vnode: VNode_Flags, - fproc: Proc_Flags, + rw: RW_Flags `raw_union_tag:"filter=.Read, filter=.Write"`, + vnode: VNode_Flags `raw_union_tag:"filter=.VNode"`, + fproc: Proc_Flags `raw_union_tag:"filter=.Proc"`, // vm: VM_Flags, - timer: Timer_Flags, + timer: Timer_Flags `raw_union_tag:"filter=.Timer"`, + user: User_Flags `raw_union_tag:"filter=.User"`, }, // Filter specific data. data: i64, @@ -245,12 +276,20 @@ when ODIN_OS == .Darwin { } } +when ODIN_OS == .NetBSD { + @(private) + LKEVENT :: "__kevent50" +} else { + @(private) + LKEVENT :: "kevent" +} + @(private) log2 :: intrinsics.constant_log2 foreign lib { @(link_name="kqueue") _kqueue :: proc() -> KQ --- - @(link_name="kevent") + @(link_name=LKEVENT) _kevent :: proc(kq: KQ, change_list: [^]KEvent, n_changes: c.int, event_list: [^]KEvent, n_events: c.int, timeout: ^posix.timespec) -> c.int --- } diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin index 213d6c26f..2ca7558a2 100644 --- a/core/sys/linux/bits.odin +++ b/core/sys/linux/bits.odin @@ -2269,3 +2269,40 @@ Swap_Flags_Bits :: enum { PREFER = log2(0x8000), DISCARD = log2(0x10000), } + +Eventfd_Flags_Bits :: enum { + SEMAPHORE, + CLOEXEC = auto_cast Open_Flags_Bits.CLOEXEC, + NONBLOCK = auto_cast Open_Flags_Bits.NONBLOCK, +} + +Sched_Policy :: enum u32 { + OTHER = 0, + BATCH = 3, + IDLE = 5, + FIFO = 1, + RR = 2, + DEADLINE = 6, +} + +Sched_Flag_Bits :: enum { + RESET_ON_FORK = log2(0x01), + RECLAIM = log2(0x02), + DL_OVERRUN = log2(0x04), + KEEP_POLICY = log2(0x08), + KEEP_PARAMS = log2(0x10), + UTIL_CLAMP_MIN = log2(0x20), + UTIL_CLAMP_MAX = log2(0x40), +} + +Sched_Attr_Flag_Bits :: enum {} + +/* + See `constants.odin` for `MFD_HUGE_16KB`, et al. +*/ +Memfd_Create_Flag_Bits :: enum { + CLOEXEC = log2(0x1), + ALLOW_SEALING = log2(0x2), + HUGETLB = log2(0x4), +} + diff --git a/core/sys/linux/constants.odin b/core/sys/linux/constants.odin index 71a16312e..4de57f40b 100644 --- a/core/sys/linux/constants.odin +++ b/core/sys/linux/constants.odin @@ -395,6 +395,20 @@ MAP_HUGE_1GB :: transmute(Map_Flags)(u32(30) << MAP_HUGE_SHIFT) MAP_HUGE_2GB :: transmute(Map_Flags)(u32(31) << MAP_HUGE_SHIFT) MAP_HUGE_16GB :: transmute(Map_Flags)(u32(34) << MAP_HUGE_SHIFT) +MFD_HUGE_16KB :: transmute(Memfd_Create_Flags)(u32(14) << MAP_HUGE_SHIFT) +MFD_HUGE_64KB :: transmute(Memfd_Create_Flags)(u32(16) << MAP_HUGE_SHIFT) +MFD_HUGE_512KB :: transmute(Memfd_Create_Flags)(u32(19) << MAP_HUGE_SHIFT) +MFD_HUGE_1MB :: transmute(Memfd_Create_Flags)(u32(20) << MAP_HUGE_SHIFT) +MFD_HUGE_2MB :: transmute(Memfd_Create_Flags)(u32(21) << MAP_HUGE_SHIFT) +MFD_HUGE_8MB :: transmute(Memfd_Create_Flags)(u32(23) << MAP_HUGE_SHIFT) +MFD_HUGE_16MB :: transmute(Memfd_Create_Flags)(u32(24) << MAP_HUGE_SHIFT) +MFD_HUGE_32MB :: transmute(Memfd_Create_Flags)(u32(25) << MAP_HUGE_SHIFT) +MFD_HUGE_256MB :: transmute(Memfd_Create_Flags)(u32(28) << MAP_HUGE_SHIFT) +MFD_HUGE_512MB :: transmute(Memfd_Create_Flags)(u32(29) << MAP_HUGE_SHIFT) +MFD_HUGE_1GB :: transmute(Memfd_Create_Flags)(u32(30) << MAP_HUGE_SHIFT) +MFD_HUGE_2GB :: transmute(Memfd_Create_Flags)(u32(31) << MAP_HUGE_SHIFT) +MFD_HUGE_16GB :: transmute(Memfd_Create_Flags)(u32(34) << MAP_HUGE_SHIFT) + /* Get window size */ TIOCGWINSZ :: 0x5413 diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index faeda6f43..392761e1d 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -2043,22 +2043,71 @@ setpriority :: proc "contextless" (which: Priority_Which, who: i32, prio: i32) - return Errno(-ret) } -// TODO(flysand): sched_setparam +/* + Set scheduling parameters. + Available since Linux 2.0. +*/ +sched_setparam :: proc "contextless" (pid: Pid, param: ^Sched_Param) -> (Errno) { + ret := syscall(SYS_sched_setparam, pid, param) + return Errno(-ret) +} -// TODO(flysand): sched_getparam +/* + Get scheduling parameters. + Available since Linux 2.0. +*/ +sched_getparam :: proc "contextless" (pid: Pid, param: ^Sched_Param) -> (Errno) { + ret := syscall(SYS_sched_getparam, pid, param) + return Errno(-ret) +} -// TODO(flysand): sched_setscheduler +/* + Set scheduling policy/parameters. + Available since Linux 2.0. +*/ +sched_setscheduler :: proc "contextless" (pid: Pid, policy: i32, param: ^Sched_Param) -> (Errno) { + ret := syscall(SYS_sched_setscheduler, pid, policy, param) + return Errno(-ret) +} -// TODO(flysand): sched_getscheduler +/* + Get scheduling policy/parameters. + Available since Linux 2.0. +*/ +sched_getscheduler :: proc "contextless" (pid: Pid, policy: i32, param: ^Sched_Param) -> (i32, Errno) { + ret := syscall(SYS_sched_getscheduler, pid) + return errno_unwrap(ret, i32) +} -// TODO(flysand): sched_get_priority_max +/* + Get static priority range. + Available since Linux 2.0. +*/ +sched_get_priority_max :: proc "contextless" (policy: i32) -> (i32, Errno) { + ret := syscall(SYS_sched_get_priority_max, policy) + return errno_unwrap(ret, i32) +} -// TODO(flysand): sched_get_priority_min +/* + Get static priority range. + Available since Linux 2.0. +*/ +sched_get_priority_min :: proc "contextless" (policy: i32) -> (i32, Errno) { + ret := syscall(SYS_sched_get_priority_min, policy) + return errno_unwrap(ret, i32) +} -// TODO(flysand): sched_rr_get_interval +/* + get the SCHED_RR interval for the named process. + Available since Linux 2.0. +*/ +sched_rr_get_interval :: proc "contextless" (pid: Pid, tp: ^Time_Spec) -> (Errno) { + ret := syscall(SYS_sched_rr_get_interval, pid, tp) + return Errno(-ret) +} /* - Lock and memory. + Lock memory. Available since Linux 2.0. If flags specified, available since Linux 4.4. */ @@ -2560,9 +2609,29 @@ futex :: proc{ futex_wake_bitset, } -// TODO(flysand): sched_setaffinity +/* + Set a thread's CPU affinity mask. + Available since Linux 2.6. + + If you are running on a system with less than 128 cores you can use `linux.Cpu_Set` as the type for the mask argument. + Otherwise use an array of integers. +*/ +sched_setaffinity :: proc "contextless" (pid: Pid, cpusetsize: uint, mask: rawptr) -> (Errno) { + ret := syscall(SYS_sched_setaffinity, pid, cpusetsize, mask) + return Errno(-ret) +} -// TODO(flysand): sched_getaffinity +/* + Get a thread's CPU affinity mask. + Available since Linux 2.6. + + If you are running on a system with less than 128 cores you can use `linux.Cpu_Set` as the type for the mask argument. + Otherwise use an array of integers. +*/ +sched_getaffinity :: proc "contextless" (pid: Pid, cpusetsize: uint, mask: rawptr) -> (Errno) { + ret := syscall(SYS_sched_getaffinity, pid, cpusetsize, mask) + return Errno(-ret) +} // TODO(flysand): set_thread_area @@ -3001,7 +3070,10 @@ timerfd_create :: proc "contextless" (clock_id: Clock_Id, flags: Open_Flags) -> return errno_unwrap2(ret, Fd) } -// TODO(flysand): eventfd +eventfd :: proc "contextless" (initval: u32, flags: Eventfd_Flags) -> (Fd, Errno) { + ret := syscall(SYS_eventfd2, initval, transmute(i32)flags) + return errno_unwrap2(ret, Fd) +} // TODO(flysand): fallocate @@ -3100,7 +3172,14 @@ sendmmsg :: proc "contextless" (sock: Fd, msg_vec: []MMsg_Hdr, flags: Socket_Msg // TODO(flysand): setns -// TODO(flysand): getcpu +/* + Determine CPU and NUMA node on which the calling thread is running. + Available since Linux 2.6.19. +*/ +getcpu :: proc "contextless" (cpu, node: ^u32) -> (Errno) { + ret := syscall(SYS_getcpu, cpu, node) + return Errno(-ret) +} // TODO(flysand): process_vm_readv @@ -3110,9 +3189,23 @@ sendmmsg :: proc "contextless" (sock: Fd, msg_vec: []MMsg_Hdr, flags: Socket_Msg // TODO(flysand): finit_module -// TODO(flysand): sched_setattr +/* + Set scheduling policy and attributes. + Available since Linux 3.14. +*/ +sched_setattr :: proc "contextless" (pid: Pid, attr: ^Sched_Attr, flags: Sched_Attr_Flags) -> (Errno) { + ret := syscall(SYS_sched_setattr, pid, attr, transmute(u32)flags) + return Errno(-ret) +} -// TODO(flysand): sched_getattr +/* + Get scheduling policy and attributes. + Available since Linux 3.14. +*/ +sched_getattr :: proc "contextless" (pid: Pid, attr: ^Sched_Attr, size: u32, flags: Sched_Attr_Flags) -> (Errno) { + ret := syscall(SYS_sched_getattr, pid, attr, transmute(u32)flags) + return Errno(-ret) +} /* Rename the file with names relative to the specified dirfd's with other options. @@ -3130,7 +3223,14 @@ getrandom :: proc "contextless" (buf: []u8, flags: Get_Random_Flags) -> (int, Er return errno_unwrap(ret, int) } -// TODO(flysand): memfd_create +/* + Create an anonymous file. + Available since Linux 3.17. +*/ +memfd_create :: proc "contextless" (name: cstring, flags: Memfd_Create_Flags) -> (Fd, Errno) { + ret := syscall(SYS_memfd_create, cast(rawptr)name, transmute(u32)flags) + return errno_unwrap(ret, Fd) +} // TODO(flysand): kexec_file_load diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin index 0910f11ec..c2334e5b6 100644 --- a/core/sys/linux/types.odin +++ b/core/sys/linux/types.odin @@ -1589,23 +1589,23 @@ IO_Uring_SQE :: struct { using __ioprio: struct #raw_union { ioprio: u16, sq_accept_flags: IO_Uring_Accept_Flags, - sq_send_recv_flags: IO_Uring_Send_Recv_Flags, + sq_send_recv_flags: IO_Uring_Send_Recv_Flags `raw_union_tag:"opcode=.SENDMSG, opcode=.RECVMSG, opcode=.SEND, opcode=.RECV"`, }, fd: Fd, using __offset: struct #raw_union { // Offset into file. - off: u64, - addr2: u64, + off: u64 `raw_union_tag:"opcode=.READV, opcode=.WRITEV, opcode=.SPLICE, opcode=.POLL_REMOVE, opcode=.EPOLL_CTL, opcode=.TIMEOUT, opcode=.ACCEPT, opcode=.CONNECT, opcode=.READ, opcode=.WRITE, opcode=.FILES_UPDATE, opcode=.SOCKET"`, + addr2: u64 `raw_union_tag:"opcode=.SEND, opcode=.BIND"`, using _: struct { cmd_op: u32, __pad1: u32, }, - statx: ^Statx, + statx: ^Statx `raw_union_tag:"opcode=.STATX"`, }, using __iovecs: struct #raw_union { // Pointer to buffer or iovecs. - addr: u64, - splice_off_in: u64, + addr: u64 `raw_union_tag:"opcode=.READV, opcode=.WRITEV, opcode=.POLL_REMOVE, opcode=.EPOLL_CTL, opcode=.SENDMSG, opcode=.RECVMSG, opcode=.SEND, opcode=.RECV, opcode=.TIMEOUT, opcode=.TIMEOUT_REMOVE, opcode=.ACCEPT, opcode=.ASYNC_CANCEL, opcode=.LINK_TIMEOUT, opcode=.CONNECT, opcode=.MADVISE, opcode=.OPENAT, opcode=.STATX, opcode=.READ, opcode=.WRITE, opcode=.FILES_UPDATE, opcode=.BIND, opcode=.LISTEN"`, + splice_off_in: u64 `raw_union_tag:"opcode=.SPLICE"`, using _: struct { level: u32, optname: u32, @@ -1613,28 +1613,28 @@ IO_Uring_SQE :: struct { }, using __len: struct #raw_union { // Buffer size or number of iovecs. - len: u32, - poll_flags: IO_Uring_Poll_Add_Flags, - statx_mask: Statx_Mask, - epoll_ctl_op: EPoll_Ctl_Opcode, - shutdown_how: Shutdown_How, + len: u32 `raw_union_tag:"opcode=.READV, opcode=.WRITEV, opcode=.SPLICE, opcode=.SEND, opcode=.RECV, opcode=.TIMEOUT, opcode=.LINK_TIMEOUT, opcode=.MADVISE, opcode=.OPENAT, opcode=.READ, opcode=.WRITE, opcode=.TEE, opcode=.FILES_UPDATE, opcode=.SOCKET"`, + poll_flags: IO_Uring_Poll_Add_Flags `raw_union_tag:"opcode=.POLL_ADD, opcode=.POLL_REMOVE"`, + statx_mask: Statx_Mask `raw_union_tag:"opcode=.STATX"`, + epoll_ctl_op: EPoll_Ctl_Opcode `raw_union_tag:"opcode=.EPOLL_CTL"`, + shutdown_how: Shutdown_How `raw_union_tag:"opcode=.SHUTDOWN"`, }, using __contents: struct #raw_union { - rw_flags: i32, - fsync_flags: IO_Uring_Fsync_Flags, + rw_flags: i32 `raw_union_tag:"opcode=.READV, opcode=.WRITEV, opcode=.SOCKET"`, + fsync_flags: IO_Uring_Fsync_Flags `raw_union_tag:"opcode=.FSYNC"`, // compatibility. - poll_events: Fd_Poll_Events, + poll_events: Fd_Poll_Events `raw_union_tag:"opcode=.POLL_ADD, opcode=.POLL_REMOVE"`, // word-reversed for BE. poll32_events: u32, sync_range_flags: u32, - msg_flags: Socket_Msg, - timeout_flags: IO_Uring_Timeout_Flags, - accept_flags: Socket_FD_Flags, + msg_flags: Socket_Msg `raw_union_tag:"opcode=.SENDMSG, opcode=.RECVMSG, opcode=.SEND, opcode=.RECV"`, + timeout_flags: IO_Uring_Timeout_Flags `raw_union_tag:"opcode=.TIMEOUT, opcode=.TIMEOUT_REMOVE, opcode=.LINK_TIMEOUT"`, + accept_flags: Socket_FD_Flags `raw_union_tag:"opcode=.ACCEPT"`, cancel_flags: u32, - open_flags: Open_Flags, - statx_flags: FD_Flags, - fadvise_advice: u32, - splice_flags: IO_Uring_Splice_Flags, + open_flags: Open_Flags `raw_union_tag:"opcode=.OPENAT"`, + statx_flags: FD_Flags `raw_union_tag:"opcode=.STATX"`, + fadvise_advice: u32 `raw_union_tag:"opcode=.MADVISE"`, + splice_flags: IO_Uring_Splice_Flags `raw_union_tag:"opcode=.SPLICE, opcode=.TEE"`, rename_flags: u32, unlink_flags: u32, hardlink_flags: u32, @@ -1653,10 +1653,10 @@ IO_Uring_SQE :: struct { // Personality to use, if used. personality: u16, using _: struct #raw_union { - splice_fd_in: Fd, - file_index: u32, + splice_fd_in: Fd `raw_union_tag:"opcode=.SPLICE, opcode=.TEE"`, + file_index: u32 `raw_union_tag:"opcode=.ACCEPT, opcode=.OPENAT, opcode=.CLOSE, opcode=.SOCKET"`, using _: struct { - addr_len: u16, + addr_len: u16 `raw_union_tag:"opcode=.SEND"`, __pad3: [1]u16, }, }, @@ -1748,3 +1748,35 @@ Mount_Flags :: bit_set[Mount_Flags_Bits; uint] Umount2_Flags :: bit_set[Umount2_Flags_Bits; u32] Swap_Flags :: bit_set[Swap_Flags_Bits; u32] + +Eventfd_Flags :: bit_set[Eventfd_Flags_Bits; i32] + +Cpu_Set :: bit_set[0 ..< 128] + +Sched_Param :: struct { + sched_priority: i32, +} + +Sched_Flags :: bit_set[Sched_Flag_Bits; u32] + +Sched_Attr :: struct { + size: u32, + + sched_policy: Sched_Policy, + sched_flags: Sched_Flags, + sched_nice: i32, + sched_priority: u32, + + /* For the DEADLINE policy */ + sched_runtime: u64, + sched_deadline: u64, + sched_period: u64, + + /* Utilization hints */ + sched_util_min: u32, + sched_util_max: u32, +} + +Sched_Attr_Flags :: bit_set[Sched_Attr_Flag_Bits; u32] + +Memfd_Create_Flags :: bit_set[Memfd_Create_Flag_Bits; u32] diff --git a/core/sys/linux/uring/doc.odin b/core/sys/linux/uring/doc.odin new file mode 100644 index 000000000..8b9ae5ee8 --- /dev/null +++ b/core/sys/linux/uring/doc.odin @@ -0,0 +1,88 @@ +/* +Wrapper/convenience package over the raw io_uring syscalls, providing help with setup, creation, and operating the ring. + +The following example shows a simple `cat` program implementation using the package. + +Example: + package main + + import "base:runtime" + + import "core:fmt" + import "core:os" + import "core:sys/linux" + import "core:sys/linux/uring" + + Request :: struct { + path: cstring, + buffer: []byte, + completion: linux.IO_Uring_CQE, + } + + main :: proc() { + if len(os.args) < 2 { + fmt.eprintfln("Usage: %s [file name] <[file name] ...>", os.args[0]) + os.exit(1) + } + + requests := make_soa(#soa []Request, len(os.args)-1) + defer delete(requests) + + ring: uring.Ring + params := uring.DEFAULT_PARAMS + err := uring.init(&ring, ¶ms) + fmt.assertf(err == nil, "uring.init: %v", err) + defer uring.destroy(&ring) + + for &request, i in requests { + request.path = runtime.args__[i+1] + // sets up a read requests and adds it to the ring buffer. + submit_read_request(request.path, &request.buffer, &ring) + } + + ulen := u32(len(requests)) + + // submit the requests and wait for them to complete right away. + n, serr := uring.submit(&ring, ulen) + fmt.assertf(serr == nil, "uring.submit: %v", serr) + assert(n == ulen) + + // copy the completed requests out of the ring buffer. + cn := uring.copy_cqes_ready(&ring, requests.completion[:ulen]) + assert(cn == ulen) + + for request in requests { + // check result of the requests. + fmt.assertf(request.completion.res >= 0, "read %q failed: %v", request.path, linux.Errno(-request.completion.res)) + // print out. + fmt.print(string(request.buffer)) + + delete(request.buffer) + } + } + + submit_read_request :: proc(path: cstring, buffer: ^[]byte, ring: ^uring.Ring) { + fd, err := linux.open(path, {}) + fmt.assertf(err == nil, "open(%q): %v", path, err) + + file_sz := get_file_size(fd) + + buffer^ = make([]byte, file_sz) + + _, ok := uring.read(ring, 0, fd, buffer^, 0) + assert(ok, "could not get read sqe") + } + + get_file_size :: proc(fd: linux.Fd) -> uint { + st: linux.Stat + err := linux.fstat(fd, &st) + fmt.assertf(err == nil, "fstat: %v", err) + + if linux.S_ISREG(st.mode) { + return uint(st.size) + } + + panic("not a regular file") + } +*/ +package uring diff --git a/core/sys/linux/uring/ops.odin b/core/sys/linux/uring/ops.odin new file mode 100644 index 000000000..7b3392e98 --- /dev/null +++ b/core/sys/linux/uring/ops.odin @@ -0,0 +1,847 @@ +package uring + +import "core:sys/linux" + +// Do not perform any I/O. This is useful for testing the performance of the uring implementation itself. +nop :: proc(ring: ^Ring, user_data: u64) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .NOP + sqe.user_data = user_data + + ok = true + return +} + +// Vectored read operation, see also readv(2). +readv :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, iovs: []linux.IO_Vec, off: u64) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .READV + sqe.fd = fd + sqe.addr = cast(u64)uintptr(raw_data(iovs)) + sqe.len = u32(len(iovs)) + sqe.off = off + sqe.user_data = user_data + + ok = true + return +} + +// Vectored write operation, see also writev(2). +writev :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, iovs: []linux.IO_Vec, off: u64) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .WRITEV + sqe.fd = fd + sqe.addr = cast(u64)uintptr(raw_data(iovs)) + sqe.len = u32(len(iovs)) + sqe.off = off + sqe.user_data = user_data + + ok = true + return +} + +read_fixed :: proc() { + unimplemented() +} + +write_fixed :: proc() { + unimplemented() +} + +/* +File sync. See also fsync(2). + +Optionally off and len can be used to specify a range within the file to be synced rather than syncing the entire file, which is the default behavior. + +Note that, while I/O is initiated in the order in which it appears in the submission queue, completions are unordered. +For example, an application which places a write I/O followed by an fsync in the submission queue cannot expect the fsync to apply to the write. +The two operations execute in parallel, so the fsync may complete before the write is issued to the storage. +The same is also true for previously issued writes that have not completed prior to the fsync. +To enforce ordering one may utilize linked SQEs, +IOSQE_IO_DRAIN or wait for the arrival of CQEs of requests which have to be ordered before a given request before submitting its SQE. +*/ +fsync :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, flags: linux.IO_Uring_Fsync_Flags) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .FSYNC + sqe.fsync_flags = flags + sqe.fd = fd + sqe.user_data = user_data + + ok = true + return +} + +/* +Poll the fd specified in the submission queue entry for the events specified in the poll_events field. + +Unlike poll or epoll without EPOLLONESHOT, by default this interface always works in one shot mode. +That is, once the poll operation is completed, it will have to be resubmitted. + +If IORING_POLL_ADD_MULTI is set in the SQE len field, then the poll will work in multi shot mode instead. +That means it'll repatedly trigger when the requested event becomes true, and hence multiple CQEs can be generated from this single SQE. +The CQE flags field will have IORING_CQE_F_MORE set on completion if the application should expect further CQE entries from the original request. +If this flag isn't set on completion, then the poll request has been terminated and no further events will be generated. +This mode is available since 5.13. + +This command works like an async poll(2) and the completion event result is the returned mask of events. + +Without IORING_POLL_ADD_MULTI and the initial poll operation with IORING_POLL_ADD_MULTI the operation is level triggered, +i.e. if there is data ready or events pending etc. +at the time of submission a corresponding CQE will be posted. +Potential further completions beyond the first caused by a IORING_POLL_ADD_MULTI are edge triggered. +*/ +poll_add :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, events: linux.Fd_Poll_Events, flags: linux.IO_Uring_Poll_Add_Flags) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .POLL_ADD + sqe.fd = fd + sqe.poll_events = events + sqe.poll_flags = flags + sqe.user_data = user_data + + ok = true + return +} + +/* +Remove an existing poll request. + +If found, the res field of the struct io_uring_cqe will contain 0. +If not found, res will contain -ENOENT, or -EALREADY if the poll request was in the process of completing already. +*/ +poll_remove :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, events: linux.Fd_Poll_Events) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .POLL_REMOVE + sqe.fd = fd + sqe.poll_events = events + sqe.user_data = user_data + + ok = true + return +} + +/* +Update the events of an existing poll request. + +The request will update an existing poll request with the mask of events passed in with this request. +The lookup is based on the user_data field of the original SQE submitted. + +Updating an existing poll is available since 5.13. +*/ +poll_update_events :: proc(ring: ^Ring, user_data: u64, orig_user_data: u64, fd: linux.Fd, events: linux.Fd_Poll_Events) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .POLL_REMOVE + sqe.fd = fd + sqe.addr = orig_user_data + sqe.poll_events = events + sqe.user_data = user_data + sqe.poll_flags = {.UPDATE_EVENTS} + + ok = true + return +} + +/* +Update the user data of an existing poll request. + +The request will update the user_data of an existing poll request based on the value passed. + +Updating an existing poll is available since 5.13. +*/ +poll_update_user_data :: proc(ring: ^Ring, user_data: u64, orig_user_data: u64, new_user_data: u64, fd: linux.Fd) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .POLL_REMOVE + sqe.fd = fd + sqe.off = orig_user_data + sqe.addr = new_user_data + sqe.user_data = user_data + sqe.poll_flags = {.UPDATE_USER_DATA} + + ok = true + return +} + +/* +Add, remove or modify entries in the interest list of epoll(7). + +See epoll_ctl(2) for details of the system call. + +Available since 5.6. +*/ +epoll_ctl :: proc(ring: ^Ring, user_data: u64, epfd: linux.Fd, op: linux.EPoll_Ctl_Opcode, fd: linux.Fd, event: ^linux.EPoll_Event) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .EPOLL_CTL + sqe.fd = epfd + sqe.off = u64(fd) + sqe.epoll_ctl_op = op + sqe.addr = cast(u64)uintptr(event) + sqe.user_data = user_data + + ok = true + return +} + +sync_file_range :: proc() { + unimplemented() +} + +/* +Issue the equivalent of a sendmsg(2) system call. + +See also sendmsg(2) for the general description of the related system call. + +poll_first: if set, uring will assume the socket is currently full and attempting to send data will be unsuccessful. +For this case, uring will arm internal poll and trigger a send of the data when there is enough space available. +This initial send attempt can be wasteful for the case where the socket is expected to be full, setting this flag will +bypass the initial send attempt and go straight to arming poll. +If poll does indicate that data can be sent, the operation will proceed. + +Available since 5.3. +*/ +sendmsg :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, msghdr: ^linux.Msg_Hdr, flags: linux.Socket_Msg, poll_first := false) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .SENDMSG + sqe.fd = fd + sqe.addr = cast(u64)uintptr(msghdr) + sqe.msg_flags = flags + sqe.user_data = user_data + sqe.sq_send_recv_flags = {.RECVSEND_POLL_FIRST} if poll_first else {} + + ok = true + return +} + +/* +Works just like sendmsg, but receives instead of sends. + +poll_first: If set, uring will assume the socket is currently empty and attempting to receive data will be unsuccessful. +For this case, uring will arm internal poll and trigger a receive of the data when the socket has data to be read. +This initial receive attempt can be wasteful for the case where the socket is expected to be empty, setting this flag will bypass the initial receive attempt and go straight to arming poll. +If poll does indicate that data is ready to be received, the operation will proceed. + +Available since 5.3. +*/ +recvmsg :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, msghdr: ^linux.Msg_Hdr, flags: linux.Socket_Msg, poll_first := false) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .RECVMSG + sqe.fd = fd + sqe.addr = cast(u64)uintptr(msghdr) + sqe.msg_flags = flags + sqe.user_data = user_data + sqe.sq_send_recv_flags = {.RECVSEND_POLL_FIRST} if poll_first else {} + + ok = true + return +} + +/* +Issue the equivalent of a send(2) system call. + +See also send(2) for the general description of the related system call. + +poll_first: If set, uring will assume the socket is currently full and attempting to send data will be unsuccessful. +For this case, uring will arm internal poll and trigger a send of the data when there is enough space available. +This initial send attempt can be wasteful for the case where the socket is expected to be full, setting this flag will bypass the initial send attempt and go straight to arming poll. +If poll does indicate that data can be sent, the operation will proceed. + +Available since 5.6. +*/ +send :: proc(ring: ^Ring, user_data: u64, sockfd: linux.Fd, buf: []byte, flags: linux.Socket_Msg, poll_first := false) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .SEND + sqe.fd = sockfd + sqe.addr = cast(u64)uintptr(raw_data(buf)) + sqe.len = u32(len(buf)) + sqe.msg_flags = flags + sqe.user_data = user_data + sqe.sq_send_recv_flags = {.RECVSEND_POLL_FIRST} if poll_first else {} + + ok = true + return +} + +sendto :: proc(ring: ^Ring, user_data: u64, sockfd: linux.Fd, buf: []byte, flags: linux.Socket_Msg, dest: ^$T, poll_first := false) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) + where T == linux.Sock_Addr_In || T == linux.Sock_Addr_In6 || T == linux.Sock_Addr_Un || T == linux.Sock_Addr_Any { + + sqe = send(ring, user_data, sockfd, buf, flags, poll_first) or_return + sqe.addr2 = u64(uintptr(dest)) + sqe.addr_len = u16(size_of(T)) + + ok = true + return +} + +/* +Works just like send, but receives instead of sends. + +poll_first: If set, uring will assume the socket is currently empty and attempting to receive data will be unsuccessful. +For this case, uring will arm internal poll and trigger a receive of the data when the socket has data to be read. +This initial receive attempt can be wasteful for the case where the socket is expected to be empty, setting this flag will bypass the initial receive attempt and go straight to arming poll. +If poll does indicate that data is ready to be received, the operation will proceed. + +Available since 5.6. +*/ +recv :: proc(ring: ^Ring, user_data: u64, sockfd: linux.Fd, buf: []byte, flags: linux.Socket_Msg, poll_first := false) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .RECV + sqe.fd = sockfd + sqe.addr = cast(u64)uintptr(raw_data(buf)) + sqe.len = cast(u32)uintptr(len(buf)) + sqe.msg_flags = flags + sqe.user_data = user_data + sqe.sq_send_recv_flags = {.RECVSEND_POLL_FIRST} if poll_first else {} + + ok = true + return +} + +/* +Register a timeout operation. + +The timeout will complete when either the timeout expires, or after the specified number of +events complete (if `count` is greater than `0`). + +`flags` may be `0` for a relative timeout, or `IORING_TIMEOUT_ABS` for an absolute timeout. + +The completion event result will be `-ETIME` if the timeout completed through expiration, +`0` if the timeout completed after the specified number of events, or `-ECANCELED` if the +timeout was removed before it expired. + +uring timeouts use the `CLOCK.MONOTONIC` clock source. +*/ +timeout :: proc(ring: ^Ring, user_data: u64, ts: ^linux.Time_Spec, count: u32, flags: linux.IO_Uring_Timeout_Flags) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .TIMEOUT + sqe.fd = -1 + sqe.addr = cast(u64)uintptr(ts) + sqe.len = 1 + sqe.off = u64(count) + sqe.timeout_flags = flags + sqe.user_data = user_data + + ok = true + return +} + +/* +Rmove an existing timeout operation. + +The timeout is identified by it's `user_data`. + +The completion event result will be `0` if the timeout was found and cancelled successfully, +`-EBUSY` if the timeout was found but expiration was already in progress, or +`-ENOENT` if the timeout was not found. +*/ +timeout_remove :: proc(ring: ^Ring, user_data: u64, timeout_user_data: u64, flags: linux.IO_Uring_Timeout_Flags) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .TIMEOUT_REMOVE + sqe.fd = -1 + sqe.addr = timeout_user_data + sqe.timeout_flags = flags + sqe.user_data = user_data + + ok = true + return +} + +/* +Issue the equivalent of an accept4(2) system call. + +See also accept4(2) for the general description of the related system call. + +If the file_index field is set to a positive number, the file won't be installed into the normal file table as usual +but will be placed into the fixed file table at index file_index - 1. +In this case, instead of returning a file descriptor, the result will contain either 0 on success or an error. +If the index points to a valid empty slot, the installation is guaranteed to not fail. +If there is already a file in the slot, it will be replaced, similar to IORING_OP_FILES_UPDATE. +Please note that only uring has access to such files and no other syscall can use them. See IOSQE_FIXED_FILE and IORING_REGISTER_FILES. + +Available since 5.5. +*/ +accept :: proc(ring: ^Ring, user_data: u64, sockfd: linux.Fd, addr: ^$T, addr_len: ^i32, flags: linux.Socket_FD_Flags, file_index: u32 = 0) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) +where T == linux.Sock_Addr_In || T == linux.Sock_Addr_In6 || T == linux.Sock_Addr_Un || T == linux.Sock_Addr_Any { + + sqe = get_sqe(ring) or_return + sqe.opcode = .ACCEPT + sqe.fd = sockfd + sqe.addr = cast(u64)uintptr(addr) + sqe.off = cast(u64)uintptr(addr_len) + sqe.accept_flags = flags + sqe.user_data = user_data + sqe.file_index = file_index + + ok = true + return +} + +/* +Attempt to cancel an already issued request. + +The request is identified by it's user data. + +The cancelation request will complete with one of the following results codes. + +If found, the res field of the cqe will contain 0. +If not found, res will contain -ENOENT. + +If found and attempted canceled, the res field will contain -EALREADY. +In this case, the request may or may not terminate. +In general, requests that are interruptible (like socket IO) will get canceled, while disk IO requests cannot be canceled if already started. + +Available since 5.5. +*/ +async_cancel :: proc(ring: ^Ring, orig_user_data: u64, user_data: u64) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .ASYNC_CANCEL + sqe.addr = orig_user_data + sqe.user_data = user_data + + ok = true + return +} + +/* +Adds a link timeout operation. + +You need to set linux.IOSQE_IO_LINK to flags of the target operation +and then call this method right after the target operation. +See https://lwn.net/Articles/803932/ for detail. + +If the dependent request finishes before the linked timeout, the timeout +is canceled. If the timeout finishes before the dependent request, the +dependent request will be canceled. + +The completion event result of the link_timeout will be +`-ETIME` if the timeout finishes before the dependent request +(in this case, the completion event result of the dependent request will +be `-ECANCELED`), or +`-EALREADY` if the dependent request finishes before the linked timeout. + +Available since 5.5. +*/ +link_timeout :: proc(ring: ^Ring, user_data: u64, ts: ^linux.Time_Spec, flags: linux.IO_Uring_Timeout_Flags) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring, 0) or_return + sqe.opcode = .LINK_TIMEOUT + sqe.fd = -1 + sqe.addr = cast(u64)uintptr(ts) + sqe.len = 1 + sqe.timeout_flags = flags + sqe.user_data = user_data + + ok = true + return +} + +/* +Issue the equivalent of a connect(2) system call. + +See also connect(2) for the general description of the related system call. + +Available since 5.5. +*/ +connect :: proc(ring: ^Ring, user_data: u64, sockfd: linux.Fd, addr: ^$T) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) +where T == linux.Sock_Addr_In || T == linux.Sock_Addr_In6 || T == linux.Sock_Addr_Un || T == linux.Sock_Addr_Any { + + sqe = get_sqe(ring) or_return + sqe.opcode = .CONNECT + sqe.fd = sockfd + sqe.addr = cast(u64)uintptr(addr) + sqe.off = size_of(T) + sqe.user_data = user_data + + ok = true + return +} + +fallocate :: proc() { + unimplemented() +} + +fadvise :: proc() { + unimplemented() +} + +/* +Issue the equivalent of a madvise(2) system call. + +See also madvise(2) for the general description of the related system call. + +Available since 5.6. +*/ +madvise :: proc(ring: ^Ring, user_data: u64, addr: rawptr, size: u32, advise: linux.MAdvice) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .MADVISE + sqe.addr = u64(uintptr(addr)) + sqe.len = size + sqe.fadvise_advice = cast(u32)transmute(int)advise + sqe.user_data = user_data + + ok = true + return +} + +/* +Issue the equivalent of a openat(2) system call. + +See also openat(2) for the general description of the related system call. + +Available since 5.6. + +If the file_index is set to a positive number, +the file won't be installed into the normal file table as usual but will be placed into the fixed file table at index file_index - 1. +In this case, instead of returning a file descriptor, the result will contain either 0 on success or an error. +If the index points to a valid empty slot, the installation is guaranteed to not fail. +If there is already a file in the slot, it will be replaced, similar to IORING_OP_FILES_UPDATE. +Please note that only uring has access to such files and no other syscall can use them. +See IOSQE_FIXED_FILE and IORING_REGISTER_FILES. + +Available since 5.15. +*/ +openat :: proc(ring: ^Ring, user_data: u64, dirfd: linux.Fd, path: cstring, mode: linux.Mode, flags: linux.Open_Flags, file_index: u32 = 0) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .OPENAT + sqe.fd = dirfd + sqe.addr = cast(u64)transmute(uintptr)path + sqe.len = transmute(u32)mode + sqe.open_flags = flags + sqe.user_data = user_data + sqe.file_index = file_index + + ok = true + return +} + +openat2 :: proc() { + unimplemented() +} + +/* +Issue the equivalent of a close(2) system call. + +See also close(2) for the general description of the related system call. + +Available since 5.6. + +If the file_index field is set to a positive number, this command can be used to close files that were +direct opened through IORING_OP_OPENAT, IORING_OP_OPENAT2, or IORING_OP_ACCEPT using the uring specific direct descriptors. +Note that only one of the descriptor fields may be set. +The direct close feature is available since the 5.15 kernel, where direct descriptors were introduced. +*/ +close :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, file_index: u32 = 0) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .CLOSE + sqe.fd = fd + sqe.user_data = user_data + sqe.file_index = file_index + + ok = true + return +} + +/* +Issue the equivalent of a statx(2) system call. + +See also statx(2) for the general description of the related system call. + +Available since 5.6. +*/ +statx :: proc(ring: ^Ring, user_data: u64, dirfd: linux.Fd, pathname: cstring, flags: linux.FD_Flags, mask: linux.Statx_Mask, buf: ^linux.Statx) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .STATX + sqe.fd = dirfd + sqe.addr = cast(u64)transmute(uintptr)pathname + sqe.statx_flags = flags + sqe.statx_mask = mask + sqe.statx = buf + sqe.user_data = user_data + + ok = true + return +} + +/* +Issue the equivalent of a pread(2) system call. + +If offset is set to -1 , the offset will use (and advance) the file position, like the read(2) system calls. +These are non-vectored versions of the IORING_OP_READV and IORING_OP_WRITEV opcodes. +See also read(2) for the general description of the related system call. + +Available since 5.6. +*/ +read :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, buf: []u8, offset: u64) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .READ + sqe.fd = fd + sqe.addr = cast(u64)uintptr(raw_data(buf)) + sqe.len = u32(len(buf)) + sqe.off = offset + sqe.user_data = user_data + + ok = true + return +} + +/* +Issue the equivalent of a pwrite(2) system call. + +If offset is set to -1 , the offset will use (and advance) the file position, like the read(2) system calls. +These are non-vectored versions of the IORING_OP_READV and IORING_OP_WRITEV opcodes. +See also write(2) for the general description of the related system call. + +Available since 5.6. +*/ +write :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, buf: []u8, offset: u64) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .WRITE + sqe.fd = fd + sqe.addr = cast(u64)uintptr(raw_data(buf)) + sqe.len = u32(len(buf)) + sqe.off = offset + sqe.user_data = user_data + + ok = true + return +} + +/* +Issue the equivalent of a splice(2) system call. + +A sentinel value of -1 is used to pass the equivalent of a NULL for the offsets to splice(2). + +Please note that one of the file descriptors must refer to a pipe. +See also splice(2) for the general description of the related system call. + +Available since 5.7. + +*/ +splice :: proc(ring: ^Ring, user_data: u64, fd_in: linux.Fd, off_in: i64, fd_out: linux.Fd, off_out: i64, len: u32, flags: linux.IO_Uring_Splice_Flags) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .SPLICE + sqe.splice_fd_in = fd_in + sqe.splice_off_in = cast(u64)off_in + sqe.fd = fd_out + sqe.off = cast(u64)off_out + sqe.len = len + sqe.splice_flags = flags + sqe.user_data = user_data + + ok = true + return +} + +/* +Issue the equivalent of a tee(2) system call. + +Please note that both of the file descriptors must refer to a pipe. +See also tee(2) for the general description of the related system call. + +Available since 5.8. +*/ +tee :: proc(ring: ^Ring, user_data: u64, fd_in: linux.Fd, fd_out: linux.Fd, len: u32, flags: linux.IO_Uring_Splice_Flags) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .TEE + sqe.splice_fd_in = fd_in + sqe.fd = fd_out + sqe.len = len + sqe.splice_flags = flags + sqe.user_data = user_data + + ok = true + return +} + +/* +This command is an alternative to using IORING_REGISTER_FILES_UPDATE which then works in an async fashion, like the rest of the uring commands. + +Note that the array of file descriptors pointed to in addr must remain valid until this operation has completed. + +Available since 5.6. +*/ +files_update :: proc(ring: ^Ring, user_data: u64, fds: []linux.Fd, off: u64) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .FILES_UPDATE + sqe.addr = cast(u64)uintptr(raw_data(fds)) + sqe.len = cast(u32)len(fds) + sqe.off = off + sqe.user_data = user_data + + ok = true + return +} + +provide_buffers :: proc() { + unimplemented() +} + +remove_buffers :: proc() { + unimplemented() +} + +/* +Issue the equivalent of a shutdown(2) system call. + +Available since 5.11. +*/ +shutdown :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, how: linux.Shutdown_How) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .SHUTDOWN + sqe.fd = fd + sqe.shutdown_how = how + sqe.user_data = user_data + + ok = true + return +} + +renameat :: proc() { + unimplemented() +} + +unlinkat :: proc() { + unimplemented() +} + +mkdirat :: proc() { + unimplemented() +} + +symlinkat :: proc() { + unimplemented() +} + +linkat :: proc() { + unimplemented() +} + +msg_ring :: proc() { + unimplemented() +} + +/* +Issue the equivalent of a socket(2) system call. + +See also socket(2) for the general description of the related system call. + +Available since 5.19. + +If the file_index field is set to a positive number, the file won't be installed into the normal file +table as usual but will be placed into the fixed file table at index file_index - 1. +In this case, instead of returning a file descriptor, the result will contain either 0 on success or an error. +If the index points to a valid empty slot, the installation is guaranteed to not fail. +If there is already a file in the slot, it will be replaced, similar to IORING_OP_FILES_UPDATE. +Please note that only uring has access to such files and no other syscall can use them. +See IOSQE_FIXED_FILE and IORING_REGISTER_FILES. +*/ +socket :: proc(ring: ^Ring, user_data: u64, domain: linux.Address_Family, socktype: linux.Socket_Type, protocol: linux.Protocol, file_index: u32 = 0) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .SOCKET + sqe.user_data = user_data + sqe.fd = cast(linux.Fd)domain + sqe.off = cast(u64)socktype + sqe.len = cast(u32)protocol + sqe.rw_flags = {} + sqe.file_index = file_index + + ok = true + return +} + +uring_cmd :: proc() { + unimplemented() +} + +send_zc :: proc() { + unimplemented() +} + +sendmsg_zc :: proc() { + unimplemented() +} + +waitid :: proc() { + unimplemented() +} + +setxattr :: proc() { + unimplemented() +} + +getxattr :: proc() { + unimplemented() +} + +fsetxattr :: proc() { + unimplemented() +} + +fgetxattr :: proc() { + unimplemented() +} + +/* +Issues the equivalent of the bind(2) system call. + +Available since 6.11. +*/ +bind :: proc(ring: ^Ring, user_data: u64, sock: linux.Fd, addr: ^$T) -> (sqe: linux.IO_Uring_SQE, ok: bool) + where + T == linux.Sock_Addr_In || + T == linux.Sock_Addr_In6 || + T == linux.Sock_Addr_Un || + T == linux.Sock_Addr_Any +{ + sqe = get_sqe(ring) or_return + sqe.opcode = .BIND + sqe.user_data = user_data + sqe.fd = sock + sqe.addr = cast(u64)uintptr(addr) + sqe.addr2 = size_of(T) + + ok = true + return +} + +/* +Issues the equivalent of the listen(2) system call. + +fd must contain the file descriptor of the socket and addr must contain the backlog parameter, i.e. the maximum amount of pending queued connections. + +Available since 6.11. +*/ +listen :: proc(ring: ^Ring, user_data: u64, fd: linux.Fd, backlog: u64) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sqe = get_sqe(ring) or_return + sqe.opcode = .LISTEN + sqe.user_data = user_data + sqe.fd = fd + sqe.addr = backlog + + ok = true + return +} + +ftruncate :: proc() { + unimplemented() +} + +read_multishot :: proc() { + unimplemented() +} + +futex_wait :: proc() { + unimplemented() +} + +futex_wake :: proc() { + unimplemented() +} + +futex_waitv :: proc() { + unimplemented() +} + +fixed_fd_install :: proc() { + unimplemented() +} + +fixed_file :: proc() { + unimplemented() +} diff --git a/core/sys/linux/uring/uring.odin b/core/sys/linux/uring/uring.odin new file mode 100644 index 000000000..8e4315e6a --- /dev/null +++ b/core/sys/linux/uring/uring.odin @@ -0,0 +1,294 @@ +package uring + +import "core:math" +import "core:sync" +import "core:sys/linux" + +DEFAULT_THREAD_IDLE_MS :: 1000 +DEFAULT_ENTRIES :: 32 +MAX_ENTRIES :: 4096 + +Ring :: struct { + fd: linux.Fd, + sq: Submission_Queue, + cq: Completion_Queue, + flags: linux.IO_Uring_Setup_Flags, + features: linux.IO_Uring_Features, +} + +DEFAULT_PARAMS :: linux.IO_Uring_Params { + sq_thread_idle = DEFAULT_THREAD_IDLE_MS, +} + +// Initialize and setup an uring, `entries` must be a power of 2 between 1 and 4096. +init :: proc(ring: ^Ring, params: ^linux.IO_Uring_Params, entries: u32 = DEFAULT_ENTRIES) -> (err: linux.Errno) { + assert(entries <= MAX_ENTRIES, "too many entries") + assert(entries != 0, "entries must be positive") + assert(math.is_power_of_two(int(entries)), "entries must be a power of two") + + fd := linux.io_uring_setup(entries, params) or_return + defer if err != nil { linux.close(fd) } + + if .SINGLE_MMAP not_in params.features { + // NOTE: Could support this, but currently isn't. + err = .ENOSYS + return + } + + assert(.CQE32 not_in params.flags, "unsupported flag") // NOTE: Could support this by making IO_Uring generic. + assert(.SQE128 not_in params.flags, "unsupported flag") // NOTE: Could support this by making IO_Uring generic. + + sq := submission_queue_make(fd, params) or_return + + ring.fd = fd + ring.sq = sq + ring.cq = completion_queue_make(fd, params, &sq) + ring.flags = params.flags + ring.features = params.features + + return +} + +destroy :: proc(ring: ^Ring) { + assert(ring.fd >= 0) + submission_queue_destroy(&ring.sq) + linux.close(ring.fd) + ring.fd = -1 +} + +// Returns a pointer to a vacant submission queue entry, or nil if the submission queue is full. +// NOTE: extra is so you can make sure there is space for related entries, defaults to 1 so +// a link timeout op can always be added after another. +get_sqe :: proc(ring: ^Ring, extra: int = 1) -> (sqe: ^linux.IO_Uring_SQE, ok: bool) { + sq := &ring.sq + head: u32 = sync.atomic_load_explicit(sq.head, .Acquire) + next := sq.sqe_tail + 1 + + if int(next - head) > len(sq.sqes)-extra { + sqe = nil + ok = false + return + } + + sqe = &sq.sqes[sq.sqe_tail & sq.mask] + sqe^ = {} + + sq.sqe_tail = next + ok = true + return +} + +free_space :: proc(ring: ^Ring) -> int { + sq := &ring.sq + head := sync.atomic_load_explicit(sq.head, .Acquire) + next := sq.sqe_tail + 1 + free := len(sq.sqes) - int(next - head) + assert(free >= 0) + return free +} + +// Sync internal state with kernel ring state on the submission queue side. +// Returns the number of all pending events in the submission queue. +// Rationale is to determine that an enter call is needed. +flush_sq :: proc(ring: ^Ring) -> (n_pending: u32) { + sq := &ring.sq + to_submit := sq.sqe_tail - sq.sqe_head + if to_submit != 0 { + tail := sq.tail^ + i: u32 = 0 + for ; i < to_submit; i += 1 { + sq.array[tail & sq.mask] = sq.sqe_head & sq.mask + tail += 1 + sq.sqe_head += 1 + } + sync.atomic_store_explicit(sq.tail, tail, .Release) + } + n_pending = sq_ready(ring) + return +} + +// Returns true if we are not using an SQ thread (thus nobody submits but us), +// or if IORING_SQ_NEED_WAKEUP is set and the SQ thread must be explicitly awakened. +// For the latter case, we set the SQ thread wakeup flag. +// Matches the implementation of sq_ring_needs_enter() in liburing. +sq_ring_needs_enter :: proc(ring: ^Ring, flags: ^linux.IO_Uring_Enter_Flags) -> bool { + assert(flags^ == {}) + if .SQPOLL not_in ring.flags { return true } + if .NEED_WAKEUP in sync.atomic_load_explicit(ring.sq.flags, .Relaxed) { + flags^ += {.SQ_WAKEUP} + return true + } + return false +} + + +// Submits the submission queue entries acquired via get_sqe(). +// Returns the number of entries submitted. +// Optionally wait for a number of events by setting `wait_nr`, and/or set a maximum wait time by setting `timeout`. +submit :: proc(ring: ^Ring, wait_nr: u32 = 0, timeout: ^linux.Time_Spec = nil) -> (n_submitted: u32, err: linux.Errno) { + n_submitted = flush_sq(ring) + flags: linux.IO_Uring_Enter_Flags + if sq_ring_needs_enter(ring, &flags) || wait_nr > 0 { + if wait_nr > 0 || .IOPOLL in ring.flags { + flags += {.GETEVENTS} + } + + flags += {.EXT_ARG} + ext: linux.IO_Uring_Getevents_Arg + ext.ts = timeout + + n_submitted_: int + n_submitted_, err = linux.io_uring_enter2(ring.fd, n_submitted, wait_nr, flags, &ext) + assert(n_submitted_ >= 0) + n_submitted = u32(n_submitted_) + } + return +} + +// Returns the number of submission queue entries in the submission queue. +sq_ready :: proc(ring: ^Ring) -> u32 { + // Always use the shared ring state (i.e. head and not sqe_head) to avoid going out of sync, + // see https://github.com/axboe/liburing/issues/92. + return ring.sq.sqe_tail - sync.atomic_load_explicit(ring.sq.head, .Acquire) +} + +// Returns the number of completion queue entries in the completion queue (yet to consume). +cq_ready :: proc(ring: ^Ring) -> (n_ready: u32) { + return sync.atomic_load_explicit(ring.cq.tail, .Acquire) - ring.cq.head^ +} + +// Copies as many CQEs as are ready, and that can fit into the destination `cqes` slice. +// If none are available, enters into the kernel to wait for at most `wait_nr` CQEs. +// Returns the number of CQEs copied, advancing the CQ ring. +// Provides all the wait/peek methods found in liburing, but with batching and a single method. +// TODO: allow for timeout. +copy_cqes :: proc(ring: ^Ring, cqes: []linux.IO_Uring_CQE, wait_nr: u32) -> (n_copied: u32, err: linux.Errno) { + n_copied = copy_cqes_ready(ring, cqes) + if n_copied > 0 { return } + if wait_nr > 0 || cq_ring_needs_flush(ring) { + _ = linux.io_uring_enter(ring.fd, 0, wait_nr, {.GETEVENTS}, nil) or_return + n_copied = copy_cqes_ready(ring, cqes) + } + return +} + +copy_cqes_ready :: proc(ring: ^Ring, cqes: []linux.IO_Uring_CQE) -> (n_copied: u32) { + n_ready := cq_ready(ring) + n_copied = min(u32(len(cqes)), n_ready) + head := ring.cq.head^ + tail := head + n_copied + shift := u32(.CQE32 in ring.flags) + + i := 0 + for head != tail { + cqes[i] = ring.cq.cqes[(head & ring.cq.mask) << shift] + head += 1 + i += 1 + } + cq_advance(ring, n_copied) + return +} + +cq_ring_needs_flush :: proc(ring: ^Ring) -> bool { + return .CQ_OVERFLOW in sync.atomic_load_explicit(ring.sq.flags, .Relaxed) +} + +// For advanced use cases only that implement custom completion queue methods. +// If you use copy_cqes() or copy_cqe() you must not call cqe_seen() or cq_advance(). +// Must be called exactly once after a zero-copy CQE has been processed by your application. +// Not idempotent, calling more than once will result in other CQEs being lost. +// Matches the implementation of cqe_seen() in liburing. +cqe_seen :: proc(ring: ^Ring) { + cq_advance(ring, 1) +} + +// For advanced use cases only that implement custom completion queue methods. +// Matches the implementation of cq_advance() in liburing. +cq_advance :: proc(ring: ^Ring, count: u32) { + if count == 0 { return } + sync.atomic_store_explicit(ring.cq.head, ring.cq.head^ + count, .Release) +} + +Submission_Queue :: struct { + head: ^u32, + tail: ^u32, + mask: u32, + flags: ^linux.IO_Uring_Submission_Queue_Flags, + dropped: ^u32, + array: []u32, + sqes: []linux.IO_Uring_SQE, + mmap: []u8, + mmap_sqes: []u8, + + // We use `sqe_head` and `sqe_tail` in the same way as liburing: + // We increment `sqe_tail` (but not `tail`) for each call to `get_sqe()`. + // We then set `tail` to `sqe_tail` once, only when these events are actually submitted. + // This allows us to amortize the cost of the @atomicStore to `tail` across multiple SQEs. + sqe_head: u32, + sqe_tail: u32, +} + +submission_queue_make :: proc(fd: linux.Fd, params: ^linux.IO_Uring_Params) -> (sq: Submission_Queue, err: linux.Errno) { + assert(fd >= 0, "uninitialized queue fd") + assert(.SINGLE_MMAP in params.features, "unsupported feature") // NOTE: Could support this, but currently isn't. + + sq_size := params.sq_off.array + params.sq_entries * size_of(u32) + cq_size := params.cq_off.cqes + params.cq_entries * size_of(linux.IO_Uring_CQE) + size := max(sq_size, cq_size) + + // PERF: .POPULATE commits all pages right away, is that desired? + + cqe_map := cast([^]byte)(linux.mmap(0, uint(size), {.READ, .WRITE}, {.SHARED, .POPULATE}, fd, linux.IORING_OFF_SQ_RING) or_return) + defer if err != nil { linux.munmap(cqe_map, uint(size)) } + + size_sqes := params.sq_entries * size_of(linux.IO_Uring_SQE) + sqe_map := cast([^]byte)(linux.mmap(0, uint(size_sqes), {.READ, .WRITE}, {.SHARED, .POPULATE}, fd, linux.IORING_OFF_SQES) or_return) + + array := cast([^]u32)cqe_map[params.sq_off.array:] + sqes := cast([^]linux.IO_Uring_SQE)sqe_map + + sq.head = cast(^u32)&cqe_map[params.sq_off.head] + sq.tail = cast(^u32)&cqe_map[params.sq_off.tail] + sq.mask = (cast(^u32)&cqe_map[params.sq_off.ring_mask])^ + sq.flags = cast(^linux.IO_Uring_Submission_Queue_Flags)&cqe_map[params.sq_off.flags] + sq.dropped = cast(^u32)&cqe_map[params.sq_off.dropped] + sq.array = array[:params.sq_entries] + sq.sqes = sqes[:params.sq_entries] + sq.mmap = cqe_map[:size] + sq.mmap_sqes = sqe_map[:size_sqes] + + return +} + +submission_queue_destroy :: proc(sq: ^Submission_Queue) -> (err: linux.Errno) { + err = linux.munmap(raw_data(sq.mmap), uint(len(sq.mmap))) + err2 := linux.munmap(raw_data(sq.mmap_sqes), uint(len(sq.mmap_sqes))) + if err == nil { err = err2 } + return +} + +Completion_Queue :: struct { + head: ^u32, + tail: ^u32, + mask: u32, + overflow: ^u32, + cqes: []linux.IO_Uring_CQE, +} + +completion_queue_make :: proc(fd: linux.Fd, params: ^linux.IO_Uring_Params, sq: ^Submission_Queue) -> Completion_Queue { + assert(fd >= 0, "uninitialized queue fd") + assert(.SINGLE_MMAP in params.features, "required feature SINGLE_MMAP not supported") + + mmap := sq.mmap + cqes := cast([^]linux.IO_Uring_CQE)&mmap[params.cq_off.cqes] + + return( + { + head = cast(^u32)&mmap[params.cq_off.head], + tail = cast(^u32)&mmap[params.cq_off.tail], + mask = (cast(^u32)&mmap[params.cq_off.ring_mask])^, + overflow = cast(^u32)&mmap[params.cq_off.overflow], + cqes = cqes[:params.cq_entries], + } \ + ) +} diff --git a/core/sys/orca/orca.odin b/core/sys/orca/orca.odin index a36c5fa2a..a0fe0a041 100644 --- a/core/sys/orca/orca.odin +++ b/core/sys/orca/orca.odin @@ -1,6 +1,7 @@ // Bindings for the Orca platform // // See: [[ https://orca-app.dev ]] + package orca import "core:c" @@ -8,9 +9,6 @@ import "core:c" char :: c.char // currently missing in the api.json -window :: distinct u64 - -// currently missing in the api.json pool :: struct { arena: arena, freeList: list, @@ -142,17 +140,6 @@ UNICODE_VARIATION_SELECTORS_SUPPLEMENT :: unicode_range { 0xe0100, 239 } UNICODE_SUPPLEMENTARY_PRIVATE_USE_AREA_A :: unicode_range { 0xf0000, 65533 } UNICODE_SUPPLEMENTARY_PRIVATE_USE_AREA_B :: unicode_range { 0x100000, 65533 } -clock_kind :: enum c.int { - MONOTONIC, - UPTIME, - DATE, -} - -@(default_calling_convention="c", link_prefix="oc_") -foreign { - clock_time :: proc(clock: clock_kind) -> f64 --- -} - file_write_slice :: proc(file: file, slice: []char) -> u64 { return file_write(file, u64(len(slice)), raw_data(slice)) } @@ -204,6 +191,26 @@ foreign { mat2x3_rotate :: proc(radians: f32) -> mat2x3 --- // Return a 2x3 matrix representing a translation. mat2x3_translate :: proc(x: f32, y: f32) -> mat2x3 --- + // Return a 2x3 matrix representing a scale. + mat2x3_scale :: proc(x: f32, y: f32) -> mat2x3 --- +} + +//////////////////////////////////////////////////////////////////////////////// +// API for sampling the system clock. +//////////////////////////////////////////////////////////////////////////////// + +clock_kind :: enum u32 { + // A clock incrementing monotonically. + MONOTONIC = 0, + // A clock incrementing monotonically during uptime. + UPTIME = 1, + // A clock driven by the platform time. + DATE = 2, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + clock_time :: proc(clock: clock_kind) -> f64 --- } //////////////////////////////////////////////////////////////////////////////// @@ -354,10 +361,14 @@ foreign { arena_init_with_options :: proc(arena: ^arena, options: ^arena_options) --- // Release all resources allocated to a memory arena. arena_cleanup :: proc(arena: ^arena) --- - // Allocate a block of memory from an arena. + // Allocate a zero initialized block of memory from an arena. arena_push :: proc(arena: ^arena, size: u64) -> rawptr --- - // Allocate an aligned block of memory from an arena. + // Allocate an aligned, zero initialized block of memory from an arena. arena_push_aligned :: proc(arena: ^arena, size: u64, alignment: u32) -> rawptr --- + // Allocate a block of memory from an arena. + arena_push_uninitialized :: proc(arena: ^arena, size: u64) -> rawptr --- + // Allocate an aligned block of memory from an arena. + arena_push_aligned_uninitialized :: proc(arena: ^arena, size: u64, alignment: u32) -> rawptr --- // Reset an arena. All memory that was previously allocated from this arena is released to the arena, and can be reallocated by later calls to `oc_arena_push` and similar functions. No memory is actually released _to the system_. arena_clear :: proc(arena: ^arena) --- // Begin a memory scope. This creates an `oc_arena_scope` object that stores the current offset of the arena. The arena can later be reset to that offset by calling `oc_arena_scope_end`, releasing all memory that was allocated within the scope to the arena. @@ -530,15 +541,15 @@ utf8_status :: enum u32 { // The operation unexpectedly encountered the end of the utf8 sequence. OUT_OF_BOUNDS = 1, // A continuation byte was encountered where a leading byte was expected. - UNEXPECTED_CONTINUATION_BYTE = 3, + UNEXPECTED_CONTINUATION_BYTE = 2, // A leading byte was encountered in the middle of the encoding of utf8 codepoint. - UNEXPECTED_LEADING_BYTE = 4, + UNEXPECTED_LEADING_BYTE = 3, // The utf8 sequence contains an invalid byte. - INVALID_BYTE = 5, + INVALID_BYTE = 4, // The operation encountered an invalid utf8 codepoint. - INVALID_CODEPOINT = 6, + INVALID_CODEPOINT = 5, // The utf8 sequence contains an overlong encoding of a utf8 codepoint. - OVERLONG_ENCODING = 7, + OVERLONG_ENCODING = 6, } // A type representing the result of decoding of utf8-encoded codepoint. @@ -1044,6 +1055,9 @@ file_dialog_result :: struct { selection: str8_list, } +// An opaque handle identifying a window. +window :: distinct u64 + @(default_calling_convention="c", link_prefix="oc_") foreign { // Set the title of the application's window. @@ -1054,8 +1068,93 @@ foreign { request_quit :: proc() --- // Convert a scancode to a keycode, according to current keyboard layout. scancode_to_keycode :: proc(scanCode: scan_code) -> key_code --- +} + +//////////////////////////////////////////////////////////////////////////////// +// Application user input. +//////////////////////////////////////////////////////////////////////////////// + +key_state :: struct { + lastUpdate: u64, + transitionCount: u32, + repeatCount: u32, + down: bool, + sysClicked: bool, + sysDoubleClicked: bool, + sysTripleClicked: bool, +} + +keyboard_state :: struct { + keys: [349]key_state, + mods: keymod_flags, +} + +mouse_state :: struct { + lastUpdate: u64, + posValid: bool, + pos: vec2, + delta: vec2, + wheel: vec2, + using _: struct #raw_union { + buttons: [5]key_state, + using _: struct { + left: key_state, + right: key_state, + middle: key_state, + ext1: key_state, + ext2: key_state, + }, + }, +} + +BACKING_SIZE :: 64 + +text_state :: struct { + lastUpdate: u64, + backing: [64]utf32, + codePoints: str32, +} + +clipboard_state :: struct { + lastUpdate: u64, + pastedText: str8, +} + +input_state :: struct { + frameCounter: u64, + keyboard: keyboard_state, + mouse: mouse_state, + text: text_state, + clipboard: clipboard_state, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + input_process_event :: proc(arena: ^arena, state: ^input_state, event: ^event) --- + input_next_frame :: proc(state: ^input_state) --- + key_down :: proc(state: ^input_state, key: key_code) -> bool --- + key_press_count :: proc(state: ^input_state, key: key_code) -> u8 --- + key_release_count :: proc(state: ^input_state, key: key_code) -> u8 --- + key_repeat_count :: proc(state: ^input_state, key: key_code) -> u8 --- + key_down_scancode :: proc(state: ^input_state, key: scan_code) -> bool --- + key_press_count_scancode :: proc(state: ^input_state, key: scan_code) -> u8 --- + key_release_count_scancode :: proc(state: ^input_state, key: scan_code) -> u8 --- + key_repeat_count_scancode :: proc(state: ^input_state, key: scan_code) -> u8 --- + mouse_down :: proc(state: ^input_state, button: mouse_button) -> bool --- + mouse_pressed :: proc(state: ^input_state, button: mouse_button) -> u8 --- + mouse_released :: proc(state: ^input_state, button: mouse_button) -> u8 --- + mouse_clicked :: proc(state: ^input_state, button: mouse_button) -> bool --- + mouse_double_clicked :: proc(state: ^input_state, button: mouse_button) -> bool --- + mouse_position :: proc(state: ^input_state) -> vec2 --- + mouse_delta :: proc(state: ^input_state) -> vec2 --- + mouse_wheel :: proc(state: ^input_state) -> vec2 --- + input_text_utf32 :: proc(arena: ^arena, state: ^input_state) -> str32 --- + input_text_utf8 :: proc(arena: ^arena, state: ^input_state) -> str8 --- // Put a string in the clipboard. clipboard_set_string :: proc(string: str8) --- + clipboard_pasted :: proc(state: ^input_state) -> bool --- + clipboard_pasted_text :: proc(state: ^input_state) -> str8 --- + key_mods :: proc(state: ^input_state) -> keymod_flags --- } //////////////////////////////////////////////////////////////////////////////// @@ -1073,7 +1172,7 @@ file :: distinct u64 // Flags for the `oc_file_open()` function. file_open_flag :: enum u16 { // Open the file in 'append' mode. All writes append data at the end of the file. - APPEND = 1, + APPEND = 0, // Truncate the file to 0 bytes when opening. TRUNCATE, // Create the file if it does not exist. @@ -1090,7 +1189,7 @@ file_open_flags :: bit_set[file_open_flag; u16] // This enum describes the access permissions of a file handle. file_access_flag :: enum u16 { // The file handle can be used for reading from the file. - READ = 1, + READ = 0, // The file handle can be used for writing to the file. WRITE, } @@ -1242,7 +1341,7 @@ file_type :: enum u32 { // A type describing file permissions. file_perm_flag :: enum u16 { - OTHER_EXEC = 1, + OTHER_EXEC = 0, OTHER_WRITE, OTHER_READ, GROUP_EXEC, @@ -1272,6 +1371,18 @@ file_status :: struct { modificationDate: datestamp, } +// An type describing a list of enumerated files in a given directory. +file_list :: struct { + list: list, + eltCount: u64, +} + +file_listdir_elt :: struct { + listElt: list_elt, + basename: str8, + type: file_type, +} + @(default_calling_convention="c", link_prefix="oc_") foreign { // Send a single I/O request and wait for its completion. @@ -1299,6 +1410,7 @@ foreign { file_get_status :: proc(file: file) -> file_status --- file_size :: proc(file: file) -> u64 --- file_open_with_request :: proc(path: str8, rights: file_access, flags: file_open_flags) -> file --- + file_listdir :: proc(arena: ^arena, directory: str8) -> file_list --- } //////////////////////////////////////////////////////////////////////////////// @@ -1703,87 +1815,6 @@ foreign { // Graphical User Interface API. //////////////////////////////////////////////////////////////////////////////// -key_state :: struct { - lastUpdate: u64, - transitionCount: u32, - repeatCount: u32, - down: bool, - sysClicked: bool, - sysDoubleClicked: bool, - sysTripleClicked: bool, -} - -keyboard_state :: struct { - keys: [349]key_state, - mods: keymod_flags, -} - -mouse_state :: struct { - lastUpdate: u64, - posValid: bool, - pos: vec2, - delta: vec2, - wheel: vec2, - using _: struct #raw_union { - buttons: [5]key_state, - using _: struct { - left: key_state, - right: key_state, - middle: key_state, - ext1: key_state, - ext2: key_state, - }, - }, -} - -BACKING_SIZE :: 64 - -text_state :: struct { - lastUpdate: u64, - backing: [64]utf32, - codePoints: str32, -} - -clipboard_state :: struct { - lastUpdate: u64, - pastedText: str8, -} - -input_state :: struct { - frameCounter: u64, - keyboard: keyboard_state, - mouse: mouse_state, - text: text_state, - clipboard: clipboard_state, -} - -@(default_calling_convention="c", link_prefix="oc_") -foreign { - input_process_event :: proc(arena: ^arena, state: ^input_state, event: ^event) --- - input_next_frame :: proc(state: ^input_state) --- - key_down :: proc(state: ^input_state, key: key_code) -> bool --- - key_press_count :: proc(state: ^input_state, key: key_code) -> u8 --- - key_release_count :: proc(state: ^input_state, key: key_code) -> u8 --- - key_repeat_count :: proc(state: ^input_state, key: key_code) -> u8 --- - key_down_scancode :: proc(state: ^input_state, key: scan_code) -> bool --- - key_press_count_scancode :: proc(state: ^input_state, key: scan_code) -> u8 --- - key_release_count_scancode :: proc(state: ^input_state, key: scan_code) -> u8 --- - key_repeat_count_scancode :: proc(state: ^input_state, key: scan_code) -> u8 --- - mouse_down :: proc(state: ^input_state, button: mouse_button) -> bool --- - mouse_pressed :: proc(state: ^input_state, button: mouse_button) -> u8 --- - mouse_released :: proc(state: ^input_state, button: mouse_button) -> u8 --- - mouse_clicked :: proc(state: ^input_state, button: mouse_button) -> bool --- - mouse_double_clicked :: proc(state: ^input_state, button: mouse_button) -> bool --- - mouse_position :: proc(state: ^input_state) -> vec2 --- - mouse_delta :: proc(state: ^input_state) -> vec2 --- - mouse_wheel :: proc(state: ^input_state) -> vec2 --- - input_text_utf32 :: proc(arena: ^arena, state: ^input_state) -> str32 --- - input_text_utf8 :: proc(arena: ^arena, state: ^input_state) -> str8 --- - clipboard_pasted :: proc(state: ^input_state) -> bool --- - clipboard_pasted_text :: proc(state: ^input_state) -> str8 --- - key_mods :: proc(state: ^input_state) -> keymod_flags --- -} - //////////////////////////////////////////////////////////////////////////////// // Graphical User Interface Core API. //////////////////////////////////////////////////////////////////////////////// @@ -1857,11 +1888,11 @@ ui_attribute_mask :: enum u32 { SIZE_WIDTH = 1, SIZE_HEIGHT = 2, LAYOUT_AXIS = 4, - LAYOUT_ALIGN_X = 64, - LAYOUT_ALIGN_Y = 128, - LAYOUT_SPACING = 32, LAYOUT_MARGIN_X = 8, LAYOUT_MARGIN_Y = 16, + LAYOUT_SPACING = 32, + LAYOUT_ALIGN_X = 64, + LAYOUT_ALIGN_Y = 128, FLOATING_X = 256, FLOATING_Y = 512, FLOAT_TARGET_X = 1024, @@ -1873,10 +1904,10 @@ ui_attribute_mask :: enum u32 { COLOR = 65536, BG_COLOR = 131072, BORDER_COLOR = 262144, - BORDER_SIZE = 2097152, - ROUNDNESS = 4194304, FONT = 524288, FONT_SIZE = 1048576, + BORDER_SIZE = 2097152, + ROUNDNESS = 4194304, DRAW_MASK = 8388608, ANIMATION_TIME = 16777216, ANIMATION_MASK = 33554432, @@ -1898,6 +1929,7 @@ ui_box_size :: [2]ui_size ui_box_floating :: [2]bool +// These bits *disable* the corresponding element when they're set. ui_draw_mask :: enum u32 { BACKGROUND = 1, BORDER = 2, diff --git a/core/sys/posix/fcntl.odin b/core/sys/posix/fcntl.odin index db095c418..52d97f528 100644 --- a/core/sys/posix/fcntl.odin +++ b/core/sys/posix/fcntl.odin @@ -70,7 +70,7 @@ foreign lib { [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html ]] */ - openat :: proc(fd: FD, path: cstring, flags: O_Flags, mode: mode_t = {}) -> FD --- + openat :: proc(fd: FD, path: cstring, flags: O_Flags, #c_vararg mode: ..mode_t) -> FD --- } FCNTL_Cmd :: enum c.int { diff --git a/core/sys/win32/removal.odin b/core/sys/win32/removal.odin deleted file mode 100644 index 09c16aa05..000000000 --- a/core/sys/win32/removal.odin +++ /dev/null @@ -1,3 +0,0 @@ -package sys_win32 - -#panic(`"core:sys/win32" has been removed. Please use "core:sys/windows"`)
\ No newline at end of file diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 0ad11121e..07f34bed2 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -30,6 +30,18 @@ EV_RXCHAR :: DWORD(0x0001) EV_RXFLAG :: DWORD(0x0002) EV_TXEMPTY :: DWORD(0x0004) +WAITORTIMERCALLBACK :: #type proc "system" (lpParameter: PVOID, TimerOrWaitFired: BOOLEAN) + +PAPCFUNC :: #type proc "system" (Parameter: ULONG_PTR) + +WT_EXECUTEDEFAULT :: 0x00000000 +WT_EXECUTEINIOTHREAD :: 0x00000001 +WT_EXECUTEINPERSISTENTTHREAD :: 0x00000080 +WT_EXECUTEINWAITTHREAD :: 0x00000004 +WT_EXECUTELONGFUNCTION :: 0x00000010 +WT_EXECUTEONLYONCE :: 0x00000008 +WT_TRANSFER_IMPERSONATION :: 0x00000100 + @(default_calling_convention="system") foreign kernel32 { OutputDebugStringA :: proc(lpOutputString: LPCSTR) --- // The only A thing that is allowed @@ -567,7 +579,7 @@ foreign kernel32 { // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatusex) GetQueuedCompletionStatusEx :: proc(CompletionPort: HANDLE, lpCompletionPortEntries: ^OVERLAPPED_ENTRY, ulCount: c_ulong, ulNumEntriesRemoved: ^c_ulong, dwMilliseconds: DWORD, fAlertable: BOOL) -> BOOL --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-postqueuedcompletionstatus) - PostQueuedCompletionStatus :: proc(CompletionPort: HANDLE, dwNumberOfBytesTransferred: DWORD, dwCompletionKey: c_ulong, lpOverlapped: ^OVERLAPPED) -> BOOL --- + PostQueuedCompletionStatus :: proc(CompletionPort: HANDLE, dwNumberOfBytesTransferred: DWORD, dwCompletionKey: ULONG_PTR, lpOverlapped: ^OVERLAPPED) -> BOOL --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-gethandleinformation) GetHandleInformation :: proc(hObject: HANDLE, lpdwFlags: ^DWORD) -> BOOL --- @@ -575,6 +587,19 @@ foreign kernel32 { RtlNtStatusToDosError :: proc(status: NTSTATUS) -> ULONG --- GetSystemPowerStatus :: proc(lpSystemPowerStatus: ^SYSTEM_POWER_STATUS) -> BOOL --- + + RegisterWaitForSingleObject :: proc( + phNewWaitObject: PHANDLE, + hObject: HANDLE, + Callback: WAITORTIMERCALLBACK, + Context: PVOID, + dwMilliseconds: ULONG, + dwFlags: ULONG, + ) -> BOOL --- + + UnregisterWaitEx :: proc(WaitHandle: HANDLE, CompletionEvent: HANDLE) -> BOOL --- + + QueueUserAPC :: proc(pfnAPC: PAPCFUNC, hThread: HANDLE, dwData: ULONG_PTR) -> DWORD --- } DEBUG_PROCESS :: 0x00000001 diff --git a/core/sys/windows/mswsock.odin b/core/sys/windows/mswsock.odin new file mode 100644 index 000000000..9019fc821 --- /dev/null +++ b/core/sys/windows/mswsock.odin @@ -0,0 +1,40 @@ +#+build windows +package sys_windows + +foreign import mswsock "system:mswsock.lib" + +foreign mswsock { + TransmitFile :: proc( + hSocket: SOCKET, + hFile: HANDLE, + nNumberOfBytesToWrite: DWORD, + nNumberOfBytesPerSend: DWORD, + lpOverlapped: LPOVERLAPPED, + lpTransmitBuffers: rawptr, + dwReserved: DWORD, + ) -> BOOL --- + + AcceptEx :: proc( + sListenSocket: SOCKET, + sAcceptSocket: SOCKET, + lpOutputBuffer: PVOID, + dwReceiveDataLength: DWORD, + dwLocalAddressLength: DWORD, + dwRemoteAddressLength: DWORD, + lpdwBytesReceived: LPDWORD, + lpOverlapped: LPOVERLAPPED, + ) -> BOOL --- + + GetAcceptExSockaddrs :: proc( + lpOutputBuffer: PVOID, + dwReceiveDataLength: DWORD, + dwLocalAddressLength: DWORD, + dwRemoteAddressLength: DWORD, + LocalSockaddr: ^^sockaddr, + LocalSockaddrLength: LPINT, + RemoteSockaddr: ^^sockaddr, + RemoteSockaddrLength: LPINT, + ) --- +} + +SO_UPDATE_CONNECT_CONTEXT :: 0x7010
\ No newline at end of file diff --git a/core/sys/windows/ntdll.odin b/core/sys/windows/ntdll.odin index 747130749..8362bb9df 100644 --- a/core/sys/windows/ntdll.odin +++ b/core/sys/windows/ntdll.odin @@ -36,6 +36,20 @@ foreign ntdll_lib { QueryFlags: ULONG, FileName : PUNICODE_STRING, ) -> NTSTATUS --- + + NtCreateFile :: proc( + FileHandle: PHANDLE, + DesiredAccess: ACCESS_MASK, + ObjectAttributes: POBJECT_ATTRIBUTES, + IoStatusBlock: PIO_STATUS_BLOCK, + AllocationSize: PLARGE_INTEGER, + FileAttributes: ULONG, + ShareAccess: ULONG, + CreateDisposition: ULONG, + CreateOptions: ULONG, + EaBuffer: PVOID, + EaLength: ULONG, + ) -> NTSTATUS --- } @@ -256,4 +270,13 @@ RTL_DRIVE_LETTER_CURDIR :: struct { LIST_ENTRY :: struct { Flink: ^LIST_ENTRY, Blink: ^LIST_ENTRY, -}
\ No newline at end of file +} + +FILE_SUPERSEDE :: 0x00000000 +FILE_OPEN :: 0x00000001 +FILE_CREATE :: 0x00000002 +FILE_OPEN_IF :: 0x00000003 +FILE_OVERWRITE :: 0x00000004 +FILE_OVERWRITE_IF :: 0x00000005 + +FILE_NON_DIRECTORY_FILE :: 0x00000040 diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index e1a14fb94..f3f581844 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -2726,6 +2726,8 @@ WAIT_OBJECT_0 : DWORD : 0x00000000 WAIT_TIMEOUT : DWORD : 258 WAIT_FAILED : DWORD : 0xFFFFFFFF +WAIT_IO_COMPLETION: DWORD : 0x000000C0 + FILE_FLAG_WRITE_THROUGH : DWORD : 0x80000000 FILE_FLAG_OVERLAPPED : DWORD : 0x40000000 FILE_FLAG_NO_BUFFERING : DWORD : 0x20000000 @@ -3139,6 +3141,7 @@ OBJECT_ATTRIBUTES :: struct { SecurityDescriptor: rawptr, SecurityQualityOfService: rawptr, } +POBJECT_ATTRIBUTES :: ^OBJECT_ATTRIBUTES PUNICODE_STRING :: ^UNICODE_STRING UNICODE_STRING :: struct { @@ -3150,9 +3153,14 @@ UNICODE_STRING :: struct { OVERLAPPED :: struct { Internal: ^c_ulong, InternalHigh: ^c_ulong, - Offset: DWORD, - OffsetHigh: DWORD, - hEvent: HANDLE, + using _: struct #raw_union { + using _: struct { + Offset: DWORD, + OffsetHigh: DWORD, + }, + OffsetFull: u64, // Convenience field to set Offset and OffsetHigh with one value. + }, + hEvent: HANDLE, } OVERLAPPED_ENTRY :: struct { diff --git a/core/sys/windows/winerror.odin b/core/sys/windows/winerror.odin index 23467761d..2807ca0f8 100644 --- a/core/sys/windows/winerror.odin +++ b/core/sys/windows/winerror.odin @@ -248,6 +248,13 @@ E_HANDLE :: 0x80070006 // Handle that is not valid E_OUTOFMEMORY :: 0x8007000E // Failed to allocate necessary memory E_INVALIDARG :: 0x80070057 // One or more arguments are not valid +SEC_E_INCOMPLETE_MESSAGE :: 0x80090318 + +SEC_I_INCOMPLETE_CREDENTIALS :: 0x00090320 +SEC_I_CONTINUE_NEEDED :: 0x00090312 +SEC_I_CONTEXT_EXPIRED :: 0x00090317 +SEC_I_RENEGOTIATE :: 0x00090321 + // Severity values SEVERITY :: enum DWORD { SUCCESS = 0, diff --git a/core/sys/windows/ws2_32.odin b/core/sys/windows/ws2_32.odin index ad9089c6e..77a288d6f 100644 --- a/core/sys/windows/ws2_32.odin +++ b/core/sys/windows/ws2_32.odin @@ -38,7 +38,7 @@ WSANETWORKEVENTS :: struct { WSAID_ACCEPTEX :: GUID{0xb5367df1, 0xcbac, 0x11cf, {0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92}} WSAID_GETACCEPTEXSOCKADDRS :: GUID{0xb5367df2, 0xcbac, 0x11cf, {0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92}} -WSAID_CONNECTX :: GUID{0x25a207b9, 0xddf3, 0x4660, {0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e}} +WSAID_CONNECTEX :: GUID{0x25a207b9, 0xddf3, 0x4660, {0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e}} SIO_GET_EXTENSION_FUNCTION_POINTER :: IOC_INOUT | IOC_WS2 | 6 SIO_UDP_CONNRESET :: IOC_IN | IOC_VENDOR | 12 @@ -129,7 +129,7 @@ foreign ws2_32 { dwBufferCount: DWORD, lpNumberOfBytesSent: LPDWORD, dwFlags: DWORD, - lpTo: ^SOCKADDR_STORAGE_LH, + lpTo: ^sockaddr, iToLen: c_int, lpOverlapped: LPWSAOVERLAPPED, lpCompletionRoutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE, @@ -151,8 +151,8 @@ foreign ws2_32 { dwBufferCount: DWORD, lpNumberOfBytesRecvd: LPDWORD, lpFlags: LPDWORD, - lpFrom: ^SOCKADDR_STORAGE_LH, - lpFromlen: ^c_int, + lpFrom: ^sockaddr, + lpFromlen: LPINT, lpOverlapped: LPWSAOVERLAPPED, lpCompletionRoutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE, ) -> c_int --- diff --git a/core/thread/thread.odin b/core/thread/thread.odin index 26c1a3e27..a07801b98 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -5,7 +5,10 @@ import "base:runtime" import "core:mem" import "base:intrinsics" -_ :: intrinsics +@(private) +unall :: intrinsics.unaligned_load +@(private) +unals :: intrinsics.unaligned_store /* Value, specifying whether `core:thread` functionality is available on the @@ -347,7 +350,9 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex thread_proc :: proc(t: ^Thread) { fn := cast(proc(T))t.data assert(t.user_index >= 1) - data := (^T)(&t.user_args[0])^ + + data := unall((^T)(&t.user_args)) + fn(data) } if t = create(thread_proc, priority); t == nil { @@ -356,9 +361,7 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex t.data = rawptr(fn) t.user_index = 1 - data := data - - mem.copy(&t.user_args[0], &data, size_of(T)) + unals((^T)(&t.user_args), data) if self_cleanup { intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) @@ -393,9 +396,10 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), fn := cast(proc(T1, T2))t.data assert(t.user_index >= 2) - user_args := mem.slice_to_bytes(t.user_args[:]) - arg1 := (^T1)(raw_data(user_args))^ - arg2 := (^T2)(raw_data(user_args[size_of(T1):]))^ + ptr := uintptr(&t.user_args) + + arg1 := unall((^T1)(rawptr(ptr))) + arg2 := unall((^T2)(rawptr(ptr + size_of(T1)))) fn(arg1, arg2) } @@ -405,11 +409,10 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), t.data = rawptr(fn) t.user_index = 2 - arg1, arg2 := arg1, arg2 - user_args := mem.slice_to_bytes(t.user_args[:]) + ptr := uintptr(&t.user_args) - n := copy(user_args, mem.ptr_to_bytes(&arg1)) - _ = copy(user_args[n:], mem.ptr_to_bytes(&arg2)) + unals((^T1)(rawptr(ptr)), arg1) + unals((^T2)(rawptr(ptr + size_of(T1))), arg2) if self_cleanup { intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) @@ -444,10 +447,11 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr fn := cast(proc(T1, T2, T3))t.data assert(t.user_index >= 3) - user_args := mem.slice_to_bytes(t.user_args[:]) - arg1 := (^T1)(raw_data(user_args))^ - arg2 := (^T2)(raw_data(user_args[size_of(T1):]))^ - arg3 := (^T3)(raw_data(user_args[size_of(T1) + size_of(T2):]))^ + ptr := uintptr(&t.user_args) + + arg1 := unall((^T1)(rawptr(ptr))) + arg2 := unall((^T2)(rawptr(ptr + size_of(T1)))) + arg3 := unall((^T3)(rawptr(ptr + size_of(T1) + size_of(T2)))) fn(arg1, arg2, arg3) } @@ -457,12 +461,11 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr t.data = rawptr(fn) t.user_index = 3 - arg1, arg2, arg3 := arg1, arg2, arg3 - user_args := mem.slice_to_bytes(t.user_args[:]) + ptr := uintptr(&t.user_args) - n := copy(user_args, mem.ptr_to_bytes(&arg1)) - n += copy(user_args[n:], mem.ptr_to_bytes(&arg2)) - _ = copy(user_args[n:], mem.ptr_to_bytes(&arg3)) + unals((^T1)(rawptr(ptr)), arg1) + unals((^T2)(rawptr(ptr + size_of(T1))), arg2) + unals((^T3)(rawptr(ptr + size_of(T1) + size_of(T2))), arg3) if self_cleanup { intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index d7a03d04c..10f1438d7 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -20,6 +20,9 @@ Task :: struct { allocator: mem.Allocator, } +Thread_Init_Proc :: #type proc(thread: ^Thread, user_data: rawptr) +Thread_Fini_Proc :: #type proc(thread: ^Thread, user_data: rawptr) + // Do not access the pool's members directly while the pool threads are running, // since they use different kinds of locking and mutual exclusion devices. // Careless access can and will lead to nasty bugs. Once initialized, the @@ -36,6 +39,13 @@ Pool :: struct { num_done: int, // end of atomics + // called once per thread at startup + thread_init_proc: Thread_Init_Proc, + thread_init_data: rawptr, + // called once per thread at shutdown + thread_fini_proc: Thread_Fini_Proc, + thread_fini_data: rawptr, + is_running: bool, threads: []^Thread, @@ -55,6 +65,10 @@ pool_thread_runner :: proc(t: ^Thread) { data := cast(^Pool_Thread_Data)t.data pool := data.pool + if pool.thread_init_proc != nil { + pool.thread_init_proc(t, pool.thread_init_data) + } + for intrinsics.atomic_load(&pool.is_running) { sync.wait(&pool.sem_available) @@ -66,6 +80,10 @@ pool_thread_runner :: proc(t: ^Thread) { } } + if pool.thread_fini_proc != nil { + pool.thread_fini_proc(t, pool.thread_fini_data) + } + sync.post(&pool.sem_available, 1) } @@ -73,13 +91,26 @@ pool_thread_runner :: proc(t: ^Thread) { // it is destroyed. // // The thread pool requires an allocator which it either owns, or which is thread safe. -pool_init :: proc(pool: ^Pool, allocator: mem.Allocator, thread_count: int) { +pool_init :: proc( + pool: ^Pool, + allocator: mem.Allocator, + thread_count: int, + init_proc: Thread_Init_Proc = nil, + init_data: rawptr = nil, + fini_proc: Thread_Init_Proc = nil, + fini_data: rawptr = nil, +){ context.allocator = allocator pool.allocator = allocator queue.init(&pool.tasks) pool.tasks_done = make([dynamic]Task) pool.threads = make([]^Thread, max(thread_count, 1)) + pool.thread_init_proc = init_proc + pool.thread_fini_proc = fini_proc + pool.thread_init_data = init_data + pool.thread_fini_data = fini_data + pool.is_running = true for _, i in pool.threads { diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index e18ea593d..af2a4a3c1 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -107,7 +107,11 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { high := posix.sched_get_priority_max(policy) switch priority { case .Normal: // Okay - case .Low: params.sched_priority = low + 1 + case .Low: + params.sched_priority = low + 1 + if params.sched_priority >= high { + params.sched_priority = low + } case .High: params.sched_priority = high } res = posix.pthread_attr_setschedparam(&attrs, ¶ms) diff --git a/core/unicode/tools/generate_entity_table.odin b/core/unicode/tools/generate_entity_table.odin index 020ef94e4..9517b632b 100644 --- a/core/unicode/tools/generate_entity_table.odin +++ b/core/unicode/tools/generate_entity_table.odin @@ -18,7 +18,7 @@ OPTIONS :: xml.Options{ flags = { .Ignore_Unsupported, }, expected_doctype = "un Entity :: struct { name: string, - codepoint: rune, + codepoints: [2]rune, description: string, } @@ -57,7 +57,10 @@ generate_encoding_entity_table :: proc() { fmt.printf("Found `<charlist>` with %v children.\n", len(charlist.value)) entity_map: map[string]Entity + defer delete(entity_map) + names: [dynamic]string + defer delete(names) min_name_length := max(int) max_name_length := min(int) @@ -78,7 +81,13 @@ generate_encoding_entity_table :: proc() { fmt.eprintln("`<character id=\"...\">` attribute not found.") os.exit(1) } else { - codepoint := strconv.atoi(codepoint_string) + r1, _, r2 := strings.partition(codepoint_string, "-") + + codepoint, codepoint2: int + codepoint, _ = strconv.parse_int(r1) + if r2 != "" { + codepoint2, _ = strconv.parse_int(r2) + } desc, desc_ok := xml.find_child_by_ident(doc, id, "description") description := "" @@ -115,7 +124,7 @@ generate_encoding_entity_table :: proc() { e := Entity{ name = name, - codepoint = rune(codepoint), + codepoints = {rune(codepoint), rune(codepoint2)}, description = description, } @@ -162,22 +171,22 @@ generate_encoding_entity_table :: proc() { entity_name - a string, like "copy" that describes a user-encoded Unicode entity as used in XML. Returns: - "decoded" - The decoded rune if found by name, or -1 otherwise. - "ok" - true if found, false if not. + "decoded" - The decoded runes if found by name, or all zero otherwise. + "rune_count" - The number of decoded runes + "ok" - true if found, false if not. IMPORTANT: XML processors (including browsers) treat these names as case-sensitive. So do we. */ -named_xml_entity_to_rune :: proc(name: string) -> (decoded: rune, ok: bool) { +named_xml_entity_to_rune :: proc(name: string) -> (decoded: [2]rune, rune_count: int, ok: bool) { /* Early out if the name is too short or too long. min as a precaution in case the generated table has a bogus value. */ if len(name) < min(1, XML_NAME_TO_RUNE_MIN_LENGTH) || len(name) > XML_NAME_TO_RUNE_MAX_LENGTH { - return -1, false + return } - switch rune(name[0]) { -`) + switch rune(name[0]) {`) prefix := '?' should_close := false @@ -200,13 +209,17 @@ named_xml_entity_to_rune :: proc(name: string) -> (decoded: rune, ok: bool) { fmt.wprintf(w, " ") } fmt.wprintf(w, " // %v\n", e.description) - fmt.wprintf(w, "\t\t\treturn %v, true\n", rune_to_string(e.codepoint)) + if e.codepoints[1] != 0 { + fmt.wprintf(w, "\t\t\treturn {{%q, %q}}, 2, true\n", e.codepoints[0], e.codepoints[1]) + } else { + fmt.wprintf(w, "\t\t\treturn {{%q, 0}}, 1, true\n", e.codepoints[0]) + } should_close = true } fmt.wprintln(w, "\t\t}") fmt.wprintln(w, "\t}") - fmt.wprintln(w, "\treturn -1, false") + fmt.wprintln(w, "\treturn") fmt.wprintln(w, "}\n") fmt.wprintln(w, GENERATED) @@ -221,12 +234,6 @@ named_xml_entity_to_rune :: proc(name: string) -> (decoded: rune, ok: bool) { } else { fmt.printf("Failed to write generated \"%v\".\n", generated_filename) } - - delete(entity_map) - delete(names) - for &name in names { - free(&name) - } } GENERATED :: `/* @@ -262,14 +269,6 @@ TABLE_FILE_PROLOG :: `/* */ ` -rune_to_string :: proc(r: rune) -> (res: string) { - res = fmt.tprintf("%08x", int(r)) - for len(res) > 2 && res[:2] == "00" { - res = res[2:] - } - return fmt.tprintf("rune(0x%v)", res) -} - is_dotted_name :: proc(name: string) -> (dotted: bool) { for r in name { if r == '.' { return true} diff --git a/examples/all/all_linux.odin b/examples/all/all_linux.odin index 2b70fa1e1..d7d005ba0 100644 --- a/examples/all/all_linux.odin +++ b/examples/all/all_linux.odin @@ -2,4 +2,5 @@ package all @(require) import "core:sys/linux" -@(require) import "vendor:x11/xlib"
\ No newline at end of file +@(require) import "core:sys/linux/uring" +@(require) import "vendor:x11/xlib" diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 7895b4640..9a7613ba5 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -17,6 +17,7 @@ package all @(require) import "core:container/avl" @(require) import "core:container/bit_array" +@(require) import "core:container/pool" @(require) import "core:container/priority_queue" @(require) import "core:container/queue" @(require) import "core:container/small_array" @@ -104,6 +105,8 @@ package all @(require) import "core:mem/tlsf" @(require) import "core:mem/virtual" +@(require) import "core:nbio" + @(require) import "core:odin/ast" @(require) import doc_format "core:odin/doc-format" @(require) import "core:odin/parser" @@ -156,4 +159,4 @@ package all @(require) import "core:unicode/utf8/utf8string" @(require) import "core:unicode/utf16" -main :: proc() {}
\ No newline at end of file +main :: proc() {} diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 7160f3721..7d43788cd 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -582,6 +582,7 @@ struct BuildContext { RelocMode reloc_mode; bool disable_red_zone; + bool disable_unwind; isize max_error_count; @@ -839,6 +840,15 @@ gb_global TargetMetrics target_freestanding_amd64_win64 = { TargetABI_Win64, }; +gb_global TargetMetrics target_freestanding_amd64_mingw = { + TargetOs_freestanding, + TargetArch_amd64, + 8, 8, AMD64_MAX_ALIGNMENT, 32, + str_lit("x86_64-pc-windows-gnu"), + TargetABI_Win64, +}; + + gb_global TargetMetrics target_freestanding_arm64 = { TargetOs_freestanding, TargetArch_arm64, @@ -901,6 +911,7 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("freestanding_amd64_sysv"), &target_freestanding_amd64_sysv }, { str_lit("freestanding_amd64_win64"), &target_freestanding_amd64_win64 }, + { str_lit("freestanding_amd64_mingw"), &target_freestanding_amd64_mingw }, { str_lit("freestanding_arm64"), &target_freestanding_arm64 }, { str_lit("freestanding_arm32"), &target_freestanding_arm32 }, diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 1b3e6912c..929891826 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -2695,6 +2695,16 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_size_of: { // size_of :: proc(Type or expr) -> untyped int + if (ce->args[0]->kind == Ast_UnaryExpr) { + ast_node(arg, UnaryExpr, ce->args[0]); + if (arg->op.kind == Token_And) { + ERROR_BLOCK(); + + warning(ce->args[0], "'size_of(&x)' returns the size of a pointer, not the size of x"); + error_line("\tSuggestion: Use 'size_of(rawptr)' if you want the size of the pointer"); + } + } + Operand o = {}; check_expr_or_type(c, &o, ce->args[0]); if (o.mode == Addressing_Invalid) { diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 27babd255..8019d00c3 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1473,6 +1473,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { e->Procedure.no_sanitize_address = ac.no_sanitize_address; e->Procedure.no_sanitize_memory = ac.no_sanitize_memory; + e->Procedure.no_sanitize_thread = ac.no_sanitize_thread; e->deprecated_message = ac.deprecated_message; e->warning_message = ac.warning_message; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 2b2ae09cc..99f803a08 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1133,7 +1133,11 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ x.mode = Addressing_Value; x.type = t; if (check_is_assignable_to(c, &x, type)) { - add_entity_use(c, operand->expr, e); + if (operand->expr->kind == Ast_SelectorExpr) { + add_entity_use(c, operand->expr->SelectorExpr.selector, e); + } else { + add_entity_use(c, operand->expr, e); + } good = true; break; } @@ -3528,7 +3532,7 @@ gb_internal bool check_cast_internal(CheckerContext *c, Operand *x, Type *type) if (core_type(bt)->kind == Type_Basic) { return check_representable_as_constant(c, x->value, type, &x->value) || (is_type_pointer(type) && check_is_castable_to(c, x, type)); - } else if (!are_types_identical(elem, bt) && elem->kind == Type_Basic) { + } else if (!are_types_identical(elem, bt) && elem->kind == Type_Basic && x->type->kind == Type_Basic) { return check_representable_as_constant(c, x->value, elem, &x->value) || (is_type_pointer(elem) && check_is_castable_to(c, x, elem)); } else if (check_is_castable_to(c, x, type)) { @@ -8210,7 +8214,7 @@ gb_internal void check_objc_call_expr(CheckerContext *c, Operand *operand, Ast * add_objc_proc_type(c, call, return_type, param_types); } -gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *proc, Slice<Ast *> const &args, ProcInlining inlining, Type *type_hint) { +gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *proc, Slice<Ast *> const &args, ProcInlining inlining, ProcTailing tailing, Type *type_hint) { if (proc != nullptr && proc->kind == Ast_BasicDirective) { ast_node(bd, BasicDirective, proc); @@ -8241,7 +8245,10 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c return Expr_Expr; } if (inlining != ProcInlining_none) { - error(call, "Inlining operators are not allowed on built-in procedures"); + error(call, "Inlining directives are not allowed on built-in procedures"); + } + if (tailing != ProcTailing_none) { + error(call, "Tailing directives are not allowed on built-in procedures"); } } else { if (proc != nullptr) { @@ -8383,6 +8390,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } bool is_call_inlined = false; + bool is_call_tailed = true; switch (inlining) { case ProcInlining_inline: @@ -8417,6 +8425,23 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } } + switch (tailing) { + case ProcTailing_none: + break; + case ProcTailing_must_tail: + is_call_tailed = true; + if (c->curr_proc_sig == nullptr || !are_types_identical(c->curr_proc_sig, pt)) { + ERROR_BLOCK(); + gbString a = type_to_string(pt); + gbString b = type_to_string(c->curr_proc_sig); + error(call, "Use of '#must_tail' of a procedure must have the same type as the procedure it was called within"); + error_line("\tCall type: %s, parent type: %s", a, b); + gb_string_free(b); + gb_string_free(a); + } + break; + } + { String invalid; if (pt->kind == Type_Proc && pt->Proc.require_target_feature.len != 0) { @@ -11554,6 +11579,15 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast return kind; case_end; + case_ast_node(ht, HelperType, node); + Type *type = check_type(c, ht->type); + if (type != nullptr && type != t_invalid) { + o->mode = Addressing_Type; + o->type = type; + } + return kind; + case_end; + case_ast_node(i, Implicit, node); switch (i->kind) { case Token_context: @@ -11816,7 +11850,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast case_end; case_ast_node(ce, CallExpr, node); - return check_call_expr(c, o, node, ce->proc, ce->args, ce->inlining, type_hint); + return check_call_expr(c, o, node, ce->proc, ce->args, ce->inlining, ce->tailing, type_hint); case_end; case_ast_node(de, DerefExpr, node); @@ -12557,6 +12591,12 @@ gb_internal gbString write_expr_to_string(gbString str, Ast *node, bool shorthan case_end; case_ast_node(ce, CallExpr, node); + switch (ce->tailing) { + case ProcTailing_must_tail: + str = gb_string_appendc(str, "#must_tail "); + break; + } + switch (ce->inlining) { case ProcInlining_inline: str = gb_string_appendc(str, "#force_inline "); diff --git a/src/checker.cpp b/src/checker.cpp index ab3ba50dd..453f3e241 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -3990,6 +3990,12 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { } ac->no_sanitize_memory = true; return true; + } else if (name == "no_sanitize_thread") { + if (value != nullptr) { + error(value, "'%.*s' expects no parameter", LIT(name)); + } + ac->no_sanitize_thread = true; + return true; } return false; } diff --git a/src/checker.hpp b/src/checker.hpp index bda7b2746..f9c279a51 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -141,6 +141,7 @@ struct AttributeContext { bool instrumentation_exit : 1; bool no_sanitize_address : 1; bool no_sanitize_memory : 1; + bool no_sanitize_thread : 1; bool rodata : 1; bool ignore_duplicates : 1; u32 optimization_mode; // ProcedureOptimizationMode diff --git a/src/entity.cpp b/src/entity.cpp index 2b21fdcac..55aca8069 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -268,6 +268,7 @@ struct Entity { bool is_anonymous : 1; bool no_sanitize_address : 1; bool no_sanitize_memory : 1; + bool no_sanitize_thread : 1; bool is_objc_impl_or_import : 1; bool is_objc_class_method : 1; } Procedure; diff --git a/src/linker.cpp b/src/linker.cpp index c2a3ee928..c68417994 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -737,7 +737,21 @@ try_cross_linking:; } if (build_context.build_mode == BuildMode_StaticLibrary) { - compiler_error("TODO(bill): -build-mode:static on non-windows targets"); + TIME_SECTION("Static Library Creation"); + + gbString ar_command = gb_string_make(heap_allocator(), ""); + defer (gb_string_free(ar_command)); + + ar_command = gb_string_appendc(ar_command, "ar rcs "); + ar_command = gb_string_append_fmt(ar_command, "\"%.*s\" ", LIT(output_filename)); + ar_command = gb_string_appendc(ar_command, object_files); + + result = system_exec_command_line_app("ar", ar_command); + if (result) { + return result; + } + + return result; } // NOTE(dweiler): We use clang as a frontend for the linker as there are diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 1cde65640..0bf3d1125 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2111,7 +2111,7 @@ gb_internal void lb_create_startup_runtime_generate_body(lbModule *m, lbProcedur for (Entity *e : info->init_procedures) { lbValue value = lb_find_procedure_value_from_entity(m, e); - lb_emit_call(p, value, {}, ProcInlining_none); + lb_emit_call(p, value, {}, ProcInlining_none, ProcTailing_none); } @@ -2157,7 +2157,7 @@ gb_internal lbProcedure *lb_create_cleanup_runtime(lbModule *main_module) { // C for (Entity *e : info->fini_procedures) { lbValue value = lb_find_procedure_value_from_entity(main_module, e); - lb_emit_call(p, value, {}, ProcInlining_none); + lb_emit_call(p, value, {}, ProcInlining_none, ProcTailing_none); } lb_end_procedure_body(p); @@ -2850,7 +2850,7 @@ gb_internal lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *star } lbValue startup_runtime_value = {startup_runtime->value, startup_runtime->type}; - lb_emit_call(p, startup_runtime_value, {}, ProcInlining_none); + lb_emit_call(p, startup_runtime_value, {}, ProcInlining_none, ProcTailing_none); if (build_context.command_kind == Command_test) { Type *t_Internal_Test = find_type_in_pkg(m->info, str_lit("testing"), str_lit("Internal_Test")); @@ -2917,16 +2917,16 @@ gb_internal lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *star auto exit_args = array_make<lbValue>(temporary_allocator(), 1); exit_args[0] = lb_emit_select(p, result, lb_const_int(m, t_int, 0), lb_const_int(m, t_int, 1)); - lb_emit_call(p, exit_runner, exit_args, ProcInlining_none); + lb_emit_call(p, exit_runner, exit_args, ProcInlining_none, ProcTailing_none); } else { if (m->info->entry_point != nullptr) { lbValue entry_point = lb_find_procedure_value_from_entity(m, m->info->entry_point); - lb_emit_call(p, entry_point, {}, ProcInlining_no_inline); + lb_emit_call(p, entry_point, {}, ProcInlining_no_inline, ProcTailing_none); } if (call_cleanup) { lbValue cleanup_runtime_value = {cleanup_runtime->value, cleanup_runtime->type}; - lb_emit_call(p, cleanup_runtime_value, {}, ProcInlining_none); + lb_emit_call(p, cleanup_runtime_value, {}, ProcInlining_none, ProcTailing_none); } if (is_dll_main) { diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index da5d91f2e..e10471527 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -345,6 +345,7 @@ struct lbProcedure { Ast * body; u64 tags; ProcInlining inlining; + ProcTailing tailing; bool is_foreign; bool is_export; bool is_entry_point; @@ -484,7 +485,7 @@ gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlo gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlock *block, Ast *node); gb_internal lbValue lb_emit_transmute(lbProcedure *p, lbValue value, Type *t); gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left, lbValue right); -gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array<lbValue> const &args, ProcInlining inlining = ProcInlining_none); +gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array<lbValue> const &args, ProcInlining inlining = ProcInlining_none, ProcTailing tailing = ProcTailing_none); gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t); gb_internal lbValue lb_emit_comp_against_nil(lbProcedure *p, TokenKind op_kind, lbValue x); @@ -670,6 +671,7 @@ enum lbCallingConventionKind : unsigned { lbCallingConvention_PreserveAll = 15, lbCallingConvention_Swift = 16, lbCallingConvention_CXX_FAST_TLS = 17, + lbCallingConvention_PreserveNone = 21, lbCallingConvention_FirstTargetCC = 64, lbCallingConvention_X86_StdCall = 64, lbCallingConvention_X86_FastCall = 65, @@ -723,6 +725,10 @@ lbCallingConventionKind const lb_calling_convention_map[ProcCC_MAX] = { lbCallingConvention_Win64, // ProcCC_Win64, lbCallingConvention_X86_64_SysV, // ProcCC_SysV, + lbCallingConvention_PreserveNone, // ProcCC_PreserveNone, + lbCallingConvention_PreserveMost, // ProcCC_PreserveMost, + lbCallingConvention_PreserveAll, // ProcCC_PreserveAll, + }; enum : LLVMDWARFTypeEncoding { diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 216d600da..640a43111 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -117,6 +117,7 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i p->type_expr = decl->type_expr; p->body = pl->body; p->inlining = pl->inlining; + p->tailing = pl->tailing; p->is_foreign = entity->Procedure.is_foreign; p->is_export = entity->Procedure.is_export; p->is_entry_point = false; @@ -152,6 +153,10 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i lb_ensure_abi_function_type(m, p); lb_add_function_type_attributes(p->value, p->abi_function_type, p->abi_function_type->calling_convention); + if (build_context.disable_unwind) { + lb_add_attribute_to_proc(m, p->value, "nounwind"); + } + if (pt->Proc.diverging) { lb_add_attribute_to_proc(m, p->value, "noreturn"); } @@ -346,7 +351,7 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i if (build_context.sanitizer_flags & SanitizerFlag_Memory && !entity->Procedure.no_sanitize_memory) { lb_add_attribute_to_proc(m, p->value, "sanitize_memory"); } - if (build_context.sanitizer_flags & SanitizerFlag_Thread) { + if (build_context.sanitizer_flags & SanitizerFlag_Thread && !entity->Procedure.no_sanitize_thread) { lb_add_attribute_to_proc(m, p->value, "sanitize_thread"); } } @@ -387,6 +392,7 @@ gb_internal lbProcedure *lb_create_dummy_procedure(lbModule *m, String link_name p->body = nullptr; p->tags = 0; p->inlining = ProcInlining_none; + p->tailing = ProcTailing_none; p->is_foreign = false; p->is_export = false; p->is_entry_point = false; @@ -855,7 +861,7 @@ gb_internal Array<lbValue> lb_value_to_array(lbProcedure *p, gbAllocator const & -gb_internal lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue return_ptr, Array<lbValue> const &processed_args, Type *abi_rt, lbAddr context_ptr, ProcInlining inlining) { +gb_internal lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue return_ptr, Array<lbValue> const &processed_args, Type *abi_rt, lbAddr context_ptr, ProcInlining inlining, ProcTailing tailing) { GB_ASSERT(p->module->ctx == LLVMGetTypeContext(LLVMTypeOf(value.value))); unsigned arg_count = cast(unsigned)processed_args.count; @@ -972,6 +978,15 @@ gb_internal lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue break; } + switch (tailing) { + case ProcTailing_none: + break; + case ProcTailing_must_tail: + LLVMSetTailCall(ret, true); + LLVMSetTailCallKind(ret, LLVMTailCallKindMustTail); + break; + } + lbValue res = {}; res.value = ret; res.type = abi_rt; @@ -1045,7 +1060,7 @@ gb_internal lbValue lb_emit_conjugate(lbProcedure *p, lbValue val, Type *type) { return lb_emit_load(p, res); } -gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array<lbValue> const &args, ProcInlining inlining) { +gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array<lbValue> const &args, ProcInlining inlining, ProcTailing tailing) { lbModule *m = p->module; Type *pt = base_type(value.type); @@ -1168,10 +1183,10 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array<lbValue> c if (return_by_pointer) { lbValue return_ptr = lb_add_local_generated(p, rt, true).addr; - lb_emit_call_internal(p, value, return_ptr, processed_args, nullptr, context_ptr, inlining); + lb_emit_call_internal(p, value, return_ptr, processed_args, nullptr, context_ptr, inlining, tailing); result = lb_emit_load(p, return_ptr); } else if (rt != nullptr) { - result = lb_emit_call_internal(p, value, {}, processed_args, rt, context_ptr, inlining); + result = lb_emit_call_internal(p, value, {}, processed_args, rt, context_ptr, inlining, tailing); if (ft->ret.cast_type) { result.value = OdinLLVMBuildTransmute(p, result.value, ft->ret.cast_type); } @@ -1184,7 +1199,7 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array<lbValue> c result = lb_emit_conv(p, result, rt); } } else { - lb_emit_call_internal(p, value, {}, processed_args, nullptr, context_ptr, inlining); + lb_emit_call_internal(p, value, {}, processed_args, nullptr, context_ptr, inlining, tailing); } if (original_rt != rt) { @@ -4402,6 +4417,25 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { return lb_handle_objc_auto_send(p, expr, slice(call_args, 0, call_args.count)); } - return lb_emit_call(p, value, call_args, ce->inlining); + + ProcInlining inlining = ce->inlining; + ProcTailing tailing = ce->tailing; + + if (tailing == ProcTailing_none && + proc_entity && proc_entity->kind == Entity_Procedure && + proc_entity->decl_info && + proc_entity->decl_info->proc_lit) { + ast_node(pl, ProcLit, proc_entity->decl_info->proc_lit); + + if (pl->inlining != ProcInlining_none) { + inlining = pl->inlining; + } + + if (pl->tailing != ProcTailing_none) { + tailing = pl->tailing; + } + } + + return lb_emit_call(p, value, call_args, inlining, tailing); } diff --git a/src/main.cpp b/src/main.cpp index 9a5df8aea..753f58986 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -363,6 +363,8 @@ enum BuildFlagKind { BuildFlag_RelocMode, BuildFlag_DisableRedZone, + BuildFlag_DisableUnwind, + BuildFlag_DisallowDo, BuildFlag_DefaultToNilAllocator, BuildFlag_DefaultToPanicAllocator, @@ -592,6 +594,8 @@ gb_internal bool parse_build_flags(Array<String> args) { add_flag(&build_flags, BuildFlag_RelocMode, str_lit("reloc-mode"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_DisableRedZone, str_lit("disable-red-zone"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_DisableUnwind, str_lit("disable-unwind"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_DisallowDo, str_lit("disallow-do"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_DefaultToNilAllocator, str_lit("default-to-nil-allocator"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_DefaultToPanicAllocator, str_lit("default-to-panic-allocator"),BuildFlagParam_None, Command__does_check); @@ -1424,6 +1428,10 @@ gb_internal bool parse_build_flags(Array<String> args) { case BuildFlag_DisableRedZone: build_context.disable_red_zone = true; break; + case BuildFlag_DisableUnwind: + build_context.disable_unwind = true; + break; + case BuildFlag_DisallowDo: build_context.disallow_do = true; break; @@ -2971,6 +2979,10 @@ gb_internal int print_show_help(String const arg0, String command, String option if (check) { if (print_flag("-target:<string>")) { print_usage_line(2, "Sets the target for the executable to be built in."); + print_usage_line(2, "Examples:"); + print_usage_line(3, "-target:linux_amd64"); + print_usage_line(3, "-target:windows_amd64"); + print_usage_line(3, "-target:\"?\" for a list"); } if (print_flag("-terse-errors")) { diff --git a/src/parser.cpp b/src/parser.cpp index 823021e04..fc55dae97 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2176,7 +2176,7 @@ gb_internal bool ast_on_same_line(Token const &x, Ast *yp) { return x.pos.line == y.pos.line; } -gb_internal Ast *parse_force_inlining_operand(AstFile *f, Token token) { +gb_internal Ast *parse_inlining_or_tailing_operand(AstFile *f, Token token) { Ast *expr = parse_unary_expr(f, false); Ast *e = strip_or_return_expr(expr); if (e == nullptr) { @@ -2187,11 +2187,14 @@ gb_internal Ast *parse_force_inlining_operand(AstFile *f, Token token) { return ast_bad_expr(f, token, f->curr_token); } ProcInlining pi = ProcInlining_none; + ProcTailing pt = ProcTailing_none; if (token.kind == Token_Ident) { if (token.string == "force_inline") { pi = ProcInlining_inline; } else if (token.string == "force_no_inline") { pi = ProcInlining_no_inline; + } else if (token.string == "must_tail") { + pt = ProcTailing_must_tail; } } @@ -2211,6 +2214,15 @@ gb_internal Ast *parse_force_inlining_operand(AstFile *f, Token token) { } } + if (pt != ProcTailing_none) { + if (e->kind == Ast_ProcLit) { + syntax_error(expr, "'#must_call' can only be applied to a procedure call, not the procedure literal"); + e->ProcLit.tailing = pt; + } else if (e->kind == Ast_CallExpr) { + e->CallExpr.tailing = pt; + } + } + return expr; } @@ -2507,8 +2519,9 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { syntax_error(tag, "#relative types have now been removed in favour of \"core:relative\""); return ast_relative_type(f, tag, type); } else if (name.string == "force_inline" || - name.string == "force_no_inline") { - return parse_force_inlining_operand(f, name); + name.string == "force_no_inline" || + name.string == "must_tail") { + return parse_inlining_or_tailing_operand(f, name); } return ast_basic_directive(f, token, name); } @@ -4008,6 +4021,10 @@ gb_internal ProcCallingConvention string_to_calling_convention(String const &s) if (s == "win64") return ProcCC_Win64; if (s == "sysv") return ProcCC_SysV; + if (s == "preserve/none") return ProcCC_PreserveNone; + if (s == "preserve/most") return ProcCC_PreserveMost; + if (s == "preserve/all") return ProcCC_PreserveAll; + if (s == "system") { if (build_context.metrics.os == TargetOs_windows) { return ProcCC_StdCall; @@ -5399,8 +5416,9 @@ gb_internal Ast *parse_stmt(AstFile *f) { expect_semicolon(f); return stmt; } else if (name.string == "force_inline" || - name.string == "force_no_inline") { - Ast *expr = parse_force_inlining_operand(f, name); + name.string == "force_no_inline" || + name.string == "must_tail") { + Ast *expr = parse_inlining_or_tailing_operand(f, name); Ast *stmt = ast_expr_stmt(f, expr); expect_semicolon(f); return stmt; diff --git a/src/parser.hpp b/src/parser.hpp index 71b61d95f..011330438 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -263,12 +263,17 @@ struct ForeignFileWorkerData { -enum ProcInlining { - ProcInlining_none = 0, - ProcInlining_inline = 1, +enum ProcInlining : u8 { + ProcInlining_none = 0, + ProcInlining_inline = 1, ProcInlining_no_inline = 2, }; +enum ProcTailing : u8 { + ProcTailing_none = 0, + ProcTailing_must_tail = 1, +}; + enum ProcTag { ProcTag_bounds_check = 1<<0, ProcTag_no_bounds_check = 1<<1, @@ -296,6 +301,9 @@ enum ProcCallingConvention : i32 { ProcCC_Win64 = 9, ProcCC_SysV = 10, + ProcCC_PreserveNone = 11, + ProcCC_PreserveMost = 12, + ProcCC_PreserveAll = 13, ProcCC_MAX, @@ -315,6 +323,9 @@ gb_global char const *proc_calling_convention_strings[ProcCC_MAX] = { "inlineasm", "win64", "sysv", + "preserve/none", + "preserve/most", + "preserve/all", }; gb_internal ProcCallingConvention default_calling_convention(void) { @@ -441,6 +452,7 @@ struct AstSplitArgs { Ast *body; \ u64 tags; \ ProcInlining inlining; \ + ProcTailing tailing; \ Token where_token; \ Slice<Ast *> where_clauses; \ DeclInfo *decl; \ @@ -486,6 +498,7 @@ AST_KIND(_ExprBegin, "", bool) \ Token close; \ Token ellipsis; \ ProcInlining inlining; \ + ProcTailing tailing; \ bool optional_ok_one; \ bool was_selector; \ AstSplitArgs *split_args; \ diff --git a/src/types.cpp b/src/types.cpp index 18e3b56ac..911cd4448 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -5210,40 +5210,12 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha case Type_Proc: str = gb_string_appendc(str, "proc"); - switch (type->Proc.calling_convention) { - case ProcCC_Odin: - if (default_calling_convention() != ProcCC_Odin) { - str = gb_string_appendc(str, " \"odin\" "); - } - break; - case ProcCC_Contextless: - if (default_calling_convention() != ProcCC_Contextless) { - str = gb_string_appendc(str, " \"contextless\" "); - } - break; - case ProcCC_CDecl: - str = gb_string_appendc(str, " \"c\" "); - break; - case ProcCC_StdCall: - str = gb_string_appendc(str, " \"std\" "); - break; - case ProcCC_FastCall: - str = gb_string_appendc(str, " \"fastcall\" "); - break; - break; - case ProcCC_None: - str = gb_string_appendc(str, " \"none\" "); - break; - case ProcCC_Naked: - str = gb_string_appendc(str, " \"naked\" "); - break; - // case ProcCC_VectorCall: - // str = gb_string_appendc(str, " \"vectorcall\" "); - // break; - // case ProcCC_ClrCall: - // str = gb_string_appendc(str, " \"clrcall\" "); - // break; + if (type->Proc.calling_convention != default_calling_convention()) { + str = gb_string_appendc(str, " \""); + str = gb_string_appendc(str, proc_calling_convention_strings[type->Proc.calling_convention]); + str = gb_string_appendc(str, "\" "); } + str = gb_string_appendc(str, "("); if (type->Proc.params) { str = write_type_to_string(str, type->Proc.params, shorthand, allow_polymorphic); diff --git a/tests/core/container/test_core_rbtree.odin b/tests/core/container/test_core_rbtree.odin index d220b7ed6..78d710b7f 100644 --- a/tests/core/container/test_core_rbtree.odin +++ b/tests/core/container/test_core_rbtree.odin @@ -4,22 +4,15 @@ import rb "core:container/rbtree" import "core:math/rand" import "core:testing" import "base:intrinsics" -import "core:mem" import "core:slice" import "core:log" test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - track.bad_free_callback = mem.tracking_allocator_bad_free_callback_add_to_array - defer mem.tracking_allocator_destroy(&track) - context.allocator = mem.tracking_allocator(&track) - log.infof("Testing Red-Black Tree($Key=%v,$Value=%v) using random seed %v.", type_info_of(Key), type_info_of(Value), t.seed) tree: rb.Tree(Key, Value) rb.init(&tree) - testing.expect(t, rb.len(&tree) == 0, "empty: len should be 0") + testing.expect(t, rb.len(tree) == 0, "empty: len should be 0") testing.expect(t, rb.first(&tree) == nil, "empty: first should be nil") testing.expect(t, rb.last(&tree) == nil, "empty: last should be nil") iter := rb.iterator(&tree, .Forward) @@ -48,7 +41,7 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { } entry_count := len(inserted_map) - testing.expect(t, rb.len(&tree) == entry_count, "insert: len after") + testing.expect(t, rb.len(tree) == entry_count, "insert: len after") validate_rbtree(t, &tree) first := rb.first(&tree) @@ -58,8 +51,8 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { // Ensure that all entries can be found. for k, v in inserted_map { - testing.expect(t, v == rb.find(&tree, k), "Find(): Node") - testing.expect(t, k == v.key, "Find(): Node key") + testing.expect(t, v == rb.find(tree, k), "Find(): Node") + testing.expect(t, k == v.key, "Find(): Node key") } // Test the forward/backward iterators. @@ -97,17 +90,17 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { (^int)(user_data)^ -= 1 } for k, i in inserted_keys { - node := rb.find(&tree, k) + node := rb.find(tree, k) testing.expect(t, node != nil, "remove: find (pre)") ok := rb.remove(&tree, k) testing.expect(t, ok, "remove: succeeds") - testing.expect(t, entry_count - (i + 1) == rb.len(&tree), "remove: len (post)") + testing.expect(t, entry_count - (i + 1) == rb.len(tree), "remove: len (post)") validate_rbtree(t, &tree) - testing.expect(t, nil == rb.find(&tree, k), "remove: find (post") + testing.expect(t, nil == rb.find(tree, k), "remove: find (post") } - testing.expect(t, rb.len(&tree) == 0, "remove: len should be 0") + testing.expect(t, rb.len(tree) == 0, "remove: len should be 0") testing.expectf(t, callback_count == 0, "remove: on_remove should've been called %v times, it was %v", entry_count, callback_count) testing.expect(t, rb.first(&tree) == nil, "remove: first should be nil") testing.expect(t, rb.last(&tree) == nil, "remove: last should be nil") @@ -129,28 +122,25 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { ok = rb.iterator_remove(&iter) testing.expect(t, !ok, "iterator/remove: redundant removes should fail") - testing.expect(t, rb.find(&tree, k) == nil, "iterator/remove: node should be gone") + testing.expect(t, rb.find(tree, k) == nil, "iterator/remove: node should be gone") testing.expect(t, rb.iterator_get(&iter) == nil, "iterator/remove: get should return nil") // Ensure that iterator_next still works. node, ok = rb.iterator_next(&iter) - testing.expect(t, ok == (rb.len(&tree) > 0), "iterator/remove: next should return false") + testing.expect(t, ok == (rb.len(tree) > 0), "iterator/remove: next should return false") testing.expect(t, node == rb.first(&tree), "iterator/remove: next should return first") validate_rbtree(t, &tree) } - testing.expect(t, rb.len(&tree) == entry_count - 1, "iterator/remove: len should drop by 1") + testing.expect(t, rb.len(tree) == entry_count - 1, "iterator/remove: len should drop by 1") rb.destroy(&tree) - testing.expect(t, rb.len(&tree) == 0, "destroy: len should be 0") + testing.expect(t, rb.len(tree) == 0, "destroy: len should be 0") testing.expectf(t, callback_count == 0, "remove: on_remove should've been called %v times, it was %v", entry_count, callback_count) // print_tree_node(tree._root) delete(inserted_map) delete(inserted_keys) - testing.expectf(t, len(track.allocation_map) == 0, "Expected 0 leaks, have %v", len(track.allocation_map)) - testing.expectf(t, len(track.bad_free_array) == 0, "Expected 0 bad frees, have %v", len(track.bad_free_array)) - return } @(test) diff --git a/tests/core/encoding/base64/base64.odin b/tests/core/encoding/base64/base64.odin index ed1bee8af..93b3afb59 100644 --- a/tests/core/encoding/base64/base64.odin +++ b/tests/core/encoding/base64/base64.odin @@ -50,4 +50,19 @@ test_roundtrip :: proc(t: ^testing.T) { for v, i in decoded { testing.expect_value(t, v, values[i]) } -}
\ No newline at end of file +} + +@(test) +test_base64url :: proc(t: ^testing.T) { + plain := ">>>" + url := "Pj4-" + + encoded := base64.encode(transmute([]byte)plain, base64.ENC_URL_TABLE) + defer delete(encoded) + testing.expect_value(t, encoded, url) + + decoded := string(base64.decode(url, base64.DEC_URL_TABLE)) + defer delete(decoded) + testing.expect_value(t, decoded, plain) + +} diff --git a/tests/core/encoding/xml/test_core_xml.odin b/tests/core/encoding/xml/test_core_xml.odin index 23d583e98..9f233e00e 100644 --- a/tests/core/encoding/xml/test_core_xml.odin +++ b/tests/core/encoding/xml/test_core_xml.odin @@ -142,7 +142,7 @@ xml_test_entities_unbox_decode :: proc(t: ^testing.T) { }, expected_doctype = "html", }, - crc32 = 0x3c0973e2, + crc32 = 0x6e45a697, }) } diff --git a/tests/core/nbio/fs.odin b/tests/core/nbio/fs.odin new file mode 100644 index 000000000..6e079f96e --- /dev/null +++ b/tests/core/nbio/fs.odin @@ -0,0 +1,100 @@ +package tests_nbio + +import "core:nbio" +import "core:testing" +import "core:time" +import os "core:os/os2" + +@(test) +close_invalid_handle :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + nbio.close(max(nbio.Handle)) + + ev(t, nbio.run(), nil) + } +} + +@(test) +write_read_close :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + @static content := [20]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} + @static result: [20]byte + + FILENAME :: "test_write_read_close" + + nbio.open_poly(FILENAME, t, on_open, mode={.Read, .Write, .Create, .Trunc}) + + on_open :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.open.err, nil) + + nbio.write_poly(op.open.handle, 0, content[:], t, on_write) + } + + on_write :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.write.err, nil) + ev(t, op.write.written, len(content)) + + nbio.read_poly(op.write.handle, 0, result[:], t, on_read, all=true) + } + + on_read :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.read.err, nil) + ev(t, op.read.read, len(result)) + ev(t, result, content) + + nbio.close_poly(op.read.handle, t, on_close) + } + + on_close :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.close.err, nil) + os.remove(FILENAME) + } + + ev(t, nbio.run(), nil) + } +} + +@(test) +read_empty_file :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + FILENAME :: "test_read_empty_file" + + handle, err := nbio.open_sync(FILENAME, mode={.Read, .Write, .Create, .Trunc}) + ev(t, err, nil) + + buf: [128]byte + nbio.read_poly(handle, 0, buf[:], t, proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.read.err, nbio.FS_Error.EOF) + ev(t, op.read.read, 0) + + nbio.close_poly(op.read.handle, t, proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.close.err, nil) + os.remove(FILENAME) + }) + }) + + ev(t, nbio.run(), nil) + } +} + +@(test) +read_entire_file :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + nbio.read_entire_file(#file, t, on_read) + + on_read :: proc(t: rawptr, data: []byte, err: nbio.Read_Entire_File_Error) { + t := (^testing.T)(t) + ev(t, err.value, nil) + ev(t, string(data), #load(#file, string)) + delete(data) + } + } +} diff --git a/tests/core/nbio/nbio.odin b/tests/core/nbio/nbio.odin new file mode 100644 index 000000000..2f454f55b --- /dev/null +++ b/tests/core/nbio/nbio.odin @@ -0,0 +1,258 @@ +package tests_nbio + +import "core:log" +import "core:nbio" +import "core:testing" +import "core:thread" +import "core:time" +import os "core:os/os2" + +ev :: testing.expect_value +e :: testing.expect + +@(deferred_in=event_loop_guard_exit) +event_loop_guard :: proc(t: ^testing.T) -> bool { + err := nbio.acquire_thread_event_loop() + if err == .Unsupported || !nbio.FULLY_SUPPORTED { + log.warn("nbio unsupported, skipping") + return false + } + + ev(t, err, nil) + return true +} + +event_loop_guard_exit :: proc(t: ^testing.T) { + ev(t, nbio.run(), nil) // Could have some things to clean up from a `defer` in the test. + nbio.release_thread_event_loop() +} + +// Tests that all poly variants are correctly passing through arguments, and that +// all procs eventually get their callback called. +// +// This is important because the poly procs are only checked when they are called, +// So this will also catch any typos in their implementations. +@(test) +all_poly_work :: proc(tt: ^testing.T) { + if event_loop_guard(tt) { + testing.set_fail_timeout(tt, time.Minute) + + @static t: ^testing.T + t = tt + + @static n: int + n = 0 + NUM_TESTS :: 39 + + UDP_SOCKET :: max(nbio.UDP_Socket) + TCP_SOCKET :: max(nbio.TCP_Socket) + + tmp, terr := os.create_temp_file("", "tests_nbio_poly*", {.Non_Blocking}) + ev(t, terr, nil) + defer os.close(tmp) + + HANDLE, aerr := nbio.associate_handle(os.fd(tmp)) + ev(t, aerr, nil) + + _buf: [1]byte + buf := _buf[:] + + one :: proc(op: ^nbio.Operation, one: int) { + n += 1 + ev(t, one, 1) + } + + two :: proc(op: ^nbio.Operation, one: int, two: int) { + n += 1 + ev(t, one, 1) + ev(t, two, 2) + } + + three :: proc(op: ^nbio.Operation, one: int, two: int, three: int) { + n += 1 + ev(t, one, 1) + ev(t, two, 2) + ev(t, three, 3) + } + + nbio.accept_poly(TCP_SOCKET, 1, one) + nbio.accept_poly2(TCP_SOCKET, 1, 2, two) + nbio.accept_poly3(TCP_SOCKET, 1, 2, 3, three) + + nbio.close_poly(max(nbio.Handle), 1, one) + nbio.close_poly2(max(nbio.Handle), 1, 2, two) + nbio.close_poly3(max(nbio.Handle), 1, 2, 3, three) + + nbio.dial_poly({nbio.IP4_Address{127, 0, 0, 1}, 0}, 1, one) + nbio.dial_poly2({nbio.IP4_Address{127, 0, 0, 1}, 0}, 1, 2, two) + nbio.dial_poly3({nbio.IP4_Address{127, 0, 0, 1}, 0}, 1, 2, 3, three) + + nbio.recv_poly(TCP_SOCKET, {buf}, 1, one) + nbio.recv_poly2(TCP_SOCKET, {buf}, 1, 2, two) + nbio.recv_poly3(TCP_SOCKET, {buf}, 1, 2, 3, three) + + nbio.send_poly(TCP_SOCKET, {buf}, 1, one) + nbio.send_poly2(TCP_SOCKET, {buf}, 1, 2, two) + nbio.send_poly3(TCP_SOCKET, {buf}, 1, 2, 3, three) + + nbio.sendfile_poly(TCP_SOCKET, HANDLE, 1, one) + nbio.sendfile_poly2(TCP_SOCKET, HANDLE, 1, 2, two) + nbio.sendfile_poly3(TCP_SOCKET, HANDLE, 1, 2, 3, three) + + nbio.read_poly(HANDLE, 0, buf, 1, one) + nbio.read_poly2(HANDLE, 0, buf, 1, 2, two) + nbio.read_poly3(HANDLE, 0, buf, 1, 2, 3, three) + + nbio.write_poly(HANDLE, 0, buf, 1, one) + nbio.write_poly2(HANDLE, 0, buf, 1, 2, two) + nbio.write_poly3(HANDLE, 0, buf, 1, 2, 3, three) + + nbio.next_tick_poly(1, one) + nbio.next_tick_poly2(1, 2, two) + nbio.next_tick_poly3(1, 2, 3, three) + + nbio.timeout_poly(1, 1, one) + nbio.timeout_poly2(1, 1, 2, two) + nbio.timeout_poly3(1, 1, 2, 3, three) + + nbio.poll_poly(TCP_SOCKET, .Receive, 1, one) + nbio.poll_poly2(TCP_SOCKET, .Receive, 1, 2, two) + nbio.poll_poly3(TCP_SOCKET, .Receive, 1, 2, 3, three) + + nbio.open_poly("", 1, one) + nbio.open_poly2("", 1, 2, two) + nbio.open_poly3("", 1, 2, 3, three) + + nbio.stat_poly(HANDLE, 1, one) + nbio.stat_poly2(HANDLE, 1, 2, two) + nbio.stat_poly3(HANDLE, 1, 2, 3, three) + + ev(t, n, 0) // Test that no callbacks are ran before the loop is ticked. + ev(t, nbio.run(), nil) + ev(t, n, NUM_TESTS) // Test that all callbacks have ran. + } +} + +@(test) +two_ops_at_the_same_time :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + server, err := nbio.create_udp_socket(.IP4) + ev(t, err, nil) + defer nbio.close(server) + + berr := nbio.bind(server, {nbio.IP4_Loopback, 0}) + ev(t, berr, nil) + ep, eperr := nbio.bound_endpoint(server) + ev(t, eperr, nil) + + // Server. + { + nbio.poll_poly(server, .Receive, t, on_poll) + + on_poll :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.poll.result, nbio.Poll_Result.Ready) + } + + buf: [128]byte + nbio.recv_poly(server, {buf[:]}, t, on_recv) + + on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.recv.err, nil) + } + } + + // Client. + { + sock, cerr := nbio.create_udp_socket(.IP4) + ev(t, cerr, nil) + + // Make sure the server would block. + nbio.timeout_poly3(time.Millisecond*10, t, sock, ep.port, on_timeout) + + on_timeout :: proc(op: ^nbio.Operation, t: ^testing.T, sock: nbio.UDP_Socket, port: int) { + nbio.send_poly(sock, {transmute([]byte)string("Hiya")}, t, on_send, {nbio.IP4_Loopback, port}) + } + + on_send :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.send.err, nil) + ev(t, op.send.sent, 4) + + // Do another send after a bit, some backends don't trigger both ops when one was enough to + // use up the socket. + nbio.timeout_poly3(time.Millisecond*10, t, op.send.socket.(nbio.UDP_Socket), op.send.endpoint.port, on_timeout2) + } + + on_timeout2 :: proc(op: ^nbio.Operation, t: ^testing.T, sock: nbio.UDP_Socket, port: int) { + nbio.send_poly(sock, {transmute([]byte)string("Hiya")}, t, on_send2, {nbio.IP4_Loopback, port}) + } + + on_send2 :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.send.err, nil) + ev(t, op.send.sent, 4) + + nbio.close(op.send.socket.(nbio.UDP_Socket)) + } + } + + ev(t, nbio.run(), nil) + } +} + +@(test) +timeout :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + start := time.now() + + nbio.timeout_poly2(time.Millisecond*20, t, start, on_timeout) + + on_timeout :: proc(op: ^nbio.Operation, t: ^testing.T, start: time.Time) { + since := time.since(start) + log.infof("timeout ran after: %v", since) + testing.expect(t, since >= time.Millisecond*19) // A ms grace, for some reason it is sometimes ran after 19.8ms. + if since < 20 { + log.warnf("timeout ran after: %v", since) + } + } + + ev(t, nbio.run(), nil) + } +} + +@(test) +wake_up :: proc(t: ^testing.T) { + testing.set_fail_timeout(t, time.Minute) + if event_loop_guard(t) { + for _ in 0..<2 { + sock, _ := open_next_available_local_port(t) + + // Add an accept, with nobody dialling this should block the event loop forever. + accept := nbio.accept(sock, proc(op: ^nbio.Operation) { + log.error("shouldn't be called") + }) + + // Make sure the accept is in progress. + ev(t, nbio.tick(timeout=0), nil) + + hit: bool + thr := thread.create_and_start_with_poly_data2(nbio.current_thread_event_loop(), &hit, proc(l: ^nbio.Event_Loop, hit: ^bool) { + hit^ = true + nbio.wake_up(l) + }, context) + defer thread.destroy(thr) + + // Should block forever until the thread calling wake_up will make it return. + ev(t, nbio.tick(), nil) + e(t, hit) + + nbio.remove(accept) + nbio.close(sock) + + ev(t, nbio.run(), nil) + ev(t, nbio.tick(timeout=0), nil) + } + } +} diff --git a/tests/core/nbio/net.odin b/tests/core/nbio/net.odin new file mode 100644 index 000000000..688ee0b45 --- /dev/null +++ b/tests/core/nbio/net.odin @@ -0,0 +1,400 @@ +package tests_nbio + +import "core:mem" +import "core:nbio" +import "core:net" +import "core:testing" +import "core:time" +import "core:log" + +open_next_available_local_port :: proc(t: ^testing.T, addr: net.Address = net.IP4_Loopback, loc := #caller_location) -> (sock: net.TCP_Socket, ep: net.Endpoint) { + err: net.Network_Error + sock, err = nbio.listen_tcp({addr, 0}) + if err != nil { + log.errorf("listen_tcp: %v", err, location=loc) + return + } + + ep, err = net.bound_endpoint(sock) + if err != nil { + log.errorf("bound_endpoint: %v", err, location=loc) + } + + return +} + +@(test) +client_and_server_send_recv :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + server, ep := open_next_available_local_port(t) + + CONTENT :: [20]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} + + State :: struct { + server: net.TCP_Socket, + server_client: net.TCP_Socket, + client: net.TCP_Socket, + recv_buf: [20]byte, + send_buf: [20]byte, + } + + state := State{ + server = server, + send_buf = CONTENT, + } + + close_ok :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.close.err, nil) + } + + // Server + { + nbio.accept_poly2(server, t, &state, on_accept) + + on_accept :: proc(op: ^nbio.Operation, t: ^testing.T, state: ^State) { + ev(t, op.accept.err, nil) + + state.server_client = op.accept.client + + log.debugf("accepted connection from: %v", op.accept.client_endpoint) + + nbio.recv_poly2(state.server_client, {state.recv_buf[:]}, t, state, on_recv) + } + + on_recv :: proc(op: ^nbio.Operation, t: ^testing.T, state: ^State) { + ev(t, op.recv.err, nil) + ev(t, op.recv.received, 20) + ev(t, state.recv_buf, CONTENT) + + nbio.close_poly(state.server_client, t, close_ok) + nbio.close_poly(state.server, t, close_ok) + } + + ev(t, nbio.tick(0), nil) + } + + // Client + { + nbio.dial_poly2(ep, t, &state, on_dial) + + on_dial :: proc(op: ^nbio.Operation, t: ^testing.T, state: ^State) { + ev(t, op.dial.err, nil) + + state.client = op.dial.socket + + nbio.send_poly2(state.client, {state.send_buf[:]}, t, state, on_send) + } + + on_send :: proc(op: ^nbio.Operation, t: ^testing.T, state: ^State) { + ev(t, op.send.err, nil) + ev(t, op.send.sent, 20) + + nbio.close_poly(state.client, t, close_ok) + } + } + + ev(t, nbio.run(), nil) + } +} + +@(test) +close_and_remove_accept :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + server, _ := open_next_available_local_port(t) + + accept := nbio.accept_poly(server, t, proc(_: ^nbio.Operation, t: ^testing.T) { + testing.fail_now(t) + }) + + ev(t, nbio.tick(0), nil) + + nbio.close_poly(server, t, proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.close.err, nil) + }) + + nbio.remove(accept) + ev(t, nbio.run(), nil) + } +} + +// Tests that when a client calls `close` on it's socket, `recv` returns with `0, nil` (connection closed). +@(test) +close_errors_recv :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + server, ep := open_next_available_local_port(t) + + // Server + { + nbio.accept_poly(server, t, on_accept) + + on_accept :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.accept.err, nil) + + bytes := make([]byte, 128, context.temp_allocator) + nbio.recv_poly(op.accept.client, {bytes}, t, on_recv) + } + + on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.recv.received, 0) + ev(t, op.recv.err, nil) + } + + ev(t, nbio.tick(0), nil) + } + + // Client + { + nbio.dial_poly(ep, t, on_dial) + + on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.dial.err, nil) + nbio.close_poly(op.dial.socket, t, on_close) + } + + on_close :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.close.err, nil) + } + } + + ev(t, nbio.run(), nil) + } +} + +@(test) +ipv6 :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + server, ep := open_next_available_local_port(t, net.IP6_Loopback) + + nbio.accept_poly(server, t, on_accept) + on_accept :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.accept.err, nil) + addr, is_ipv6 := op.accept.client_endpoint.address.(net.IP6_Address) + e(t, is_ipv6) + ev(t, addr, net.IP6_Loopback) + e(t, op.accept.client_endpoint.port != 0) + nbio.close(op.accept.client) + nbio.close(op.accept.socket) + } + + nbio.dial_poly(ep, t, on_dial) + on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.dial.err, nil) + nbio.close(op.dial.socket) + } + + ev(t, nbio.run(), nil) + } +} + +@(test) +accept_timeout :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + sock, _ := open_next_available_local_port(t) + + hit: bool + nbio.accept_poly2(sock, t, &hit, on_accept, timeout=time.Millisecond) + + on_accept :: proc(op: ^nbio.Operation, t: ^testing.T, hit: ^bool) { + hit^ = true + ev(t, op.accept.err, net.Accept_Error.Timeout) + nbio.close(op.accept.socket) + } + + ev(t, nbio.run(), nil) + + e(t, hit) + } +} + +@(test) +poll_timeout :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + sock, err := nbio.create_udp_socket(.IP4) + ev(t, err, nil) + berr := nbio.bind(sock, {nbio.IP4_Loopback, 0}) + ev(t, berr, nil) + + nbio.poll_poly(sock, .Receive, t, on_poll, time.Millisecond) + on_poll :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.poll.result, nbio.Poll_Result.Timeout) + } + + ev(t, nbio.run(), nil) + } +} + +/* +This test walks through the scenario where a user wants to `poll` in order to check if some other package (in this case `core:net`), +would be able to do an operation without blocking. + +It also tests whether a poll can be issues when it is already in a ready state. +And it tests big send/recv buffers being handled properly. +*/ +@(test) +poll :: proc(t: ^testing.T) { + if event_loop_guard(t) { +// testing.set_fail_timeout(t, time.Minute) + + can_recv: bool + + sock, ep := open_next_available_local_port(t) + + // Server + { + nbio.accept_poly2(sock, t, &can_recv, on_accept) + + on_accept :: proc(op: ^nbio.Operation, t: ^testing.T, can_recv: ^bool) { + ev(t, op.accept.err, nil) + + check_recv :: proc(op: ^nbio.Operation, t: ^testing.T, can_recv: ^bool, client: net.TCP_Socket) { + // Not ready to unblock the client yet, requeue for after 10ms. + if !can_recv^ { + nbio.timeout_poly3(time.Millisecond * 10, t, can_recv, client, check_recv) + return + } + + free_all(context.temp_allocator) + + // Connection was closed by client, close server. + if op.type == .Recv && op.recv.received == 0 && op.recv.err == nil { + nbio.close(client) + return + } + + if op.type == .Recv { + log.debugf("received %M this time", op.recv.received) + } + + // Receive some data to unblock the client, which should complete the poll it does, allowing it to send data again. + buf, mem_err := make([]byte, mem.Gigabyte, context.temp_allocator) + ev(t, mem_err, nil) + nbio.recv_poly3(client, {buf}, t, can_recv, client, check_recv) + } + nbio.timeout_poly3(time.Millisecond * 10, t, can_recv, op.accept.client, check_recv) + } + + ev(t, nbio.tick(0), nil) + } + + // Client + { + nbio.dial_poly2(ep, t, &can_recv, on_dial) + + on_dial :: proc(op: ^nbio.Operation, t: ^testing.T, can_recv: ^bool) { + ev(t, op.dial.err, nil) + + // Do a poll even though we know it's ready, so we can test that all implementations can handle that. + nbio.poll_poly2(op.dial.socket, .Send, t, can_recv, on_poll1) + } + + on_poll1 :: proc(op: ^nbio.Operation, t: ^testing.T, can_recv: ^bool) { + ev(t, op.poll.result, nil) + + // Send 4 GB of data, which in my experience causes a Would_Block error because we filled up the internal buffer. + buf, mem_err := make([]byte, mem.Gigabyte*4, context.temp_allocator) + ev(t, mem_err, nil) + + // Use `core:net` as example external code that doesn't care about the event loop. + net.set_blocking(op.poll.socket, false) + n, send_err := net.send(op.poll.socket, buf) + ev(t, send_err, net.TCP_Send_Error.Would_Block) + + log.debugf("blocking after %M", n) + + // Tell the server it can start issueing recv calls, so it unblocks us. + can_recv^ = true + + // Now poll again, when the server reads enough data it should complete, telling us we can send without blocking again. + nbio.poll_poly(op.poll.socket, .Send, t, on_poll2) + } + + on_poll2 :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.poll.result, nil) + + buf: [128]byte + bytes_written, send_err := net.send(op.poll.socket, buf[:]) + ev(t, bytes_written, 128) + ev(t, send_err, nil) + + nbio.close(op.poll.socket.(net.TCP_Socket)) + } + } + + ev(t, nbio.run(), nil) + nbio.close(sock) + ev(t, nbio.run(), nil) + } +} + +@(test) +sendfile :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + CONTENT :: #load(#file) + + sock, ep := open_next_available_local_port(t) + + // Server + { + nbio.accept_poly(sock, t, on_accept) + + on_accept :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.accept.err, nil) + e(t, op.accept.client != 0) + + log.debugf("connection from: %v", op.accept.client_endpoint) + nbio.open_poly3(#file, t, op.accept.socket, op.accept.client, on_open) + } + + on_open :: proc(op: ^nbio.Operation, t: ^testing.T, server, client: net.TCP_Socket) { + ev(t, op.open.err, nil) + + nbio.sendfile_poly2(client, op.open.handle, t, server, on_sendfile) + } + + on_sendfile :: proc(op: ^nbio.Operation, t: ^testing.T, server: net.TCP_Socket) { + ev(t, op.sendfile.err, nil) + ev(t, op.sendfile.sent, len(CONTENT)) + + nbio.close(op.sendfile.file) + nbio.close(op.sendfile.socket) + nbio.close(server) + } + } + + // Client + { + nbio.dial_poly(ep, t, on_dial) + + on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.dial.err, nil) + + buf := make([]byte, len(CONTENT), context.temp_allocator) + nbio.recv_poly(op.dial.socket, {buf}, t, on_recv, all=true) + } + + on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.recv.err, nil) + ev(t, op.recv.received, len(CONTENT)) + ev(t, string(op.recv.bufs[0]), string(CONTENT)) + + nbio.close(op.recv.socket.(net.TCP_Socket)) + } + } + + ev(t, nbio.run(), nil) + } +} diff --git a/tests/core/nbio/remove.odin b/tests/core/nbio/remove.odin new file mode 100644 index 000000000..063c2cf58 --- /dev/null +++ b/tests/core/nbio/remove.odin @@ -0,0 +1,247 @@ +package tests_nbio + +import "core:nbio" +import "core:net" +import "core:testing" +import "core:time" +import "core:log" + +// Removals are pretty complex. + +@(test) +immediate_remove_of_sendfile :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + sock, ep := open_next_available_local_port(t) + + // Server + { + nbio.accept_poly(sock, t, on_accept) + + on_accept :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.accept.err, nil) + e(t, op.accept.client != 0) + + log.debugf("connection from: %v", op.accept.client_endpoint) + nbio.open_poly3(#file, t, op.accept.socket, op.accept.client, on_open) + } + + on_open :: proc(op: ^nbio.Operation, t: ^testing.T, server, client: net.TCP_Socket) { + ev(t, op.open.err, nil) + e(t, op.open.handle != 0) + + sendfile_op := nbio.sendfile_poly2(client, op.open.handle, t, server, on_sendfile) + + // oh no changed my mind. + nbio.remove(sendfile_op) + + nbio.close(op.open.handle) + nbio.close(client) + nbio.close(server) + } + + on_sendfile :: proc(op: ^nbio.Operation, t: ^testing.T, server: net.TCP_Socket) { + log.error("on_sendfile shouldn't be called") + } + } + + // Client + { + nbio.dial_poly(ep, t, on_dial) + + on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.dial.err, nil) + + buf := make([]byte, 128, context.temp_allocator) + nbio.recv_poly(op.dial.socket, {buf}, t, on_recv) + } + + on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.recv.err, nil) + + nbio.close(op.recv.socket.(net.TCP_Socket)) + } + } + + ev(t, nbio.run(), nil) + } +} + +@(test) +immediate_remove_of_sendfile_without_stat :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + sock, ep := open_next_available_local_port(t) + + // Server + { + nbio.accept_poly(sock, t, on_accept) + + on_accept :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.accept.err, nil) + e(t, op.accept.client != 0) + + log.debugf("connection from: %v", op.accept.client_endpoint) + nbio.open_poly3(#file, t, op.accept.socket, op.accept.client, on_open) + } + + on_open :: proc(op: ^nbio.Operation, t: ^testing.T, server, client: net.TCP_Socket) { + ev(t, op.open.err, nil) + e(t, op.open.handle != 0) + + nbio.stat_poly3(op.open.handle, t, server, client, on_stat) + } + + on_stat :: proc(op: ^nbio.Operation, t: ^testing.T, server, client: net.TCP_Socket) { + ev(t, op.stat.err, nil) + + sendfile_op := nbio.sendfile_poly2(client, op.stat.handle, t, server, on_sendfile, nbytes=int(op.stat.size)) + + // oh no changed my mind. + nbio.remove(sendfile_op) + + nbio.timeout_poly3(time.Millisecond * 10, op.stat.handle, client, server, proc(op: ^nbio.Operation, p1: nbio.Handle, p2, p3: net.TCP_Socket){ + nbio.close(p1) + nbio.close(p2) + nbio.close(p3) + }) + } + + on_sendfile :: proc(op: ^nbio.Operation, t: ^testing.T, server: net.TCP_Socket) { + log.error("on_sendfile shouldn't be called") + } + } + + // Client + { + nbio.dial_poly(ep, t, on_dial) + + on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.dial.err, nil) + + buf := make([]byte, 128, context.temp_allocator) + nbio.recv_poly(op.dial.socket, {buf}, t, on_recv) + } + + on_recv :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.recv.err, nil) + + nbio.close(op.recv.socket.(net.TCP_Socket)) + } + } + + ev(t, nbio.run(), nil) + } +} + +// Open should free the temporary memory allocated for the path when removed. +// Can't really test that though, so should be checked manually that the internal callback is called but not the external. +@(test) +remove_open :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + open := nbio.open(#file, on_open) + nbio.remove(open) + + on_open :: proc(op: ^nbio.Operation) { + log.error("on_open shouldn't be called") + } + + ev(t, nbio.run(), nil) + } +} + +// Dial should close the socket when removed. +// Can't really test that though, so should be checked manually that the internal callback is called but not the external. +@(test) +remove_dial :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + sock, ep := open_next_available_local_port(t) + defer nbio.close(sock) + + dial := nbio.dial(ep, on_dial) + nbio.remove(dial) + + on_dial :: proc(op: ^nbio.Operation) { + log.error("on_dial shouldn't be called") + } + + ev(t, nbio.run(), nil) + } +} + +@(test) +remove_next_tick :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + nt := nbio.next_tick_poly(t, proc(op: ^nbio.Operation, t: ^testing.T) { + log.error("shouldn't be called") + }) + nbio.remove(nt) + + ev(t, nbio.run(), nil) + } +} + +@(test) +remove_timeout :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + hit: bool + timeout := nbio.timeout_poly(time.Second, &hit, proc(_: ^nbio.Operation, hit: ^bool) { + hit^ = true + }) + + nbio.remove(timeout) + + ev(t, nbio.run(), nil) + + e(t, !hit) + } +} + +@(test) +remove_multiple_poll :: proc(t: ^testing.T) { + if event_loop_guard(t) { + testing.set_fail_timeout(t, time.Minute) + + sock, ep := open_next_available_local_port(t) + defer nbio.close(sock) + + hit: bool + + first := nbio.poll(sock, .Receive, on_poll) + nbio.poll_poly2(sock, .Receive, t, &hit, on_poll2) + + on_poll :: proc(op: ^nbio.Operation) { + log.error("shouldn't be called") + } + + on_poll2 :: proc(op: ^nbio.Operation, t: ^testing.T, hit: ^bool) { + ev(t, op.poll.result, nbio.Poll_Result.Ready) + hit^ = true + } + + ev(t, nbio.tick(0), nil) + + nbio.remove(first) + + ev(t, nbio.tick(0), nil) + + nbio.dial_poly(ep, t, on_dial) + + on_dial :: proc(op: ^nbio.Operation, t: ^testing.T) { + ev(t, op.dial.err, nil) + } + + ev(t, nbio.run(), nil) + e(t, hit) + } +} diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index 9b3973a60..55fe6671d 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -10,8 +10,6 @@ A test suite for `core:net` */ -#+build !netbsd -#+build !openbsd #+feature dynamic-literals package test_core_net diff --git a/tests/core/normal.odin b/tests/core/normal.odin index e8b61fee8..d0889bf89 100644 --- a/tests/core/normal.odin +++ b/tests/core/normal.odin @@ -32,6 +32,7 @@ download_assets :: proc "contextless" () { @(require) import "math/noise" @(require) import "math/rand" @(require) import "mem" +@(require) import "nbio" @(require) import "net" @(require) import "odin" @(require) import "os" @@ -45,6 +46,7 @@ download_assets :: proc "contextless" () { @(require) import "sync" @(require) import "sync/chan" @(require) import "sys/posix" +@(require) import "sys/kqueue" @(require) import "sys/windows" @(require) import "text/i18n" @(require) import "text/match" diff --git a/tests/core/sys/kqueue/structs.odin b/tests/core/sys/kqueue/structs.odin new file mode 100644 index 000000000..edf1fdd1e --- /dev/null +++ b/tests/core/sys/kqueue/structs.odin @@ -0,0 +1,56 @@ +#+build darwin, freebsd, openbsd, netbsd +package tests_core_sys_kqueue + +import "core:strings" +import "core:testing" +import os "core:os/os2" + +@(test) +structs :: proc(t: ^testing.T) { + { + c_compiler := os.get_env("CC", context.temp_allocator) + if c_compiler == "" { + c_compiler = "clang" + } + + c_compilation, c_start_err := os.process_start({ + command = {c_compiler, #directory + "/structs/structs.c", "-o", #directory + "/structs/c_structs"}, + stdout = os.stdout, + stderr = os.stderr, + }) + testing.expect_value(t, c_start_err, nil) + + o_compilation, o_start_err := os.process_start({ + command = {ODIN_ROOT + "/odin", "build", #directory + "/structs", "-out:" + #directory + "/structs/odin_structs"}, + stdout = os.stdout, + stderr = os.stderr, + }) + testing.expect_value(t, o_start_err, nil) + + c_status, c_err := os.process_wait(c_compilation) + testing.expect_value(t, c_err, nil) + testing.expect_value(t, c_status.exit_code, 0) + + o_status, o_err := os.process_wait(o_compilation) + testing.expect_value(t, o_err, nil) + testing.expect_value(t, o_status.exit_code, 0) + } + + c_status, c_stdout, c_stderr, c_err := os.process_exec({command={#directory + "/structs/c_structs"}}, context.temp_allocator) + testing.expect_value(t, c_err, nil) + testing.expect_value(t, c_status.exit_code, 0) + testing.expect_value(t, string(c_stderr), "") + + o_status, o_stdout, o_stderr, o_err := os.process_exec({command={#directory + "/structs/odin_structs"}}, context.temp_allocator) + testing.expect_value(t, o_err, nil) + testing.expect_value(t, o_status.exit_code, 0) + testing.expect_value(t, string(o_stderr), "") + + testing.expect(t, strings.trim_space(string(c_stdout)) != "") + + testing.expect_value( + t, + strings.trim_space(string(o_stdout)), + strings.trim_space(string(c_stdout)), + ) +} diff --git a/tests/core/sys/kqueue/structs/structs.c b/tests/core/sys/kqueue/structs/structs.c new file mode 100644 index 000000000..e7620c994 --- /dev/null +++ b/tests/core/sys/kqueue/structs/structs.c @@ -0,0 +1,63 @@ +#include <stddef.h> +#include <stdio.h> +#include <sys/event.h> + +int main(int argc, char *argv[]) +{ + printf("kevent %zu %zu\n", sizeof(struct kevent), _Alignof(struct kevent)); + printf("kevent.ident %zu\n", offsetof(struct kevent, ident)); + printf("kevent.filter %zu\n", offsetof(struct kevent, filter)); + printf("kevent.flags %zu\n", offsetof(struct kevent, flags)); + printf("kevent.fflags %zu\n", offsetof(struct kevent, fflags)); + printf("kevent.data %zu\n", offsetof(struct kevent, data)); + printf("kevent.udata %zu\n", offsetof(struct kevent, udata)); + + printf("EV_ADD %d\n", EV_ADD); + printf("EV_DELETE %d\n", EV_DELETE); + printf("EV_ENABLE %d\n", EV_ENABLE); + printf("EV_DISABLE %d\n", EV_DISABLE); + printf("EV_ONESHOT %d\n", EV_ONESHOT); + printf("EV_CLEAR %d\n", EV_CLEAR); + printf("EV_RECEIPT %d\n", EV_RECEIPT); + printf("EV_DISPATCH %d\n", EV_DISPATCH); + printf("EV_ERROR %d\n", EV_ERROR); + printf("EV_EOF %d\n", EV_EOF); + + printf("EVFILT_READ %d\n", EVFILT_READ); + printf("EVFILT_WRITE %d\n", EVFILT_WRITE); + printf("EVFILT_AIO %d\n", EVFILT_AIO); + printf("EVFILT_VNODE %d\n", EVFILT_VNODE); + printf("EVFILT_PROC %d\n", EVFILT_PROC); + printf("EVFILT_SIGNAL %d\n", EVFILT_SIGNAL); + printf("EVFILT_TIMER %d\n", EVFILT_TIMER); + printf("EVFILT_USER %d\n", EVFILT_USER); + + printf("NOTE_SECONDS %u\n", NOTE_SECONDS); + printf("NOTE_USECONDS %u\n", NOTE_USECONDS); + printf("NOTE_NSECONDS %u\n", NOTE_NSECONDS); +#if defined(NOTE_ABSOLUTE) + printf("NOTE_ABSOLUTE %u\n", NOTE_ABSOLUTE); +#else + printf("NOTE_ABSOLUTE %u\n", NOTE_ABSTIME); +#endif + + printf("NOTE_LOWAT %u\n", NOTE_LOWAT); + + printf("NOTE_DELETE %u\n", NOTE_DELETE); + printf("NOTE_WRITE %u\n", NOTE_WRITE); + printf("NOTE_EXTEND %u\n", NOTE_EXTEND); + printf("NOTE_ATTRIB %u\n", NOTE_ATTRIB); + printf("NOTE_LINK %u\n", NOTE_LINK); + printf("NOTE_RENAME %u\n", NOTE_RENAME); + printf("NOTE_REVOKE %u\n", NOTE_REVOKE); + + printf("NOTE_EXIT %u\n", NOTE_EXIT); + printf("NOTE_FORK %u\n", NOTE_FORK); + printf("NOTE_EXEC %u\n", NOTE_EXEC); + + printf("NOTE_TRIGGER %u\n", NOTE_TRIGGER); + printf("NOTE_FFAND %u\n", NOTE_FFAND); + printf("NOTE_FFOR %u\n", NOTE_FFOR); + printf("NOTE_FFCOPY %u\n", NOTE_FFCOPY); + return 0; +} diff --git a/tests/core/sys/kqueue/structs/structs.odin b/tests/core/sys/kqueue/structs/structs.odin new file mode 100644 index 000000000..4886f63e4 --- /dev/null +++ b/tests/core/sys/kqueue/structs/structs.odin @@ -0,0 +1,58 @@ +package main + +import "core:fmt" +import "core:sys/kqueue" + +main :: proc() { + fmt.println("kevent", size_of(kqueue.KEvent), align_of(kqueue.KEvent)) + fmt.println("kevent.ident", offset_of(kqueue.KEvent, ident)) + fmt.println("kevent.filter", offset_of(kqueue.KEvent, filter)) + fmt.println("kevent.flags", offset_of(kqueue.KEvent, flags)) + fmt.println("kevent.fflags", offset_of(kqueue.KEvent, fflags)) + fmt.println("kevent.data", offset_of(kqueue.KEvent, data)) + fmt.println("kevent.udata", offset_of(kqueue.KEvent, udata)) + + fmt.println("EV_ADD", transmute(kqueue._Flags_Backing)kqueue.Flags{.Add}) + fmt.println("EV_DELETE", transmute(kqueue._Flags_Backing)kqueue.Flags{.Delete}) + fmt.println("EV_ENABLE", transmute(kqueue._Flags_Backing)kqueue.Flags{.Enable}) + fmt.println("EV_DISABLE", transmute(kqueue._Flags_Backing)kqueue.Flags{.Disable}) + fmt.println("EV_ONESHOT", transmute(kqueue._Flags_Backing)kqueue.Flags{.One_Shot}) + fmt.println("EV_CLEAR", transmute(kqueue._Flags_Backing)kqueue.Flags{.Clear}) + fmt.println("EV_RECEIPT", transmute(kqueue._Flags_Backing)kqueue.Flags{.Receipt}) + fmt.println("EV_DISPATCH", transmute(kqueue._Flags_Backing)kqueue.Flags{.Dispatch}) + fmt.println("EV_ERROR", transmute(kqueue._Flags_Backing)kqueue.Flags{.Error}) + fmt.println("EV_EOF", transmute(kqueue._Flags_Backing)kqueue.Flags{.EOF}) + + fmt.println("EVFILT_READ", int(kqueue.Filter.Read)) + fmt.println("EVFILT_WRITE", int(kqueue.Filter.Write)) + fmt.println("EVFILT_AIO", int(kqueue.Filter.AIO)) + fmt.println("EVFILT_VNODE", int(kqueue.Filter.VNode)) + fmt.println("EVFILT_PROC", int(kqueue.Filter.Proc)) + fmt.println("EVFILT_SIGNAL", int(kqueue.Filter.Signal)) + fmt.println("EVFILT_TIMER", int(kqueue.Filter.Timer)) + fmt.println("EVFILT_USER", int(kqueue.Filter.User)) + + fmt.println("NOTE_SECONDS", transmute(u32)kqueue.Timer_Flags{.Seconds}) + fmt.println("NOTE_USECONDS", transmute(u32)kqueue.Timer_Flags{.USeconds}) + fmt.println("NOTE_NSECONDS", transmute(u32)kqueue.TIMER_FLAGS_NSECONDS) + fmt.println("NOTE_ABSOLUTE", transmute(u32)kqueue.Timer_Flags{.Absolute}) + + fmt.println("NOTE_LOWAT", transmute(u32)kqueue.RW_Flags{.Low_Water_Mark}) + + fmt.println("NOTE_DELETE", transmute(u32)kqueue.VNode_Flags{.Delete}) + fmt.println("NOTE_WRITE", transmute(u32)kqueue.VNode_Flags{.Write}) + fmt.println("NOTE_EXTEND", transmute(u32)kqueue.VNode_Flags{.Extend}) + fmt.println("NOTE_ATTRIB", transmute(u32)kqueue.VNode_Flags{.Attrib}) + fmt.println("NOTE_LINK", transmute(u32)kqueue.VNode_Flags{.Link}) + fmt.println("NOTE_RENAME", transmute(u32)kqueue.VNode_Flags{.Rename}) + fmt.println("NOTE_REVOKE", transmute(u32)kqueue.VNode_Flags{.Revoke}) + + fmt.println("NOTE_EXIT", transmute(u32)kqueue.Proc_Flags{.Exit}) + fmt.println("NOTE_FORK", transmute(u32)kqueue.Proc_Flags{.Fork}) + fmt.println("NOTE_EXEC", transmute(u32)kqueue.Proc_Flags{.Exec}) + + fmt.println("NOTE_TRIGGER", transmute(u32)kqueue.User_Flags{.Trigger}) + fmt.println("NOTE_FFAND", transmute(u32)kqueue.User_Flags{.FFAnd}) + fmt.println("NOTE_FFOR", transmute(u32)kqueue.User_Flags{.FFOr}) + fmt.println("NOTE_FFCOPY", transmute(u32)kqueue.USER_FLAGS_COPY) +} diff --git a/tests/internal/test_imported_proc_groups.odin b/tests/internal/test_imported_proc_groups.odin new file mode 100644 index 000000000..e91af4bf5 --- /dev/null +++ b/tests/internal/test_imported_proc_groups.odin @@ -0,0 +1,11 @@ +package test_internal + +import "core:testing" +import "test_imported_proc_groups" + +// https://github.com/odin-lang/Odin/pull/6119 +@test +test_use_imported_proc_group_as_argument :: proc(t: ^testing.T) { + use_proc :: proc(proc()) { } + use_proc(test_imported_proc_groups.proc_group) +} diff --git a/tests/internal/test_imported_proc_groups/proc_group.odin b/tests/internal/test_imported_proc_groups/proc_group.odin new file mode 100644 index 000000000..59519ab9c --- /dev/null +++ b/tests/internal/test_imported_proc_groups/proc_group.odin @@ -0,0 +1,4 @@ +package test_imported_proc_groups + +proc_group :: proc{empty_proc} +empty_proc :: proc() { } diff --git a/vendor/curl/curl.odin b/vendor/curl/curl.odin index ce10c443f..41ecbcb75 100644 --- a/vendor/curl/curl.odin +++ b/vendor/curl/curl.odin @@ -14,23 +14,23 @@ when ODIN_OS == .Windows { "system:Ws2_32.lib", "system:iphlpapi.lib", } -} else when ODIN_OS == .Linux { +} else when ODIN_OS == .Darwin { @(export) foreign import lib { "system:curl", - "system:mbedtls", "system:mbedx509", "system:mbedcrypto", "system:z", + "system:SystemConfiguration.framework", } -} else when ODIN_OS == .Darwin { +} else { @(export) foreign import lib { "system:curl", + "system:mbedtls", "system:mbedx509", "system:mbedcrypto", "system:z", - "system:SystemConfiguration.framework", } } diff --git a/vendor/x11/xlib/xlib_procs.odin b/vendor/x11/xlib/xlib_procs.odin index c33c6d351..42e49ea02 100644 --- a/vendor/x11/xlib/xlib_procs.odin +++ b/vendor/x11/xlib/xlib_procs.odin @@ -241,7 +241,7 @@ foreign xlib { window: Window, pixel: uint, ) --- - SetWindowBackgroundMap :: proc( + SetWindowBackgroundPixmap :: proc( display: ^Display, window: Window, pixmap: Pixmap, |