diff options
| author | Colin Davidson <colrdavidson@gmail.com> | 2024-09-24 02:32:06 -0700 |
|---|---|---|
| committer | Colin Davidson <colrdavidson@gmail.com> | 2024-09-24 02:32:06 -0700 |
| commit | f3ab14b8ccb45d0fef8a96937635bdf0943ce7d6 (patch) | |
| tree | 1309d7c797117463996a84522ef3d1c9713a286c /core | |
| parent | 99938c7d4fb26d43a07dd4b8f4f00ab87e67e73f (diff) | |
| parent | f7d74ff3a8596efef67d151ffb758ed085e94be0 (diff) | |
Merge branch 'master' into macharena
Diffstat (limited to 'core')
401 files changed, 10760 insertions, 3352 deletions
diff --git a/core/bytes/buffer.odin b/core/bytes/buffer.odin index a7e9b1c64..f4d883353 100644 --- a/core/bytes/buffer.odin +++ b/core/bytes/buffer.odin @@ -144,6 +144,9 @@ buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) { } buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int, loc := #caller_location) -> (n: int, err: io.Error) { + if len(p) == 0 { + return 0, nil + } b.last_read = .Invalid if offset < 0 { err = .Invalid_Offset @@ -246,10 +249,13 @@ buffer_read_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io. } buffer_read_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) { + if len(p) == 0 { + return 0, nil + } b.last_read = .Invalid if uint(offset) >= len(b.buf) { - err = .Invalid_Offset + err = .EOF return } n = copy(p, b.buf[offset:]) @@ -310,6 +316,27 @@ buffer_unread_rune :: proc(b: ^Buffer) -> io.Error { return nil } +buffer_seek :: proc(b: ^Buffer, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { + abs: i64 + switch whence { + case .Start: + abs = offset + case .Current: + abs = i64(b.off) + offset + case .End: + abs = i64(len(b.buf)) + offset + case: + return 0, .Invalid_Whence + } + + abs_int := int(abs) + if abs_int < 0 { + return 0, .Invalid_Offset + } + b.last_read = .Invalid + b.off = abs_int + return abs, nil +} buffer_read_bytes :: proc(b: ^Buffer, delim: byte) -> (line: []byte, err: io.Error) { i := index_byte(b.buf[b.off:], delim) @@ -395,14 +422,17 @@ _buffer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse return io._i64_err(buffer_write(b, p)) case .Write_At: return io._i64_err(buffer_write_at(b, p, int(offset))) + case .Seek: + n, err = buffer_seek(b, offset, whence) + return case .Size: - n = i64(buffer_capacity(b)) + n = i64(buffer_length(b)) return case .Destroy: buffer_destroy(b) return case .Query: - return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Size, .Destroy}) + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Destroy, .Query}) } return 0, .Empty } diff --git a/core/bytes/bytes.odin b/core/bytes/bytes.odin index 5a510951e..c0d25bcce 100644 --- a/core/bytes/bytes.odin +++ b/core/bytes/bytes.odin @@ -334,7 +334,7 @@ Inputs: Returns: - index: The index of the byte `c`, or -1 if it was not found. */ -index_byte :: proc(s: []byte, c: byte) -> (index: int) #no_bounds_check { +index_byte :: proc "contextless" (s: []byte, c: byte) -> (index: int) #no_bounds_check { i, l := 0, len(s) // Guard against small strings. On modern systems, it is ALWAYS @@ -469,18 +469,16 @@ Inputs: Returns: - index: The index of the byte `c`, or -1 if it was not found. */ -last_index_byte :: proc(s: []byte, c: byte) -> int #no_bounds_check { +last_index_byte :: proc "contextless" (s: []byte, c: byte) -> int #no_bounds_check { i := len(s) // Guard against small strings. On modern systems, it is ALWAYS // worth vectorizing assuming there is a hardware vector unit, and // the data size is large enough. if i < SIMD_REG_SIZE_128 { - if i > 0 { // Handle s == nil. - for /**/; i >= 0; i -= 1 { - if s[i] == c { - return i - } + #reverse for ch, j in s { + if ch == c { + return j } } return -1 diff --git a/core/bytes/reader.odin b/core/bytes/reader.odin index 4b18345ba..2e1c5ed42 100644 --- a/core/bytes/reader.odin +++ b/core/bytes/reader.odin @@ -9,10 +9,11 @@ Reader :: struct { prev_rune: int, // previous reading index of rune or < 0 } -reader_init :: proc(r: ^Reader, s: []byte) { +reader_init :: proc(r: ^Reader, s: []byte) -> io.Stream { r.s = s r.i = 0 r.prev_rune = -1 + return reader_to_stream(r) } reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) { @@ -33,6 +34,9 @@ reader_size :: proc(r: ^Reader) -> i64 { } reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) { + if len(p) == 0 { + return 0, nil + } if r.i >= i64(len(r.s)) { return 0, .EOF } @@ -42,6 +46,9 @@ reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) { return } reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Error) { + if len(p) == 0 { + return 0, nil + } if off < 0 { return 0, .Invalid_Offset } @@ -97,7 +104,6 @@ reader_unread_rune :: proc(r: ^Reader) -> io.Error { return nil } reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { - r.prev_rune = -1 abs: i64 switch whence { case .Start: @@ -114,6 +120,7 @@ reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.E return 0, .Invalid_Offset } r.i = abs + r.prev_rune = -1 return abs, nil } reader_write_to :: proc(r: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) { diff --git a/core/c/libc/errno.odin b/core/c/libc/errno.odin index 843b2f1b6..de429a6ec 100644 --- a/core/c/libc/errno.odin +++ b/core/c/libc/errno.odin @@ -98,6 +98,14 @@ when ODIN_OS == .Haiku { ERANGE :: B_POSIX_ERROR_BASE + 17 } +when ODIN_OS == .JS { + _ :: libc + _get_errno :: proc "c" () -> ^int { + @(static) errno: int + return &errno + } +} + // Odin has no way to make an identifier "errno" behave as a function call to // read the value, or to produce an lvalue such that you can assign a different // error value to errno. To work around this, just expose it as a function like diff --git a/core/c/libc/stdio.odin b/core/c/libc/stdio.odin index 4be00ff0b..a94a53696 100644 --- a/core/c/libc/stdio.odin +++ b/core/c/libc/stdio.odin @@ -89,6 +89,30 @@ when ODIN_OS == .Linux { } } +when ODIN_OS == .JS { + fpos_t :: struct #raw_union { _: [16]char, _: longlong, _: double, } + + _IOFBF :: 0 + _IOLBF :: 1 + _IONBF :: 2 + + BUFSIZ :: 1024 + + EOF :: int(-1) + + FOPEN_MAX :: 1000 + + FILENAME_MAX :: 4096 + + L_tmpnam :: 20 + + SEEK_SET :: 0 + SEEK_CUR :: 1 + SEEK_END :: 2 + + TMP_MAX :: 308915776 +} + when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { fpos_t :: distinct i64 @@ -368,7 +392,7 @@ to_stream :: proc(file: ^FILE) -> io.Stream { return 0, .Empty case .Query: - return io.query_utility({ .Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size }) + return io.query_utility({ .Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query }) } return } diff --git a/core/c/libc/stdlib.odin b/core/c/libc/stdlib.odin index 08c6fa6f0..98280e44b 100644 --- a/core/c/libc/stdlib.odin +++ b/core/c/libc/stdlib.odin @@ -10,6 +10,9 @@ when ODIN_OS == .Windows { foreign import libc "system:c" } +@(require) +import "base:runtime" + when ODIN_OS == .Windows { RAND_MAX :: 0x7fff @@ -145,6 +148,10 @@ aligned_alloc :: #force_inline proc "c" (alignment, size: size_t) -> rawptr { _aligned_malloc :: proc(size, alignment: size_t) -> rawptr --- } return _aligned_malloc(size=size, alignment=alignment) + } else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { + context = runtime.default_context() + data, _ := runtime.mem_alloc_bytes(auto_cast size, auto_cast alignment) + return raw_data(data) } else { foreign libc { aligned_alloc :: proc(alignment, size: size_t) -> rawptr --- @@ -160,6 +167,9 @@ aligned_free :: #force_inline proc "c" (ptr: rawptr) { _aligned_free :: proc(ptr: rawptr) --- } _aligned_free(ptr) + } else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { + context = runtime.default_context() + runtime.mem_free(ptr) } else { free(ptr) } diff --git a/core/c/libc/string.odin b/core/c/libc/string.odin index cde9c7e6b..4ec4f3a7a 100644 --- a/core/c/libc/string.odin +++ b/core/c/libc/string.odin @@ -12,6 +12,7 @@ when ODIN_OS == .Windows { foreign import libc "system:c" } +@(default_calling_convention="c") foreign libc { // 7.24.2 Copying functions memcpy :: proc(s1, s2: rawptr, n: size_t) -> rawptr --- diff --git a/core/c/libc/time.odin b/core/c/libc/time.odin index 21859c602..6828793ec 100644 --- a/core/c/libc/time.odin +++ b/core/c/libc/time.odin @@ -45,7 +45,7 @@ when ODIN_OS == .Windows { } } -when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku { +when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku || ODIN_OS == .JS { @(default_calling_convention="c") foreign libc { // 7.27.2 Time manipulation functions @@ -79,7 +79,7 @@ when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS = } else { @(private) LDIFFTIME :: "difftime" @(private) LMKTIME :: "mktime" - @(private) LTIME :: "ltime" + @(private) LTIME :: "time" @(private) LCTIME :: "ctime" @(private) LGMTIME :: "gmtime" @(private) LLOCALTIME :: "localtime" diff --git a/core/c/libc/wctype.odin b/core/c/libc/wctype.odin index a41fe7fac..b96410b4c 100644 --- a/core/c/libc/wctype.odin +++ b/core/c/libc/wctype.odin @@ -14,7 +14,7 @@ when ODIN_OS == .Windows { wctrans_t :: distinct wchar_t wctype_t :: distinct ushort -} else when ODIN_OS == .Linux { +} else when ODIN_OS == .Linux || ODIN_OS == .JS { wctrans_t :: distinct intptr_t wctype_t :: distinct ulong diff --git a/core/compress/gzip/doc.odin b/core/compress/gzip/doc.odin new file mode 100644 index 000000000..fd7ef5a19 --- /dev/null +++ b/core/compress/gzip/doc.odin @@ -0,0 +1,90 @@ +/* + Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. + Made available under Odin's BSD-3 license. + + List of contributors: + Jeroen van Rijn: Initial implementation. + Ginger Bill: Cosmetic changes. + + A small GZIP implementation as an example. +*/ + +/* +Example: + import "core:bytes" + import "core:os" + import "core:compress" + import "core:fmt" + + // Small GZIP file with fextra, fname and fcomment present. + @private + TEST: []u8 = { + 0x1f, 0x8b, 0x08, 0x1c, 0xcb, 0x3b, 0x3a, 0x5a, + 0x02, 0x03, 0x07, 0x00, 0x61, 0x62, 0x03, 0x00, + 0x63, 0x64, 0x65, 0x66, 0x69, 0x6c, 0x65, 0x6e, + 0x61, 0x6d, 0x65, 0x00, 0x54, 0x68, 0x69, 0x73, + 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x63, 0x6f, + 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x2b, 0x48, + 0xac, 0xcc, 0xc9, 0x4f, 0x4c, 0x01, 0x00, 0x15, + 0x6a, 0x2c, 0x42, 0x07, 0x00, 0x00, 0x00, + } + + main :: proc() { + // Set up output buffer. + buf := bytes.Buffer{} + + stdout :: proc(s: string) { + os.write_string(os.stdout, s) + } + stderr :: proc(s: string) { + os.write_string(os.stderr, s) + } + + args := os.args + + if len(args) < 2 { + stderr("No input file specified.\n") + err := load(data=TEST, buf=&buf, known_gzip_size=len(TEST)) + if err == nil { + stdout("Displaying test vector: ") + stdout(bytes.buffer_to_string(&buf)) + stdout("\n") + } else { + fmt.printf("gzip.load returned %v\n", err) + } + bytes.buffer_destroy(&buf) + os.exit(0) + } + + // The rest are all files. + args = args[1:] + err: Error + + for file in args { + if file == "-" { + // Read from stdin + s := os.stream_from_handle(os.stdin) + ctx := &compress.Context_Stream_Input{ + input = s, + } + err = load(ctx, &buf) + } else { + err = load(file, &buf) + } + if err != nil { + if err != E_General.File_Not_Found { + stderr("File not found: ") + stderr(file) + stderr("\n") + os.exit(1) + } + stderr("GZIP returned an error.\n") + bytes.buffer_destroy(&buf) + os.exit(2) + } + stdout(bytes.buffer_to_string(&buf)) + } + bytes.buffer_destroy(&buf) + } +*/ +package compress_gzip diff --git a/core/compress/gzip/example.odin b/core/compress/gzip/example.odin deleted file mode 100644 index 09540aafc..000000000 --- a/core/compress/gzip/example.odin +++ /dev/null @@ -1,89 +0,0 @@ -//+build ignore -package compress_gzip - -/* - Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. - Made available under Odin's BSD-3 license. - - List of contributors: - Jeroen van Rijn: Initial implementation. - Ginger Bill: Cosmetic changes. - - A small GZIP implementation as an example. -*/ - -import "core:bytes" -import "core:os" -import "core:compress" -import "core:fmt" - -// Small GZIP file with fextra, fname and fcomment present. -@private -TEST: []u8 = { - 0x1f, 0x8b, 0x08, 0x1c, 0xcb, 0x3b, 0x3a, 0x5a, - 0x02, 0x03, 0x07, 0x00, 0x61, 0x62, 0x03, 0x00, - 0x63, 0x64, 0x65, 0x66, 0x69, 0x6c, 0x65, 0x6e, - 0x61, 0x6d, 0x65, 0x00, 0x54, 0x68, 0x69, 0x73, - 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x63, 0x6f, - 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x2b, 0x48, - 0xac, 0xcc, 0xc9, 0x4f, 0x4c, 0x01, 0x00, 0x15, - 0x6a, 0x2c, 0x42, 0x07, 0x00, 0x00, 0x00, -} - -main :: proc() { - // Set up output buffer. - buf := bytes.Buffer{} - - stdout :: proc(s: string) { - os.write_string(os.stdout, s) - } - stderr :: proc(s: string) { - os.write_string(os.stderr, s) - } - - args := os.args - - if len(args) < 2 { - stderr("No input file specified.\n") - err := load(data=TEST, buf=&buf, known_gzip_size=len(TEST)) - if err == nil { - stdout("Displaying test vector: ") - stdout(bytes.buffer_to_string(&buf)) - stdout("\n") - } else { - fmt.printf("gzip.load returned %v\n", err) - } - bytes.buffer_destroy(&buf) - os.exit(0) - } - - // The rest are all files. - args = args[1:] - err: Error - - for file in args { - if file == "-" { - // Read from stdin - s := os.stream_from_handle(os.stdin) - ctx := &compress.Context_Stream_Input{ - input = s, - } - err = load(ctx, &buf) - } else { - err = load(file, &buf) - } - if err != nil { - if err != E_General.File_Not_Found { - stderr("File not found: ") - stderr(file) - stderr("\n") - os.exit(1) - } - stderr("GZIP returned an error.\n") - bytes.buffer_destroy(&buf) - os.exit(2) - } - stdout(bytes.buffer_to_string(&buf)) - } - bytes.buffer_destroy(&buf) -} diff --git a/core/compress/shoco/model.odin b/core/compress/shoco/model.odin index f62236c00..919563441 100644 --- a/core/compress/shoco/model.odin +++ b/core/compress/shoco/model.odin @@ -4,7 +4,6 @@ which is an English word model. */ -// package shoco is an implementation of the shoco short string compressor package compress_shoco DEFAULT_MODEL :: Shoco_Model { @@ -145,4 +144,4 @@ DEFAULT_MODEL :: Shoco_Model { { 0xc0000000, 2, 4, { 25, 22, 19, 16, 16, 16, 16, 16 }, { 15, 7, 7, 7, 0, 0, 0, 0 }, 0xe0, 0xc0 }, { 0xe0000000, 4, 8, { 23, 19, 15, 11, 8, 5, 2, 0 }, { 31, 15, 15, 15, 7, 7, 7, 3 }, 0xf0, 0xe0 }, }, -}
\ No newline at end of file +} diff --git a/core/compress/shoco/shoco.odin b/core/compress/shoco/shoco.odin index 269dd8875..b393b8356 100644 --- a/core/compress/shoco/shoco.odin +++ b/core/compress/shoco/shoco.odin @@ -8,7 +8,7 @@ An implementation of [shoco](https://github.com/Ed-von-Schleck/shoco) by Christian Schramm. */ -// package shoco is an implementation of the shoco short string compressor +// package shoco is an implementation of the shoco short string compressor. package compress_shoco import "base:intrinsics" @@ -308,4 +308,4 @@ compress_string :: proc(input: string, model := DEFAULT_MODEL, allocator := cont resize(&buf, length) or_return return buf[:length], result } -compress :: proc{compress_string_to_buffer, compress_string}
\ No newline at end of file +compress :: proc{compress_string_to_buffer, compress_string} diff --git a/core/compress/zlib/doc.odin b/core/compress/zlib/doc.odin new file mode 100644 index 000000000..0a5b4eb40 --- /dev/null +++ b/core/compress/zlib/doc.odin @@ -0,0 +1,50 @@ +/* + Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. + Made available under Odin's BSD-3 license. + + List of contributors: + Jeroen van Rijn: Initial implementation. + + An example of how to use `zlib.inflate`. +*/ + +/* +Example: + package main + + import "core:bytes" + import "core:fmt" + + main :: proc() { + ODIN_DEMO := []u8{ + 120, 218, 101, 144, 65, 110, 131, 48, 16, 69, 215, 246, 41, 190, 44, 69, 73, 32, 148, 182, + 75, 75, 28, 32, 251, 46, 217, 88, 238, 0, 86, 192, 32, 219, 36, 170, 170, 172, 122, 137, + 238, 122, 197, 30, 161, 70, 162, 20, 81, 203, 139, 25, 191, 255, 191, 60, 51, 40, 125, 81, + 53, 33, 144, 15, 156, 155, 110, 232, 93, 128, 208, 189, 35, 89, 117, 65, 112, 222, 41, 99, + 33, 37, 6, 215, 235, 195, 17, 239, 156, 197, 170, 118, 170, 131, 44, 32, 82, 164, 72, 240, + 253, 245, 249, 129, 12, 185, 224, 76, 105, 61, 118, 99, 171, 66, 239, 38, 193, 35, 103, 85, + 172, 66, 127, 33, 139, 24, 244, 235, 141, 49, 204, 223, 76, 208, 205, 204, 166, 7, 173, 60, + 97, 159, 238, 37, 214, 41, 105, 129, 167, 5, 102, 27, 152, 173, 97, 178, 129, 73, 129, 231, + 5, 230, 27, 152, 175, 225, 52, 192, 127, 243, 170, 157, 149, 18, 121, 142, 115, 109, 227, 122, + 64, 87, 114, 111, 161, 49, 182, 6, 181, 158, 162, 226, 206, 167, 27, 215, 246, 48, 56, 99, + 67, 117, 16, 47, 13, 45, 35, 151, 98, 231, 75, 1, 173, 90, 61, 101, 146, 71, 136, 244, + 170, 218, 145, 176, 123, 45, 173, 56, 113, 134, 191, 51, 219, 78, 235, 95, 28, 249, 253, 7, + 159, 150, 133, 125, + } + OUTPUT_SIZE :: 432 + + buf: bytes.Buffer + + // We can pass ", true" to inflate a raw DEFLATE stream instead of a ZLIB wrapped one. + err := inflate(input=ODIN_DEMO, buf=&buf, expected_output_size=OUTPUT_SIZE) + defer bytes.buffer_destroy(&buf) + + if err != nil { + fmt.printf("\nError: %v\n", err) + } + s := bytes.buffer_to_string(&buf) + fmt.printf("Input: %v bytes, output (%v bytes):\n%v\n", len(ODIN_DEMO), len(s), s) + assert(len(s) == OUTPUT_SIZE) + } +*/ +package compress_zlib diff --git a/core/compress/zlib/example.odin b/core/compress/zlib/example.odin deleted file mode 100644 index fedd6671d..000000000 --- a/core/compress/zlib/example.odin +++ /dev/null @@ -1,47 +0,0 @@ -//+build ignore -package compress_zlib - -/* - Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. - Made available under Odin's BSD-3 license. - - List of contributors: - Jeroen van Rijn: Initial implementation. - - An example of how to use `zlib.inflate`. -*/ - -import "core:bytes" -import "core:fmt" - -main :: proc() { - ODIN_DEMO := []u8{ - 120, 218, 101, 144, 65, 110, 131, 48, 16, 69, 215, 246, 41, 190, 44, 69, 73, 32, 148, 182, - 75, 75, 28, 32, 251, 46, 217, 88, 238, 0, 86, 192, 32, 219, 36, 170, 170, 172, 122, 137, - 238, 122, 197, 30, 161, 70, 162, 20, 81, 203, 139, 25, 191, 255, 191, 60, 51, 40, 125, 81, - 53, 33, 144, 15, 156, 155, 110, 232, 93, 128, 208, 189, 35, 89, 117, 65, 112, 222, 41, 99, - 33, 37, 6, 215, 235, 195, 17, 239, 156, 197, 170, 118, 170, 131, 44, 32, 82, 164, 72, 240, - 253, 245, 249, 129, 12, 185, 224, 76, 105, 61, 118, 99, 171, 66, 239, 38, 193, 35, 103, 85, - 172, 66, 127, 33, 139, 24, 244, 235, 141, 49, 204, 223, 76, 208, 205, 204, 166, 7, 173, 60, - 97, 159, 238, 37, 214, 41, 105, 129, 167, 5, 102, 27, 152, 173, 97, 178, 129, 73, 129, 231, - 5, 230, 27, 152, 175, 225, 52, 192, 127, 243, 170, 157, 149, 18, 121, 142, 115, 109, 227, 122, - 64, 87, 114, 111, 161, 49, 182, 6, 181, 158, 162, 226, 206, 167, 27, 215, 246, 48, 56, 99, - 67, 117, 16, 47, 13, 45, 35, 151, 98, 231, 75, 1, 173, 90, 61, 101, 146, 71, 136, 244, - 170, 218, 145, 176, 123, 45, 173, 56, 113, 134, 191, 51, 219, 78, 235, 95, 28, 249, 253, 7, - 159, 150, 133, 125, - } - OUTPUT_SIZE :: 432 - - buf: bytes.Buffer - - // We can pass ", true" to inflate a raw DEFLATE stream instead of a ZLIB wrapped one. - err := inflate(input=ODIN_DEMO, buf=&buf, expected_output_size=OUTPUT_SIZE) - defer bytes.buffer_destroy(&buf) - - if err != nil { - fmt.printf("\nError: %v\n", err) - } - s := bytes.buffer_to_string(&buf) - fmt.printf("Input: %v bytes, output (%v bytes):\n%v\n", len(ODIN_DEMO), len(s), s) - assert(len(s) == OUTPUT_SIZE) -} diff --git a/core/compress/zlib/zlib.odin b/core/compress/zlib/zlib.odin index 2dc9e81df..be8a7d7d3 100644 --- a/core/compress/zlib/zlib.odin +++ b/core/compress/zlib/zlib.odin @@ -1,4 +1,4 @@ -//+vet !using-param +#+vet !using-param package compress_zlib /* diff --git a/core/container/bit_array/bit_array.odin b/core/container/bit_array/bit_array.odin index b53bacda7..9a76dc78f 100644 --- a/core/container/bit_array/bit_array.odin +++ b/core/container/bit_array/bit_array.odin @@ -1,5 +1,6 @@ package container_dynamic_bit_array +import "base:builtin" import "base:intrinsics" import "core:mem" @@ -18,7 +19,7 @@ NUM_BITS :: 64 Bit_Array :: struct { bits: [dynamic]u64, bias: int, - max_index: int, + length: int, free_pointer: bool, } @@ -52,9 +53,9 @@ Returns: */ iterate_by_all :: proc (it: ^Bit_Array_Iterator) -> (set: bool, index: int, ok: bool) { index = it.word_idx * NUM_BITS + int(it.bit_idx) + it.array.bias - if index > it.array.max_index { return false, 0, false } + if index >= it.array.length + it.array.bias { return false, 0, false } - word := it.array.bits[it.word_idx] if len(it.array.bits) > it.word_idx else 0 + word := it.array.bits[it.word_idx] if builtin.len(it.array.bits) > it.word_idx else 0 set = (word >> it.bit_idx & 1) == 1 it.bit_idx += 1 @@ -106,22 +107,22 @@ Returns: */ @(private="file") iterate_internal_ :: proc (it: ^Bit_Array_Iterator, $ITERATE_SET_BITS: bool) -> (index: int, ok: bool) { - word := it.array.bits[it.word_idx] if len(it.array.bits) > it.word_idx else 0 + word := it.array.bits[it.word_idx] if builtin.len(it.array.bits) > it.word_idx else 0 when ! ITERATE_SET_BITS { word = ~word } // If the word is empty or we have already gone over all the bits in it, // b.bit_idx is greater than the index of any set bit in the word, // meaning that word >> b.bit_idx == 0. - for it.word_idx < len(it.array.bits) && word >> it.bit_idx == 0 { + for it.word_idx < builtin.len(it.array.bits) && word >> it.bit_idx == 0 { it.word_idx += 1 it.bit_idx = 0 - word = it.array.bits[it.word_idx] if len(it.array.bits) > it.word_idx else 0 + word = it.array.bits[it.word_idx] if builtin.len(it.array.bits) > it.word_idx else 0 when ! ITERATE_SET_BITS { word = ~word } } // If we are iterating the set bits, reaching the end of the array means we have no more bits to check when ITERATE_SET_BITS { - if it.word_idx >= len(it.array.bits) { + if it.word_idx >= builtin.len(it.array.bits) { return 0, false } } @@ -135,7 +136,7 @@ iterate_internal_ :: proc (it: ^Bit_Array_Iterator, $ITERATE_SET_BITS: bool) -> it.bit_idx = 0 it.word_idx += 1 } - return index, index <= it.array.max_index + return index, index < it.array.length + it.array.bias } /* Gets the state of a bit in the bit-array @@ -160,7 +161,7 @@ get :: proc(ba: ^Bit_Array, #any_int index: uint) -> (res: bool, ok: bool) #opti If we `get` a bit that doesn't fit in the Bit Array, it's naturally `false`. This early-out prevents unnecessary resizing. */ - if leg_index + 1 > len(ba.bits) { return false, true } + if leg_index + 1 > builtin.len(ba.bits) { return false, true } val := u64(1 << uint(bit_index)) res = ba.bits[leg_index] & val == val @@ -208,7 +209,7 @@ set :: proc(ba: ^Bit_Array, #any_int index: uint, set_to: bool = true, allocator resize_if_needed(ba, leg_index) or_return - ba.max_index = max(idx, ba.max_index) + ba.length = max(1 + idx, ba.length) if set_to { ba.bits[leg_index] |= 1 << uint(bit_index) @@ -261,6 +262,9 @@ unsafe_unset :: proc(b: ^Bit_Array, bit: int) #no_bounds_check { /* A helper function to create a Bit Array with optional bias, in case your smallest index is non-zero (including negative). +The range of bits created by this procedure is `min_index..<max_index`, and the +array will be able to expand beyond `max_index` if needed. + *Allocates (`new(Bit_Array) & make(ba.bits)`)* Inputs: @@ -275,7 +279,7 @@ create :: proc(max_index: int, min_index: int = 0, allocator := context.allocato context.allocator = allocator size_in_bits := max_index - min_index - if size_in_bits < 1 { return {}, false } + if size_in_bits < 0 { return {}, false } legs := size_in_bits >> INDEX_SHIFT if size_in_bits & INDEX_MASK > 0 {legs+=1} @@ -284,7 +288,7 @@ create :: proc(max_index: int, min_index: int = 0, allocator := context.allocato res = new(Bit_Array) res.bits = bits res.bias = min_index - res.max_index = max_index + res.length = max_index - min_index res.free_pointer = true return } @@ -299,6 +303,48 @@ clear :: proc(ba: ^Bit_Array) { mem.zero_slice(ba.bits[:]) } /* +Gets the length of set and unset valid bits in the Bit_Array. + +Inputs: +- ba: The target Bit_Array + +Returns: +- length: The length of valid bits. +*/ +len :: proc(ba: ^Bit_Array) -> (length: int) { + if ba == nil { return } + return ba.length +} +/* +Shrinks the Bit_Array's backing storage to the smallest possible size. + +Inputs: +- ba: The target Bit_Array +*/ +shrink :: proc(ba: ^Bit_Array) #no_bounds_check { + if ba == nil { return } + legs_needed := builtin.len(ba.bits) + for i := legs_needed - 1; i >= 0; i -= 1 { + if ba.bits[i] == 0 { + legs_needed -= 1 + } else { + break + } + } + if legs_needed == builtin.len(ba.bits) { + return + } + ba.length = 0 + if legs_needed > 0 { + if legs_needed > 1 { + ba.length = (legs_needed - 1) * NUM_BITS + } + ba.length += NUM_BITS - int(intrinsics.count_leading_zeros(ba.bits[legs_needed - 1])) + } + resize(&ba.bits, legs_needed) + builtin.shrink(&ba.bits) +} +/* Deallocates the Bit_Array and its backing storage Inputs: @@ -321,8 +367,8 @@ resize_if_needed :: proc(ba: ^Bit_Array, legs: int, allocator := context.allocat context.allocator = allocator - if legs + 1 > len(ba.bits) { + if legs + 1 > builtin.len(ba.bits) { resize(&ba.bits, legs + 1) } - return len(ba.bits) > legs + return builtin.len(ba.bits) > legs } diff --git a/core/container/bit_array/doc.odin b/core/container/bit_array/doc.odin index 77e1904a8..36bf90002 100644 --- a/core/container/bit_array/doc.odin +++ b/core/container/bit_array/doc.odin @@ -1,8 +1,8 @@ /* The Bit Array can be used in several ways: -- By default you don't need to instantiate a Bit Array: - +By default you don't need to instantiate a Bit Array. +Example: package test import "core:fmt" @@ -22,8 +22,8 @@ The Bit Array can be used in several ways: destroy(&bits) } -- A Bit Array can optionally allow for negative indices, if the minimum value was given during creation: - +A Bit Array can optionally allow for negative indices, if the minimum value was given during creation. +Example: package test import "core:fmt" diff --git a/core/container/intrusive/list/doc.odin b/core/container/intrusive/list/doc.odin index 1a5a12f49..155f1dfe2 100644 --- a/core/container/intrusive/list/doc.odin +++ b/core/container/intrusive/list/doc.odin @@ -1,22 +1,22 @@ /* Package list implements an intrusive doubly-linked list. -An intrusive container requires a `Node` to be embedded in your own structure, like this: - +An intrusive container requires a `Node` to be embedded in your own structure, like this. +Example: My_String :: struct { node: list.Node, value: string, } -Embedding the members of a `list.Node` in your structure with the `using` keyword is also allowed: - +Embedding the members of a `list.Node` in your structure with the `using` keyword is also allowed. +Example: My_String :: struct { using node: list.Node, value: string, } -Here is a full example: - +Here is a full example. +Example: package test import "core:fmt" @@ -42,5 +42,8 @@ Here is a full example: value: string, } +Output: + Hello + World */ package container_intrusive_list diff --git a/core/container/small_array/small_array.odin b/core/container/small_array/small_array.odin index b2068469d..77bb21cbc 100644 --- a/core/container/small_array/small_array.odin +++ b/core/container/small_array/small_array.odin @@ -139,9 +139,13 @@ clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) { resize(a, 0) } -push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) { - n := copy(a.data[a.len:], items[:]) - a.len += n +push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) -> bool { + if a.len + builtin.len(items) <= cap(a^) { + n := copy(a.data[a.len:], items[:]) + a.len += n + return true + } + return false } inject_at :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T, index: int) -> bool #no_bounds_check { diff --git a/core/crypto/_aes/hw_intel/api.odin b/core/crypto/_aes/hw_intel/api.odin index 1796bb093..52669cb35 100644 --- a/core/crypto/_aes/hw_intel/api.odin +++ b/core/crypto/_aes/hw_intel/api.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package aes_hw_intel import "core:sys/info" diff --git a/core/crypto/_aes/hw_intel/ghash.odin b/core/crypto/_aes/hw_intel/ghash.odin index d61e71b3a..4320dd59b 100644 --- a/core/crypto/_aes/hw_intel/ghash.odin +++ b/core/crypto/_aes/hw_intel/ghash.odin @@ -20,7 +20,7 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -//+build amd64 +#+build amd64 package aes_hw_intel import "base:intrinsics" diff --git a/core/crypto/_aes/hw_intel/hw_intel_keysched.odin b/core/crypto/_aes/hw_intel/hw_intel_keysched.odin index 911dffbd5..bdf0d3066 100644 --- a/core/crypto/_aes/hw_intel/hw_intel_keysched.odin +++ b/core/crypto/_aes/hw_intel/hw_intel_keysched.odin @@ -20,7 +20,7 @@ // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -//+build amd64 +#+build amd64 package aes_hw_intel import "base:intrinsics" diff --git a/core/crypto/_chacha20/simd256/chacha20_simd256.odin b/core/crypto/_chacha20/simd256/chacha20_simd256.odin index 10f2d75fe..ccb02a947 100644 --- a/core/crypto/_chacha20/simd256/chacha20_simd256.odin +++ b/core/crypto/_chacha20/simd256/chacha20_simd256.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package chacha20_simd256 import "base:intrinsics" diff --git a/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin b/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin index 039d6cb96..ce673b42b 100644 --- a/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin +++ b/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin @@ -1,4 +1,4 @@ -//+build !amd64 +#+build !amd64 package chacha20_simd256 import "base:intrinsics" diff --git a/core/crypto/aead/doc.odin b/core/crypto/aead/doc.odin index 388d31453..93be674a0 100644 --- a/core/crypto/aead/doc.odin +++ b/core/crypto/aead/doc.odin @@ -10,49 +10,48 @@ algorithm. WARNING: Reusing the same key + iv to seal (encrypt) multiple messages results in catastrophic loss of security for most algorithms. -```odin -package aead_example - -import "core:bytes" -import "core:crypto" -import "core:crypto/aead" - -main :: proc() { - algo := aead.Algorithm.XCHACHA20POLY1305 - - // The example added associated data, and plaintext. - aad_str := "Get your ass in gear boys." - pt_str := "They're immanetizing the Eschaton." - - aad := transmute([]byte)aad_str - plaintext := transmute([]byte)pt_str - pt_len := len(plaintext) - - // Generate a random key for the purposes of illustration. - key := make([]byte, aead.KEY_SIZES[algo]) - defer delete(key) - crypto.rand_bytes(key) - - // `ciphertext || tag`, is a common way data is transmitted, so - // demonstrate that. - buf := make([]byte, pt_len + aead.TAG_SIZES[algo]) - defer delete(buf) - ciphertext, tag := buf[:pt_len], buf[pt_len:] - - // Seal the AAD + Plaintext. - iv := make([]byte, aead.IV_SIZES[algo]) - defer delete(iv) - crypto.rand_bytes(iv) // Random IVs are safe with XChaCha20-Poly1305. - aead.seal(algo, ciphertext, tag, key, iv, aad, plaintext) - - // Open the AAD + Ciphertext. - opened_pt := buf[:pt_len] - if ok := aead.open(algo, opened_pt, key, iv, aad, ciphertext, tag); !ok { - panic("aead example: failed to open") +Example: + package aead_example + + import "core:bytes" + import "core:crypto" + import "core:crypto/aead" + + main :: proc() { + algo := aead.Algorithm.XCHACHA20POLY1305 + + // The example added associated data, and plaintext. + aad_str := "Get your ass in gear boys." + pt_str := "They're immanetizing the Eschaton." + + aad := transmute([]byte)aad_str + plaintext := transmute([]byte)pt_str + pt_len := len(plaintext) + + // Generate a random key for the purposes of illustration. + key := make([]byte, aead.KEY_SIZES[algo]) + defer delete(key) + crypto.rand_bytes(key) + + // `ciphertext || tag`, is a common way data is transmitted, so + // demonstrate that. + buf := make([]byte, pt_len + aead.TAG_SIZES[algo]) + defer delete(buf) + ciphertext, tag := buf[:pt_len], buf[pt_len:] + + // Seal the AAD + Plaintext. + iv := make([]byte, aead.IV_SIZES[algo]) + defer delete(iv) + crypto.rand_bytes(iv) // Random IVs are safe with XChaCha20-Poly1305. + aead.seal(algo, ciphertext, tag, key, iv, aad, plaintext) + + // Open the AAD + Ciphertext. + opened_pt := buf[:pt_len] + if ok := aead.open(algo, opened_pt, key, iv, aad, ciphertext, tag); !ok { + panic("aead example: failed to open") + } + + assert(bytes.equal(opened_pt, plaintext)) } - - assert(bytes.equal(opened_pt, plaintext)) -} -``` */ -package aead
\ No newline at end of file +package aead diff --git a/core/crypto/aes/aes.odin b/core/crypto/aes/aes.odin index ef305fd21..57f49acf4 100644 --- a/core/crypto/aes/aes.odin +++ b/core/crypto/aes/aes.odin @@ -2,9 +2,9 @@ package aes implements the AES block cipher and some common modes. See: -- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf -- https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf -- https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf +- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf ]] +- [[ https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf ]] +- [[ https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf ]] */ package aes diff --git a/core/crypto/aes/aes_ctr_hw_intel.odin b/core/crypto/aes/aes_ctr_hw_intel.odin index 1c9e815ad..415758b24 100644 --- a/core/crypto/aes/aes_ctr_hw_intel.odin +++ b/core/crypto/aes/aes_ctr_hw_intel.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package aes import "base:intrinsics" diff --git a/core/crypto/aes/aes_ecb_hw_intel.odin b/core/crypto/aes/aes_ecb_hw_intel.odin index b2ff36a0c..f1d44a25f 100644 --- a/core/crypto/aes/aes_ecb_hw_intel.odin +++ b/core/crypto/aes/aes_ecb_hw_intel.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package aes import "base:intrinsics" diff --git a/core/crypto/aes/aes_gcm_hw_intel.odin b/core/crypto/aes/aes_gcm_hw_intel.odin index ffd8ed642..4cb5ab3b2 100644 --- a/core/crypto/aes/aes_gcm_hw_intel.odin +++ b/core/crypto/aes/aes_gcm_hw_intel.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package aes import "base:intrinsics" diff --git a/core/crypto/aes/aes_impl_hw_gen.odin b/core/crypto/aes/aes_impl_hw_gen.odin index 3557b1aae..0c9ec6edc 100644 --- a/core/crypto/aes/aes_impl_hw_gen.odin +++ b/core/crypto/aes/aes_impl_hw_gen.odin @@ -1,4 +1,4 @@ -//+build !amd64 +#+build !amd64 package aes @(private = "file") diff --git a/core/crypto/aes/aes_impl_hw_intel.odin b/core/crypto/aes/aes_impl_hw_intel.odin index 39ea2dc8d..0f1fa6143 100644 --- a/core/crypto/aes/aes_impl_hw_intel.odin +++ b/core/crypto/aes/aes_impl_hw_intel.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package aes import "core:crypto/_aes/hw_intel" diff --git a/core/crypto/blake2b/blake2b.odin b/core/crypto/blake2b/blake2b.odin index 384c2ffea..74396b103 100644 --- a/core/crypto/blake2b/blake2b.odin +++ b/core/crypto/blake2b/blake2b.odin @@ -2,8 +2,8 @@ package blake2b implements the BLAKE2b hash algorithm. See: -- https://datatracker.ietf.org/doc/html/rfc7693 -- https://www.blake2.net +- [[ https://datatracker.ietf.org/doc/html/rfc7693 ]] +- [[ https://www.blake2.net ]] */ package blake2b diff --git a/core/crypto/blake2s/blake2s.odin b/core/crypto/blake2s/blake2s.odin index 1ba9bef2d..339ddf027 100644 --- a/core/crypto/blake2s/blake2s.odin +++ b/core/crypto/blake2s/blake2s.odin @@ -2,8 +2,8 @@ package blake2s implements the BLAKE2s hash algorithm. See: -- https://datatracker.ietf.org/doc/html/rfc7693 -- https://www.blake2.net/ +- [[ https://datatracker.ietf.org/doc/html/rfc7693 ]] +- [[ https://www.blake2.net/ ]] */ package blake2s diff --git a/core/crypto/chacha20/chacha20.odin b/core/crypto/chacha20/chacha20.odin index 6d1a6bfc5..dfab2bc65 100644 --- a/core/crypto/chacha20/chacha20.odin +++ b/core/crypto/chacha20/chacha20.odin @@ -2,8 +2,8 @@ package chacha20 implements the ChaCha20 and XChaCha20 stream ciphers. See: -- https://datatracker.ietf.org/doc/html/rfc8439 -- https://datatracker.ietf.org/doc/draft-irtf-cfrg-xchacha/03/ +- [[ https://datatracker.ietf.org/doc/html/rfc8439 ]] +- [[ https://datatracker.ietf.org/doc/draft-irtf-cfrg-xchacha/03/ ]] */ package chacha20 diff --git a/core/crypto/chacha20poly1305/chacha20poly1305.odin b/core/crypto/chacha20poly1305/chacha20poly1305.odin index e2cd35a7e..3de2532dd 100644 --- a/core/crypto/chacha20poly1305/chacha20poly1305.odin +++ b/core/crypto/chacha20poly1305/chacha20poly1305.odin @@ -4,8 +4,8 @@ AEAD_XChaCha20_Poly1305 Authenticated Encryption with Additional Data algorithms. See: -- https://www.rfc-editor.org/rfc/rfc8439 -- https://datatracker.ietf.org/doc/html/draft-arciszewski-xchacha-03 +- [[ https://www.rfc-editor.org/rfc/rfc8439 ]] +- [[ https://datatracker.ietf.org/doc/html/draft-arciszewski-xchacha-03 ]] */ package chacha20poly1305 diff --git a/core/crypto/ed25519/ed25519.odin b/core/crypto/ed25519/ed25519.odin index 5584b06f7..460a19563 100644 --- a/core/crypto/ed25519/ed25519.odin +++ b/core/crypto/ed25519/ed25519.odin @@ -2,9 +2,9 @@ package ed25519 implements the Ed25519 EdDSA signature algorithm. See: -- https://datatracker.ietf.org/doc/html/rfc8032 -- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf -- https://eprint.iacr.org/2020/1244.pdf +- [[ https://datatracker.ietf.org/doc/html/rfc8032 ]] +- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf ]] +- [[ https://eprint.iacr.org/2020/1244.pdf ]] */ package ed25519 diff --git a/core/crypto/hash/doc.odin b/core/crypto/hash/doc.odin index d50908b94..1dfd97de2 100644 --- a/core/crypto/hash/doc.odin +++ b/core/crypto/hash/doc.odin @@ -17,46 +17,44 @@ accomplish common tasks. A third optional boolean parameter controls if the file is streamed (default), or or read at once. -```odin -package hash_example +Example: + package hash_example -import "core:crypto/hash" + import "core:crypto/hash" -main :: proc() { - input := "Feed the fire." + main :: proc() { + input := "Feed the fire." - // Compute the digest, using the high level API. - returned_digest := hash.hash(hash.Algorithm.SHA512_256, input) - defer delete(returned_digest) + // Compute the digest, using the high level API. + returned_digest := hash.hash(hash.Algorithm.SHA512_256, input) + defer delete(returned_digest) - // Variant that takes a destination buffer, instead of returning - // the digest. - digest := make([]byte, hash.DIGEST_SIZES[hash.Algorithm.BLAKE2B]) // @note: Destination buffer has to be at least as big as the digest size of the hash. - defer delete(digest) - hash.hash(hash.Algorithm.BLAKE2B, input, digest) -} -``` + // Variant that takes a destination buffer, instead of returning + // the digest. + digest := make([]byte, hash.DIGEST_SIZES[hash.Algorithm.BLAKE2B]) // @note: Destination buffer has to be at least as big as the digest size of the hash. + defer delete(digest) + hash.hash(hash.Algorithm.BLAKE2B, input, digest) + } A generic low level API is provided supporting the init/update/final interface that is typical with cryptographic hash function implementations. -```odin -package hash_example +Example: + package hash_example -import "core:crypto/hash" + import "core:crypto/hash" -main :: proc() { - input := "Let the cinders burn." + main :: proc() { + input := "Let the cinders burn." - // Compute the digest, using the low level API. - ctx: hash.Context - digest := make([]byte, hash.DIGEST_SIZES[hash.Algorithm.SHA3_512]) - defer delete(digest) + // Compute the digest, using the low level API. + ctx: hash.Context + digest := make([]byte, hash.DIGEST_SIZES[hash.Algorithm.SHA3_512]) + defer delete(digest) - hash.init(&ctx, hash.Algorithm.SHA3_512) - hash.update(&ctx, transmute([]byte)input) - hash.final(&ctx, digest) -} -``` + hash.init(&ctx, hash.Algorithm.SHA3_512) + hash.update(&ctx, transmute([]byte)input) + hash.final(&ctx, digest) + } */ -package crypto_hash
\ No newline at end of file +package crypto_hash diff --git a/core/crypto/hash/hash.odin b/core/crypto/hash/hash.odin index f7671270a..d47f0ab46 100644 --- a/core/crypto/hash/hash.odin +++ b/core/crypto/hash/hash.odin @@ -1,16 +1,15 @@ package crypto_hash /* - Copyright 2021 zhibog - Made available under the BSD-3 license. + Copyright 2021 zhibog + Made available under the BSD-3 license. - List of contributors: - zhibog, dotbmp: Initial implementation. + List of contributors: + zhibog, dotbmp: Initial implementation. */ import "core:io" import "core:mem" -import "core:os" // hash_bytes will hash the given input and return the computed digest // in a newly allocated slice. @@ -87,36 +86,3 @@ hash_stream :: proc( return dst, io.Error.None } - -// hash_file will read the file provided by the given handle and return the -// computed digest in a newly allocated slice. -hash_file :: proc( - algorithm: Algorithm, - hd: os.Handle, - load_at_once := false, - allocator := context.allocator, -) -> ( - []byte, - io.Error, -) { - if !load_at_once { - return hash_stream(algorithm, os.stream_from_handle(hd), allocator) - } - - buf, ok := os.read_entire_file(hd, allocator) - if !ok { - return nil, io.Error.Unknown - } - defer delete(buf, allocator) - - return hash_bytes(algorithm, buf, allocator), io.Error.None -} - -hash :: proc { - hash_stream, - hash_file, - hash_bytes, - hash_string, - hash_bytes_to_buffer, - hash_string_to_buffer, -} diff --git a/core/crypto/hash/hash_freestanding.odin b/core/crypto/hash/hash_freestanding.odin new file mode 100644 index 000000000..bec3c4eee --- /dev/null +++ b/core/crypto/hash/hash_freestanding.odin @@ -0,0 +1,10 @@ +#+build freestanding +package crypto_hash + +hash :: proc { + hash_stream, + hash_bytes, + hash_string, + hash_bytes_to_buffer, + hash_string_to_buffer, +} diff --git a/core/crypto/hash/hash_os.odin b/core/crypto/hash/hash_os.odin new file mode 100644 index 000000000..d54e657ad --- /dev/null +++ b/core/crypto/hash/hash_os.odin @@ -0,0 +1,38 @@ +#+build !freestanding +package crypto_hash + +import "core:io" +import "core:os" + +// hash_file will read the file provided by the given handle and return the +// computed digest in a newly allocated slice. +hash_file :: proc( + algorithm: Algorithm, + hd: os.Handle, + load_at_once := false, + allocator := context.allocator, +) -> ( + []byte, + io.Error, +) { + if !load_at_once { + return hash_stream(algorithm, os.stream_from_handle(hd), allocator) + } + + buf, ok := os.read_entire_file(hd, allocator) + if !ok { + return nil, io.Error.Unknown + } + defer delete(buf, allocator) + + return hash_bytes(algorithm, buf, allocator), io.Error.None +} + +hash :: proc { + hash_stream, + hash_file, + hash_bytes, + hash_string, + hash_bytes_to_buffer, + hash_string_to_buffer, +} diff --git a/core/crypto/hkdf/hkdf.odin b/core/crypto/hkdf/hkdf.odin index 2ac67476e..bffe09eff 100644 --- a/core/crypto/hkdf/hkdf.odin +++ b/core/crypto/hkdf/hkdf.odin @@ -2,7 +2,7 @@ package hkdf implements the HKDF HMAC-based Extract-and-Expand Key Derivation Function. -See: https://www.rfc-editor.org/rfc/rfc5869 +See: [[ https://www.rfc-editor.org/rfc/rfc5869 ]] */ package hkdf diff --git a/core/crypto/hmac/hmac.odin b/core/crypto/hmac/hmac.odin index 6aac8fca7..4813a9938 100644 --- a/core/crypto/hmac/hmac.odin +++ b/core/crypto/hmac/hmac.odin @@ -2,7 +2,7 @@ package hmac implements the HMAC MAC algorithm. See: -- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.198-1.pdf +- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.198-1.pdf ]] */ package hmac diff --git a/core/crypto/kmac/kmac.odin b/core/crypto/kmac/kmac.odin index 711f459b3..e8bf42946 100644 --- a/core/crypto/kmac/kmac.odin +++ b/core/crypto/kmac/kmac.odin @@ -2,7 +2,7 @@ package kmac implements the KMAC MAC algorithm. See: -- https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf +- [[ https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf ]] */ package kmac diff --git a/core/crypto/legacy/md5/md5.odin b/core/crypto/legacy/md5/md5.odin index c744a9bcf..28b47e0b3 100644 --- a/core/crypto/legacy/md5/md5.odin +++ b/core/crypto/legacy/md5/md5.odin @@ -5,8 +5,8 @@ WARNING: The MD5 algorithm is known to be insecure and should only be used for interoperating with legacy applications. See: -- https://eprint.iacr.org/2005/075 -- https://datatracker.ietf.org/doc/html/rfc1321 +- [[ https://eprint.iacr.org/2005/075 ]] +- [[ https://datatracker.ietf.org/doc/html/rfc1321 ]] */ package md5 diff --git a/core/crypto/legacy/sha1/sha1.odin b/core/crypto/legacy/sha1/sha1.odin index 8c6e59901..1025ecb5b 100644 --- a/core/crypto/legacy/sha1/sha1.odin +++ b/core/crypto/legacy/sha1/sha1.odin @@ -5,9 +5,9 @@ WARNING: The SHA1 algorithm is known to be insecure and should only be used for interoperating with legacy applications. See: -- https://eprint.iacr.org/2017/190 -- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf -- https://datatracker.ietf.org/doc/html/rfc3174 +- [[ https://eprint.iacr.org/2017/190 ]] +- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf ]] +- [[ https://datatracker.ietf.org/doc/html/rfc3174 ]] */ package sha1 diff --git a/core/crypto/pbkdf2/pbkdf2.odin b/core/crypto/pbkdf2/pbkdf2.odin index 20e490135..8bb5cb73e 100644 --- a/core/crypto/pbkdf2/pbkdf2.odin +++ b/core/crypto/pbkdf2/pbkdf2.odin @@ -1,7 +1,7 @@ /* package pbkdf2 implements the PBKDF2 password-based key derivation function. -See: https://www.rfc-editor.org/rfc/rfc2898 +See: [[ https://www.rfc-editor.org/rfc/rfc2898 ]] */ package pbkdf2 diff --git a/core/crypto/poly1305/poly1305.odin b/core/crypto/poly1305/poly1305.odin index 443917a6a..ea0e6c907 100644 --- a/core/crypto/poly1305/poly1305.odin +++ b/core/crypto/poly1305/poly1305.odin @@ -2,7 +2,7 @@ package poly1305 implements the Poly1305 one-time MAC algorithm. See: -- https://datatracker.ietf.org/doc/html/rfc8439 +- [[ https://datatracker.ietf.org/doc/html/rfc8439 ]] */ package poly1305 diff --git a/core/crypto/rand_bsd.odin b/core/crypto/rand_bsd.odin index 641b72933..78a6fcaaf 100644 --- a/core/crypto/rand_bsd.odin +++ b/core/crypto/rand_bsd.odin @@ -1,4 +1,4 @@ -//+build freebsd, openbsd, netbsd +#+build freebsd, openbsd, netbsd package crypto foreign import libc "system:c" diff --git a/core/crypto/rand_generic.odin b/core/crypto/rand_generic.odin index 46fb881b3..ef578f5c0 100644 --- a/core/crypto/rand_generic.odin +++ b/core/crypto/rand_generic.odin @@ -1,10 +1,10 @@ -//+build !linux -//+build !windows -//+build !openbsd -//+build !freebsd -//+build !netbsd -//+build !darwin -//+build !js +#+build !linux +#+build !windows +#+build !openbsd +#+build !freebsd +#+build !netbsd +#+build !darwin +#+build !js package crypto HAS_RAND_BYTES :: false diff --git a/core/crypto/ristretto255/ristretto255.odin b/core/crypto/ristretto255/ristretto255.odin index 3a2307da0..7b0944e33 100644 --- a/core/crypto/ristretto255/ristretto255.odin +++ b/core/crypto/ristretto255/ristretto255.odin @@ -2,7 +2,7 @@ package ristretto255 implement the ristretto255 prime-order group. See: -- https://www.rfc-editor.org/rfc/rfc9496 +- [[ https://www.rfc-editor.org/rfc/rfc9496 ]] */ package ristretto255 diff --git a/core/crypto/sha2/sha2.odin b/core/crypto/sha2/sha2.odin index 2128e3950..4230851ab 100644 --- a/core/crypto/sha2/sha2.odin +++ b/core/crypto/sha2/sha2.odin @@ -2,8 +2,8 @@ package sha2 implements the SHA2 hash algorithm family. See: -- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf -- https://datatracker.ietf.org/doc/html/rfc3874 +- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf ]] +- [[ https://datatracker.ietf.org/doc/html/rfc3874 ]] */ package sha2 diff --git a/core/crypto/sha3/sha3.odin b/core/crypto/sha3/sha3.odin index 78057f1ca..3b7bdedd7 100644 --- a/core/crypto/sha3/sha3.odin +++ b/core/crypto/sha3/sha3.odin @@ -6,7 +6,7 @@ pre-standardization Keccak algorithm is required, it can be found in crypto/legacy/keccak. See: -- https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.202.pdf +- [[ https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.202.pdf ]] */ package sha3 diff --git a/core/crypto/shake/shake.odin b/core/crypto/shake/shake.odin index 4160f4cf9..e20795b43 100644 --- a/core/crypto/shake/shake.odin +++ b/core/crypto/shake/shake.odin @@ -4,8 +4,8 @@ package shake implements the SHAKE and cSHAKE XOF algorithm families. The SHA3 hash algorithm can be found in the crypto/sha3. See: -- https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.202.pdf -- https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf +- [[ https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.202.pdf ]] +- [[ https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf ]] */ package shake diff --git a/core/crypto/siphash/siphash.odin b/core/crypto/siphash/siphash.odin index c51e38ab0..c145ab3f0 100644 --- a/core/crypto/siphash/siphash.odin +++ b/core/crypto/siphash/siphash.odin @@ -1,3 +1,12 @@ +/* +package siphash Implements the SipHash hashing algorithm. + +Use the specific procedures for a certain setup. The generic procedures will default to Siphash 2-4. + +See: +- [[ https://github.com/veorq/SipHash ]] +- [[ https://www.aumasson.jp/siphash/siphash.pdf ]] +*/ package siphash /* @@ -6,10 +15,6 @@ package siphash List of contributors: zhibog: Initial implementation. - - Implementation of the SipHash hashing algorithm, as defined at <https://github.com/veorq/SipHash> and <https://www.aumasson.jp/siphash/siphash.pdf> - - Use the specific procedures for a certain setup. The generic procdedures will default to Siphash 2-4 */ import "core:crypto" diff --git a/core/crypto/sm3/sm3.odin b/core/crypto/sm3/sm3.odin index 2faf37380..f910d735b 100644 --- a/core/crypto/sm3/sm3.odin +++ b/core/crypto/sm3/sm3.odin @@ -2,7 +2,7 @@ package sm3 implements the SM3 hash algorithm. See: -- https://datatracker.ietf.org/doc/html/draft-sca-cfrg-sm3-02 +- [[ https://datatracker.ietf.org/doc/html/draft-sca-cfrg-sm3-02 ]] */ package sm3 diff --git a/core/crypto/tuplehash/tuplehash.odin b/core/crypto/tuplehash/tuplehash.odin index ed0a3aa87..e5caaa9c9 100644 --- a/core/crypto/tuplehash/tuplehash.odin +++ b/core/crypto/tuplehash/tuplehash.odin @@ -2,7 +2,7 @@ package tuplehash implements the TupleHash and TupleHashXOF algorithms. See: -- https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf +- [[ https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf ]] */ package tuplehash diff --git a/core/crypto/x25519/x25519.odin b/core/crypto/x25519/x25519.odin index f8a301810..412a767b8 100644 --- a/core/crypto/x25519/x25519.odin +++ b/core/crypto/x25519/x25519.odin @@ -3,7 +3,7 @@ package x25519 implements the X25519 (aka curve25519) Elliptic-Curve Diffie-Hellman key exchange protocol. See: -- https://www.rfc-editor.org/rfc/rfc7748 +- [[ https://www.rfc-editor.org/rfc/rfc7748 ]] */ package x25519 diff --git a/core/debug/trace/trace_cpp.odin b/core/debug/trace/trace_cpp.odin index dc723184a..8ef377cef 100644 --- a/core/debug/trace/trace_cpp.odin +++ b/core/debug/trace/trace_cpp.odin @@ -1,5 +1,5 @@ -//+private file -//+build linux, darwin +#+private file +#+build linux, darwin package debug_trace import "base:intrinsics" diff --git a/core/debug/trace/trace_nil.odin b/core/debug/trace/trace_nil.odin index 8611d7726..8ee96720e 100644 --- a/core/debug/trace/trace_nil.odin +++ b/core/debug/trace/trace_nil.odin @@ -1,4 +1,6 @@ -//+build !windows !linux !darwin +#+build !windows +#+build !linux +#+build !darwin package debug_trace import "base:runtime" diff --git a/core/debug/trace/trace_windows.odin b/core/debug/trace/trace_windows.odin index de1461e96..c9868e338 100644 --- a/core/debug/trace/trace_windows.odin +++ b/core/debug/trace/trace_windows.odin @@ -1,5 +1,5 @@ -//+private -//+build windows +#+private +#+build windows package debug_trace import "base:intrinsics" diff --git a/core/dynlib/doc.odin b/core/dynlib/doc.odin index f5c91c54e..487fcb715 100644 --- a/core/dynlib/doc.odin +++ b/core/dynlib/doc.odin @@ -4,7 +4,6 @@ Package `core:dynlib` implements loading of shared libraries/DLLs and their symb The behaviour of dynamically loaded libraries is specific to the target platform of the program. For in depth detail on the underlying behaviour please refer to your target platform's documentation. -See `example` directory for an example library exporting 3 symbols and a host program loading them automatically -by defining a symbol table struct. +For a full example, see: [[ core/dynlib/example; https://github.com/odin-lang/Odin/tree/master/core/dynlib/example ]] */ package dynlib diff --git a/core/dynlib/lib_js.odin b/core/dynlib/lib_js.odin index bfc724c12..698cfee9c 100644 --- a/core/dynlib/lib_js.odin +++ b/core/dynlib/lib_js.odin @@ -1,5 +1,5 @@ -//+build js -//+private +#+build js +#+private package dynlib _load_library :: proc(path: string, global_symbols := false) -> (Library, bool) { diff --git a/core/dynlib/lib_unix.odin b/core/dynlib/lib_unix.odin index 8adaadb2d..f467d730d 100644 --- a/core/dynlib/lib_unix.odin +++ b/core/dynlib/lib_unix.odin @@ -1,5 +1,5 @@ -//+build linux, darwin, freebsd, openbsd, netbsd -//+private +#+build linux, darwin, freebsd, openbsd, netbsd +#+private package dynlib import "core:os" diff --git a/core/dynlib/lib_windows.odin b/core/dynlib/lib_windows.odin index b41abe3b2..6c41a1a75 100644 --- a/core/dynlib/lib_windows.odin +++ b/core/dynlib/lib_windows.odin @@ -1,5 +1,5 @@ -//+build windows -//+private +#+build windows +#+private package dynlib import win32 "core:sys/windows" diff --git a/core/encoding/ansi/doc.odin b/core/encoding/ansi/doc.odin index a0945c581..966e6be00 100644 --- a/core/encoding/ansi/doc.odin +++ b/core/encoding/ansi/doc.odin @@ -13,8 +13,8 @@ If your terminal supports 24-bit true color mode, you can also do this: fmt.println(ansi.CSI + ansi.FG_COLOR_24_BIT + ";0;255;255" + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR) For more information, see: - 1. https://en.wikipedia.org/wiki/ANSI_escape_code - 2. https://www.vt100.net/docs/vt102-ug/chapter5.html - 3. https://invisible-island.net/xterm/ctlseqs/ctlseqs.html +- [[ https://en.wikipedia.org/wiki/ANSI_escape_code ]] +- [[ https://www.vt100.net/docs/vt102-ug/chapter5.html ]] +- [[ https://invisible-island.net/xterm/ctlseqs/ctlseqs.html ]] */ package ansi diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index c54660839..bf27171f4 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -675,10 +675,6 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, return case reflect.Type_Info_Map: - if !reflect.is_string(t.key) { - return _unsupported(v, hdr) - } - raw_map := (^mem.Raw_Map)(v.data) if raw_map.allocator.procedure == nil { raw_map.allocator = context.allocator @@ -695,43 +691,31 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, new_len := uintptr(min(scap, runtime.map_len(raw_map^)+length)) runtime.map_reserve_dynamic(raw_map, t.map_info, new_len) or_return } - - // Temporary memory to unmarshal keys into before inserting them into the map. + + // Temporary memory to unmarshal values into before inserting them into the map. elem_backing := mem.alloc_bytes_non_zeroed(t.value.size, t.value.align, context.temp_allocator) or_return defer delete(elem_backing, context.temp_allocator) - map_backing_value := any{raw_data(elem_backing), t.value.id} - for idx := 0; unknown || idx < length; idx += 1 { - // Decode key, keys can only be strings. - key: string - if keyv, kerr := decode_key(d, v); unknown && kerr == .Break { - break - } else if kerr != nil { - err = kerr - return - } else { - key = keyv - } + // Temporary memory to unmarshal keys into. + key_backing := mem.alloc_bytes_non_zeroed(t.key.size, t.key.align, context.temp_allocator) or_return + defer delete(key_backing, context.temp_allocator) + key_backing_value := any{raw_data(key_backing), t.key.id} + for idx := 0; unknown || idx < length; idx += 1 { if unknown || idx > scap { // Reserve space for new element so we can return allocator errors. new_len := uintptr(runtime.map_len(raw_map^)+1) runtime.map_reserve_dynamic(raw_map, t.map_info, new_len) or_return } + mem.zero_slice(key_backing) + _unmarshal_value(d, key_backing_value, _decode_header(r) or_return) or_return + mem.zero_slice(elem_backing) _unmarshal_value(d, map_backing_value, _decode_header(r) or_return) or_return - key_ptr := rawptr(&key) - key_cstr: cstring - if reflect.is_cstring(t.key) { - assert_safe_for_cstring(key) - key_cstr = cstring(raw_data(key)) - key_ptr = &key_cstr - } - - set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_ptr, map_backing_value.data) + set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_backing_value.data, map_backing_value.data) // We already reserved space for it, so this shouldn't fail. assert(set_ptr != nil) } diff --git a/core/encoding/csv/doc.odin b/core/encoding/csv/doc.odin new file mode 100644 index 000000000..bfeadafd6 --- /dev/null +++ b/core/encoding/csv/doc.odin @@ -0,0 +1,96 @@ +/* +package csv reads and writes comma-separated values (CSV) files. +This package supports the format described in [[ RFC 4180; https://tools.ietf.org/html/rfc4180.html ]] + +Example: + package main + + import "core:fmt" + import "core:encoding/csv" + import "core:os" + + // Requires keeping the entire CSV file in memory at once + iterate_csv_from_string :: proc(filename: string) { + r: csv.Reader + r.trim_leading_space = true + r.reuse_record = true // Without it you have to delete(record) + r.reuse_record_buffer = true // Without it you have to each of the fields within it + defer csv.reader_destroy(&r) + + csv_data, ok := os.read_entire_file(filename) + if ok { + csv.reader_init_with_string(&r, string(csv_data)) + } else { + fmt.printfln("Unable to open file: %v", filename) + return + } + defer delete(csv_data) + + for r, i, err in csv.iterator_next(&r) { + if err != nil { /* Do something with error */ } + for f, j in r { + fmt.printfln("Record %v, field %v: %q", i, j, f) + } + } + } + + // Reads the CSV as it's processed (with a small buffer) + iterate_csv_from_stream :: proc(filename: string) { + fmt.printfln("Hellope from %v", filename) + r: csv.Reader + r.trim_leading_space = true + r.reuse_record = true // Without it you have to delete(record) + r.reuse_record_buffer = true // Without it you have to each of the fields within it + defer csv.reader_destroy(&r) + + handle, err := os.open(filename) + if err != nil { + fmt.eprintfln("Error opening file: %v", filename) + return + } + defer os.close(handle) + csv.reader_init(&r, os.stream_from_handle(handle)) + + for r, i in csv.iterator_next(&r) { + for f, j in r { + fmt.printfln("Record %v, field %v: %q", i, j, f) + } + } + fmt.printfln("Error: %v", csv.iterator_last_error(r)) + } + + // Read all records at once + read_csv_from_string :: proc(filename: string) { + r: csv.Reader + r.trim_leading_space = true + r.reuse_record = true // Without it you have to delete(record) + r.reuse_record_buffer = true // Without it you have to each of the fields within it + defer csv.reader_destroy(&r) + + csv_data, ok := os.read_entire_file(filename) + if ok { + csv.reader_init_with_string(&r, string(csv_data)) + } else { + fmt.printfln("Unable to open file: %v", filename) + return + } + defer delete(csv_data) + + records, err := csv.read_all(&r) + if err != nil { /* Do something with CSV parse error */ } + + defer { + for rec in records { + delete(rec) + } + delete(records) + } + + for r, i in records { + for f, j in r { + fmt.printfln("Record %v, field %v: %q", i, j, f) + } + } + } +*/ +package encoding_csv diff --git a/core/encoding/csv/example.odin b/core/encoding/csv/example.odin deleted file mode 100644 index d791eb33b..000000000 --- a/core/encoding/csv/example.odin +++ /dev/null @@ -1,88 +0,0 @@ -//+build ignore -package encoding_csv - -import "core:fmt" -import "core:encoding/csv" -import "core:os" - -// Requires keeping the entire CSV file in memory at once -iterate_csv_from_string :: proc(filename: string) { - r: csv.Reader - r.trim_leading_space = true - r.reuse_record = true // Without it you have to delete(record) - r.reuse_record_buffer = true // Without it you have to each of the fields within it - defer csv.reader_destroy(&r) - - if csv_data, ok := os.read_entire_file(filename); ok { - csv.reader_init_with_string(&r, string(csv_data)) - defer delete(csv_data) - } else { - fmt.printfln("Unable to open file: %v", filename) - return - } - - for r, i, err in csv.iterator_next(&r) { - if err != nil { /* Do something with error */ } - for f, j in r { - fmt.printfln("Record %v, field %v: %q", i, j, f) - } - } -} - -// Reads the CSV as it's processed (with a small buffer) -iterate_csv_from_stream :: proc(filename: string) { - fmt.printfln("Hellope from %v", filename) - r: csv.Reader - r.trim_leading_space = true - r.reuse_record = true // Without it you have to delete(record) - r.reuse_record_buffer = true // Without it you have to each of the fields within it - defer csv.reader_destroy(&r) - - handle, err := os.open(filename) - if err != nil { - fmt.eprintfln("Error opening file: %v", filename) - return - } - defer os.close(handle) - csv.reader_init(&r, os.stream_from_handle(handle)) - - for r, i in csv.iterator_next(&r) { - for f, j in r { - fmt.printfln("Record %v, field %v: %q", i, j, f) - } - } - fmt.printfln("Error: %v", csv.iterator_last_error(r)) -} - -// Read all records at once -read_csv_from_string :: proc(filename: string) { - r: csv.Reader - r.trim_leading_space = true - r.reuse_record = true // Without it you have to delete(record) - r.reuse_record_buffer = true // Without it you have to each of the fields within it - defer csv.reader_destroy(&r) - - if csv_data, ok := os.read_entire_file(filename); ok { - csv.reader_init_with_string(&r, string(csv_data)) - defer delete(csv_data) - } else { - fmt.printfln("Unable to open file: %v", filename) - return - } - - records, err := csv.read_all(&r) - if err != nil { /* Do something with CSV parse error */ } - - defer { - for rec in records { - delete(rec) - } - delete(records) - } - - for r, i in records { - for f, j in r { - fmt.printfln("Record %v, field %v: %q", i, j, f) - } - } -}
\ No newline at end of file diff --git a/core/encoding/csv/reader.odin b/core/encoding/csv/reader.odin index ebc7b39a0..5348624d5 100644 --- a/core/encoding/csv/reader.odin +++ b/core/encoding/csv/reader.odin @@ -1,5 +1,5 @@ // package csv reads and writes comma-separated values (CSV) files. -// This package supports the format described in RFC 4180 <https://tools.ietf.org/html/rfc4180.html> +// This package supports the format described in [[ RFC 4180; https://tools.ietf.org/html/rfc4180.html ]] package encoding_csv import "core:bufio" @@ -484,4 +484,4 @@ _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.all r.fields_per_record = len(dst) } return dst[:], err -}
\ No newline at end of file +} diff --git a/core/encoding/endian/doc.odin b/core/encoding/endian/doc.odin index 8ebefd0a4..0b43e3097 100644 --- a/core/encoding/endian/doc.odin +++ b/core/encoding/endian/doc.odin @@ -2,22 +2,23 @@ Package endian implements a simple translation between bytes and numbers with specific endian encodings. - buf: [100]u8 - put_u16(buf[:], .Little, 16) or_return +Example: + buf: [100]u8 + put_u16(buf[:], .Little, 16) or_return - You may ask yourself, why isn't `byte_order` platform Endianness by default, so we can write: - put_u16(buf[:], 16) or_return + // You may ask yourself, why isn't `byte_order` platform Endianness by default, so we can write: + put_u16(buf[:], 16) or_return - The answer is that very few file formats are written in native/platform endianness. Most of them specify the endianness of - each of their fields, or use a header field which specifies it for the entire file. + // The answer is that very few file formats are written in native/platform endianness. Most of them specify the endianness of + // each of their fields, or use a header field which specifies it for the entire file. - e.g. a file which specifies it at the top for all fields could do this: - file_order := .Little if buf[0] == 0 else .Big - field := get_u16(buf[1:], file_order) or_return + // e.g. a file which specifies it at the top for all fields could do this: + file_order := .Little if buf[0] == 0 else .Big + field := get_u16(buf[1:], file_order) or_return - If on the other hand a field is *always* Big-Endian, you're wise to explicitly state it for the benefit of the reader, - be that your future self or someone else. + // If on the other hand a field is *always* Big-Endian, you're wise to explicitly state it for the benefit of the reader, + // be that your future self or someone else. - field := get_u16(buf[:], .Big) or_return + field := get_u16(buf[:], .Big) or_return */ package encoding_endian diff --git a/core/encoding/entity/entity.odin b/core/encoding/entity/entity.odin index f5208ad6f..d2f1d46b2 100644 --- a/core/encoding/entity/entity.odin +++ b/core/encoding/entity/entity.odin @@ -1,23 +1,25 @@ -package encoding_unicode_entity /* - A unicode entity encoder/decoder - Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. Made available under Odin's BSD-3 license. + List of contributors: + Jeroen van Rijn: Initial implementation. +*/ + +/* + A unicode entity encoder/decoder. + This code has several procedures to map unicode runes to/from different textual encodings. - SGML/XML/HTML entity - -- &#<decimal>; - -- &#x<hexadecimal>; - -- &<entity name>; (If the lookup tables are compiled in). - Reference: https://www.w3.org/2003/entities/2007xml/unicode.xml + - &#<decimal>; + - &#x<hexadecimal>; + - &<entity name>; (If the lookup tables are compiled in). + Reference: [[ https://www.w3.org/2003/entities/2007xml/unicode.xml ]] - URL encode / decode %hex entity - Reference: https://datatracker.ietf.org/doc/html/rfc3986/#section-2.1 - - List of contributors: - Jeroen van Rijn: Initial implementation. + Reference: [[ https://datatracker.ietf.org/doc/html/rfc3986/#section-2.1 ]] */ +package encoding_unicode_entity import "core:unicode/utf8" import "core:unicode" @@ -353,4 +355,4 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X } return false, .None -}
\ No newline at end of file +} diff --git a/core/encoding/entity/generated.odin b/core/encoding/entity/generated.odin index 0c4742149..52027ae03 100644 --- a/core/encoding/entity/generated.odin +++ b/core/encoding/entity/generated.odin @@ -42,7 +42,7 @@ XML_NAME_TO_RUNE_MAX_LENGTH :: 31 Input: entity_name - a string, like "copy" that describes a user-encoded Unicode entity as used in XML. - Output: + Returns: "decoded" - The decoded rune if found by name, or -1 otherwise. "ok" - true if found, false if not. diff --git a/core/encoding/hxa/doc.odin b/core/encoding/hxa/doc.odin index 230d6ea66..b696bef7e 100644 --- a/core/encoding/hxa/doc.odin +++ b/core/encoding/hxa/doc.odin @@ -1,83 +1,89 @@ -// Implementation of the HxA 3D asset format -// HxA is a interchangeable graphics asset format. -// Designed by Eskil Steenberg. @quelsolaar / eskil 'at' obsession 'dot' se / www.quelsolaar.com -// -// Author of this Odin package: Ginger Bill -// -// Following comment is copied from the original C-implementation -// --------- -// -Does the world need another Graphics file format? -// Unfortunately, Yes. All existing formats are either too large and complicated to be implemented from -// scratch, or don't have some basic features needed in modern computer graphics. -// -Who is this format for? -// For people who want a capable open Graphics format that can be implemented from scratch in -// a few hours. It is ideal for graphics researchers, game developers or other people who -// wants to build custom graphics pipelines. Given how easy it is to parse and write, it -// should be easy to write utilities that process assets to preform tasks like: generating -// normals, light-maps, tangent spaces, Error detection, GPU optimization, LOD generation, -// and UV mapping. -// -Why store images in the format when there are so many good image formats already? -// Yes there are, but only for 2D RGB/RGBA images. A lot of computer graphics rendering rely -// on 1D, 3D, cube, multilayer, multi channel, floating point bitmap buffers. There almost no -// formats for this kind of data. Also 3D files that reference separate image files rely on -// file paths, and this often creates issues when the assets are moved. By including the -// texture data in the files directly the assets become self contained. -// -Why doesn't the format support <insert whatever>? -// Because the entire point is to make a format that can be implemented. Features like NURBSs, -// Construction history, or BSP trees would make the format too large to serve its purpose. -// The facilities of the formats to store meta data should make the format flexible enough -// for most uses. Adding HxA support should be something anyone can do in a days work. -// -// Structure: -// ---------- -// HxA is designed to be extremely simple to parse, and is therefore based around conventions. It has -// a few basic structures, and depending on how they are used they mean different things. This means -// that you can implement a tool that loads the entire file, modifies the parts it cares about and -// leaves the rest intact. It is also possible to write a tool that makes all data in the file -// editable without the need to understand its use. It is also possible for anyone to use the format -// to store data axillary data. Anyone who wants to store data not covered by a convention can submit -// a convention to extend the format. There should never be a convention for storing the same data in -// two differed ways. -// The data is story in a number of nodes that are stored in an array. Each node stores an array of -// meta data. Meta data can describe anything you want, and a lot of conventions will use meta data -// to store additional information, for things like transforms, lights, shaders and animation. -// Data for Vertices, Corners, Faces, and Pixels are stored in named layer stacks. Each stack consists -// of a number of named layers. All layers in the stack have the same number of elements. Each layer -// describes one property of the primitive. Each layer can have multiple channels and each layer can -// store data of a different type. -// -// HaX stores 3 kinds of nodes -// - Pixel data. -// - Polygon geometry data. -// - Meta data only. -// -// Pixel Nodes stores pixels in a layer stack. A layer may store things like Albedo, Roughness, -// Reflectance, Light maps, Masks, Normal maps, and Displacement. Layers use the channels of the -// layers to store things like color. The length of the layer stack is determined by the type and -// dimensions stored in the -// -// Geometry data is stored in 3 separate layer stacks for: vertex data, corner data and face data. The -// vertex data stores things like verities, blend shapes, weight maps, and vertex colors. The first -// layer in a vertex stack has to be a 3 channel layer named "position" describing the base position -// of the vertices. The corner stack describes data per corner or edge of the polygons. It can be used -// for things like UV, normals, and adjacency. The first layer in a corner stack has to be a 1 channel -// integer layer named "index" describing the vertices used to form polygons. The last value in each -// polygon has a negative - 1 index to indicate the end of the polygon. -// -// Example: -// A quad and a tri with the vertex index: -// [0, 1, 2, 3] [1, 4, 2] -// is stored: -// [0, 1, 2, -4, 1, 4, -3] -// The face stack stores values per face. the length of the face stack has to match the number of -// negative values in the index layer in the corner stack. The face stack can be used to store things -// like material index. -// -// Storage -// ------- -// All data is stored in little endian byte order with no padding. The layout mirrors the structs -// defined below with a few exceptions. All names are stored as a 8-bit unsigned integer indicating -// the length of the name followed by that many characters. Termination is not stored in the file. -// Text strings stored in meta data are stored the same way as names, but instead of a 8-bit unsigned -// integer a 32-bit unsigned integer is used. -package encoding_hxa
\ No newline at end of file +/* +Implementation of the HxA 3D asset format +HxA is a interchangeable graphics asset format. +Designed by Eskil Steenberg. @quelsolaar / eskil 'at' obsession 'dot' se / www.quelsolaar.com + +Author of this Odin package: Ginger Bill + +Following comment is copied from the original C-implementation +--------- +- Does the world need another Graphics file format? +Unfortunately, Yes. All existing formats are either too large and complicated to be implemented from +scratch, or don't have some basic features needed in modern computer graphics. + +- Who is this format for? +For people who want a capable open Graphics format that can be implemented from scratch in +a few hours. It is ideal for graphics researchers, game developers or other people who +wants to build custom graphics pipelines. Given how easy it is to parse and write, it +should be easy to write utilities that process assets to preform tasks like: generating +normals, light-maps, tangent spaces, Error detection, GPU optimization, LOD generation, +and UV mapping. + +- Why store images in the format when there are so many good image formats already? +Yes there are, but only for 2D RGB/RGBA images. A lot of computer graphics rendering rely +on 1D, 3D, cube, multilayer, multi channel, floating point bitmap buffers. There almost no +formats for this kind of data. Also 3D files that reference separate image files rely on +file paths, and this often creates issues when the assets are moved. By including the +texture data in the files directly the assets become self contained. + +- Why doesn't the format support <insert whatever>? +Because the entire point is to make a format that can be implemented. Features like NURBSs, +Construction history, or BSP trees would make the format too large to serve its purpose. +The facilities of the formats to store meta data should make the format flexible enough +for most uses. Adding HxA support should be something anyone can do in a days work. + +Structure: +---------- +HxA is designed to be extremely simple to parse, and is therefore based around conventions. It has +a few basic structures, and depending on how they are used they mean different things. This means +that you can implement a tool that loads the entire file, modifies the parts it cares about and +leaves the rest intact. It is also possible to write a tool that makes all data in the file +editable without the need to understand its use. It is also possible for anyone to use the format +to store data axillary data. Anyone who wants to store data not covered by a convention can submit +a convention to extend the format. There should never be a convention for storing the same data in +two differed ways. + +The data is story in a number of nodes that are stored in an array. Each node stores an array of +meta data. Meta data can describe anything you want, and a lot of conventions will use meta data +to store additional information, for things like transforms, lights, shaders and animation. +Data for Vertices, Corners, Faces, and Pixels are stored in named layer stacks. Each stack consists +of a number of named layers. All layers in the stack have the same number of elements. Each layer +describes one property of the primitive. Each layer can have multiple channels and each layer can +store data of a different type. + +HaX stores 3 kinds of nodes +- Pixel data. +- Polygon geometry data. +- Meta data only. + +Pixel Nodes stores pixels in a layer stack. A layer may store things like Albedo, Roughness, +Reflectance, Light maps, Masks, Normal maps, and Displacement. Layers use the channels of the +layers to store things like color. +The length of the layer stack is determined by the type and dimensions stored in the Geometry data +is stored in 3 separate layer stacks for: vertex data, corner data and face data. The +vertex data stores things like verities, blend shapes, weight maps, and vertex colors. The first +layer in a vertex stack has to be a 3 channel layer named "position" describing the base position +of the vertices. The corner stack describes data per corner or edge of the polygons. It can be used +for things like UV, normals, and adjacency. The first layer in a corner stack has to be a 1 channel +integer layer named "index" describing the vertices used to form polygons. The last value in each +polygon has a negative - 1 index to indicate the end of the polygon. + +For Example: + A quad and a tri with the vertex index: + [0, 1, 2, 3] [1, 4, 2] + is stored: + [0, 1, 2, -4, 1, 4, -3] + +The face stack stores values per face. the length of the face stack has to match the number of +negative values in the index layer in the corner stack. The face stack can be used to store things +like material index. + +Storage: +------- +All data is stored in little endian byte order with no padding. The layout mirrors the structs +defined below with a few exceptions. All names are stored as a 8-bit unsigned integer indicating +the length of the name followed by that many characters. Termination is not stored in the file. +Text strings stored in meta data are stored the same way as names, but instead of a 8-bit unsigned +integer a 32-bit unsigned integer is used. +*/ +package encoding_hxa diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index 127bce650..738e20c68 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -116,7 +116,30 @@ assign_int :: proc(val: any, i: $T) -> bool { case int: dst = int (i) case uint: dst = uint (i) case uintptr: dst = uintptr(i) - case: return false + case: + ti := type_info_of(v.id) + if _, ok := ti.variant.(runtime.Type_Info_Bit_Set); ok { + do_byte_swap := !reflect.bit_set_is_big_endian(v) + switch ti.size * 8 { + case 0: // no-op. + case 8: + x := (^u8)(v.data) + x^ = u8(i) + case 16: + x := (^u16)(v.data) + x^ = do_byte_swap ? intrinsics.byte_swap(u16(i)) : u16(i) + case 32: + x := (^u32)(v.data) + x^ = do_byte_swap ? intrinsics.byte_swap(u32(i)) : u32(i) + case 64: + x := (^u64)(v.data) + x^ = do_byte_swap ? intrinsics.byte_swap(u64(i)) : u64(i) + case: + panic("unknown bit_size size") + } + return true + } + return false } return true } diff --git a/core/encoding/uuid/doc.odin b/core/encoding/uuid/doc.odin index 6fa375b72..f910c33d8 100644 --- a/core/encoding/uuid/doc.odin +++ b/core/encoding/uuid/doc.odin @@ -21,8 +21,9 @@ cryptographically-secure, per RFC 9562's suggestion. - Version 6 without either a clock or node argument. - Version 7 in all cases. -Here's an example of how to set up one: - +Example: + package main + import "core:crypto" import "core:encoding/uuid" @@ -40,7 +41,7 @@ Here's an example of how to set up one: For more information on the specifications, see here: -- https://www.rfc-editor.org/rfc/rfc4122.html -- https://www.rfc-editor.org/rfc/rfc9562.html +- [[ https://www.rfc-editor.org/rfc/rfc4122.html ]] +- [[ https://www.rfc-editor.org/rfc/rfc9562.html ]] */ package uuid diff --git a/core/encoding/uuid/writing.odin b/core/encoding/uuid/writing.odin index 499cba72b..7acaa3cd7 100644 --- a/core/encoding/uuid/writing.odin +++ b/core/encoding/uuid/writing.odin @@ -11,7 +11,7 @@ Write a UUID in the 8-4-4-4-12 format. This procedure performs error checking with every byte written. If you can guarantee beforehand that your stream has enough space to hold the -UUID (32 bytes), then it is better to use `unsafe_write` instead as that will +UUID (36 bytes), then it is better to use `unsafe_write` instead as that will be faster. Inputs: @@ -22,7 +22,7 @@ Returns: - error: An `io` error, if one occurred, otherwise `nil`. */ write :: proc(w: io.Writer, id: Identifier) -> (error: io.Error) #no_bounds_check { - write_octet :: proc (w: io.Writer, octet: u8) -> io.Error #no_bounds_check { + write_octet :: proc(w: io.Writer, octet: u8) -> io.Error #no_bounds_check { high_nibble := octet >> 4 low_nibble := octet & 0xF @@ -31,15 +31,15 @@ write :: proc(w: io.Writer, id: Identifier) -> (error: io.Error) #no_bounds_chec return nil } - for index in 0 ..< 4 { write_octet(w, id[index]) or_return } + for index in 0 ..< 4 {write_octet(w, id[index]) or_return} io.write_byte(w, '-') or_return - for index in 4 ..< 6 { write_octet(w, id[index]) or_return } + for index in 4 ..< 6 {write_octet(w, id[index]) or_return} io.write_byte(w, '-') or_return - for index in 6 ..< 8 { write_octet(w, id[index]) or_return } + for index in 6 ..< 8 {write_octet(w, id[index]) or_return} io.write_byte(w, '-') or_return - for index in 8 ..< 10 { write_octet(w, id[index]) or_return } + for index in 8 ..< 10 {write_octet(w, id[index]) or_return} io.write_byte(w, '-') or_return - for index in 10 ..< 16 { write_octet(w, id[index]) or_return } + for index in 10 ..< 16 {write_octet(w, id[index]) or_return} return nil } @@ -54,7 +54,7 @@ Inputs: - id: The identifier to convert. */ unsafe_write :: proc(w: io.Writer, id: Identifier) #no_bounds_check { - write_octet :: proc (w: io.Writer, octet: u8) #no_bounds_check { + write_octet :: proc(w: io.Writer, octet: u8) #no_bounds_check { high_nibble := octet >> 4 low_nibble := octet & 0xF @@ -62,15 +62,15 @@ unsafe_write :: proc(w: io.Writer, id: Identifier) #no_bounds_check { io.write_byte(w, strconv.digits[low_nibble]) } - for index in 0 ..< 4 { write_octet(w, id[index]) } + for index in 0 ..< 4 {write_octet(w, id[index])} io.write_byte(w, '-') - for index in 4 ..< 6 { write_octet(w, id[index]) } + for index in 4 ..< 6 {write_octet(w, id[index])} io.write_byte(w, '-') - for index in 6 ..< 8 { write_octet(w, id[index]) } + for index in 6 ..< 8 {write_octet(w, id[index])} io.write_byte(w, '-') - for index in 8 ..< 10 { write_octet(w, id[index]) } + for index in 8 ..< 10 {write_octet(w, id[index])} io.write_byte(w, '-') - for index in 10 ..< 16 { write_octet(w, id[index]) } + for index in 10 ..< 16 {write_octet(w, id[index])} } /* @@ -106,7 +106,7 @@ Convert a UUID to a string in the 8-4-4-4-12 format. Inputs: - id: The identifier to convert. -- buffer: A byte buffer to store the result. Must be at least 32 bytes large. +- buffer: A byte buffer to store the result. Must be at least 36 bytes large. - loc: The caller location for debugging purposes (default: #caller_location) Returns: @@ -119,7 +119,11 @@ to_string_buffer :: proc( ) -> ( str: string, ) { - assert(len(buffer) >= EXPECTED_LENGTH, "The buffer provided is not at least 32 bytes large.", loc) + assert( + len(buffer) >= EXPECTED_LENGTH, + "The buffer provided is not at least 36 bytes large.", + loc, + ) builder := strings.builder_from_bytes(buffer) unsafe_write(strings.to_writer(&builder), id) return strings.to_string(builder) @@ -129,3 +133,4 @@ to_string :: proc { to_string_allocated, to_string_buffer, } + diff --git a/core/encoding/varint/doc.odin b/core/encoding/varint/doc.odin index c0a09873c..a00cfed15 100644 --- a/core/encoding/varint/doc.odin +++ b/core/encoding/varint/doc.odin @@ -1,10 +1,11 @@ /* - Implementation of the LEB128 variable integer encoding as used by DWARF encoding and DEX files, among others. +Implementation of the LEB128 variable integer encoding as used by DWARF encoding and DEX files, among others. - Author of this Odin package: Jeroen van Rijn +Author of this Odin package: Jeroen van Rijn + +Example: + package main - Example: - ```odin import "core:encoding/varint" import "core:fmt" @@ -22,7 +23,5 @@ assert(decoded_val == value && decode_size == encode_size && decode_err == .None) fmt.printf("Decoded as %v, using %v byte%v\n", decoded_val, decode_size, "" if decode_size == 1 else "s") } - ``` - */ -package encoding_varint
\ No newline at end of file +package encoding_varint diff --git a/core/encoding/varint/leb128.odin b/core/encoding/varint/leb128.odin index ca6513f04..606c57ba7 100644 --- a/core/encoding/varint/leb128.odin +++ b/core/encoding/varint/leb128.odin @@ -6,8 +6,6 @@ Jeroen van Rijn: Initial implementation. */ -// package varint implements variable length integer encoding and decoding using -// the LEB128 format as used by DWARF debug info, Android .dex and other file formats. package encoding_varint // In theory we should use the bigint package. In practice, varints bigger than this indicate a corrupted file. @@ -160,4 +158,4 @@ encode_ileb128 :: proc(buf: []u8, val: i128) -> (size: int, err: Error) { buf[size - 1] = u8(low) } return -}
\ No newline at end of file +} diff --git a/core/encoding/xml/doc.odin b/core/encoding/xml/doc.odin new file mode 100644 index 000000000..10d9f78be --- /dev/null +++ b/core/encoding/xml/doc.odin @@ -0,0 +1,23 @@ +/* +XML 1.0 / 1.1 parser + +A from-scratch XML implementation, loosely modelled on the [[ spec; https://www.w3.org/TR/2006/REC-xml11-20060816 ]]. + +Features: +- Supports enough of the XML 1.0/1.1 spec to handle the 99.9% of XML documents in common current usage. +- Simple to understand and use. Small. + +Caveats: +- We do NOT support HTML in this package, as that may or may not be valid XML. + If it works, great. If it doesn't, that's not considered a bug. + +- We do NOT support UTF-16. If you have a UTF-16 XML file, please convert it to UTF-8 first. Also, our condolences. +- <[!ELEMENT and <[!ATTLIST are not supported, and will be either ignored or return an error depending on the parser options. + +MAYBE: +- XML writer? +- Serialize/deserialize Odin types? + +For a full example, see: [[ core/encoding/xml/example; https://github.com/odin-lang/Odin/tree/master/core/encoding/xml/example ]] +*/ +package encoding_xml diff --git a/core/encoding/xml/xml_reader.odin b/core/encoding/xml/xml_reader.odin index b9656900f..b8c8b13a4 100644 --- a/core/encoding/xml/xml_reader.odin +++ b/core/encoding/xml/xml_reader.odin @@ -1,29 +1,11 @@ /* - XML 1.0 / 1.1 parser + 2021-2022 Jeroen van Rijn <nom@duclavier.com>. + available under Odin's BSD-3 license. - 2021-2022 Jeroen van Rijn <nom@duclavier.com>. - available under Odin's BSD-3 license. - - from-scratch XML implementation, loosely modelled on the [spec](https://www.w3.org/TR/2006/REC-xml11-20060816). - -Features: -- Supports enough of the XML 1.0/1.1 spec to handle the 99.9% of XML documents in common current usage. -- Simple to understand and use. Small. - -Caveats: -- We do NOT support HTML in this package, as that may or may not be valid XML. - If it works, great. If it doesn't, that's not considered a bug. - -- We do NOT support UTF-16. If you have a UTF-16 XML file, please convert it to UTF-8 first. Also, our condolences. -- <[!ELEMENT and <[!ATTLIST are not supported, and will be either ignored or return an error depending on the parser options. - -MAYBE: -- XML writer? -- Serialize/deserialize Odin types? - -List of contributors: -- Jeroen van Rijn: Initial implementation. + List of contributors: + - Jeroen van Rijn: Initial implementation. */ + package encoding_xml // An XML 1.0 / 1.1 parser diff --git a/core/flags/doc.odin b/core/flags/doc.odin index c3663c419..b97547806 100644 --- a/core/flags/doc.odin +++ b/core/flags/doc.odin @@ -11,15 +11,13 @@ Command-Line Syntax: Arguments are treated differently depending on how they're formatted. The format is similar to the Odin binary's way of handling compiler flags. -``` -type handling ------------- ------------------------ -<positional> depends on struct layout --<flag> set a bool true --<flag:option> set flag to option --<flag=option> set flag to option, alternative syntax --<map>:<key>=<value> set map[key] to value -``` + type handling + ------------ ------------------------ + <positional> depends on struct layout + -<flag> set a bool true + -<flag:option> set flag to option + -<flag=option> set flag to option, alternative syntax + -<map>:<key>=<value> set map[key] to value Struct Tags: @@ -40,11 +38,9 @@ Under the `args` tag, there are the following subtags: - `indistinct`: allow the setting of distinct types by their base type. `required` may be given a range specifier in the following formats: -``` -min -<max -min<max -``` + min + <max + min<max `max` is not inclusive in this range, as noted by the less-than `<` sign, so if you want to require 3 and only 3 arguments in a dynamic array, you would @@ -161,21 +157,15 @@ UNIX-style: This package also supports parsing arguments in a limited flavor of UNIX. Odin and UNIX style are mutually exclusive, and which one to be used is chosen at parse time. - -``` ---flag ---flag=argument ---flag argument ---flag argument repeating-argument -``` + --flag + --flag=argument + --flag argument + --flag argument repeating-argument `-flag` may also be substituted for `--flag`. Do note that map flags are not currently supported in this parsing style. - -Example: - -A complete example is given in the `example` subdirectory. +For a complete example, see: [[ core/flags/example; https://github.com/odin-lang/Odin/blob/master/core/flags/example/example.odin ]]. */ package flags diff --git a/core/flags/errors_bsd.odin b/core/flags/errors_bsd.odin index 1fe6de90b..4d98d2ee4 100644 --- a/core/flags/errors_bsd.odin +++ b/core/flags/errors_bsd.odin @@ -1,4 +1,4 @@ -//+build netbsd, openbsd +#+build netbsd, openbsd package flags import "base:runtime" diff --git a/core/flags/errors_nonbsd.odin b/core/flags/errors_nonbsd.odin index a77f12abf..28912b57f 100644 --- a/core/flags/errors_nonbsd.odin +++ b/core/flags/errors_nonbsd.odin @@ -1,4 +1,5 @@ -//+build !netbsd !openbsd +#+build !netbsd +#+build !openbsd package flags import "base:runtime" diff --git a/core/flags/internal_assignment.odin b/core/flags/internal_assignment.odin index bb49977eb..12ddb876f 100644 --- a/core/flags/internal_assignment.odin +++ b/core/flags/internal_assignment.odin @@ -1,4 +1,4 @@ -//+private +#+private package flags import "base:intrinsics" @@ -14,10 +14,10 @@ import "core:reflect" // positionals first before adding it to a fallback field. @(optimization_mode="favor_size") push_positional :: #force_no_inline proc (model: ^$T, parser: ^Parser, arg: string) -> (error: Error) { - if bit_array.get(&parser.filled_pos, parser.filled_pos.max_index) { - // The max index is set, which means we're out of space. + if set, valid_index := bit_array.get(&parser.filled_pos, parser.filled_pos.length - 1); set || !valid_index { + // The index below the last one is either set or invalid, which means we're out of space. // Add one free bit by setting the index above to false. - bit_array.set(&parser.filled_pos, 1 + parser.filled_pos.max_index, false) + bit_array.set(&parser.filled_pos, parser.filled_pos.length, false) } pos: int = --- diff --git a/core/flags/internal_parsing.odin b/core/flags/internal_parsing.odin index 7a769b17c..4e49f45b0 100644 --- a/core/flags/internal_parsing.odin +++ b/core/flags/internal_parsing.odin @@ -1,4 +1,4 @@ -//+private +#+private package flags import "core:container/bit_array" diff --git a/core/flags/internal_rtti.odin b/core/flags/internal_rtti.odin index 4c1db5d0b..1c559ca55 100644 --- a/core/flags/internal_rtti.odin +++ b/core/flags/internal_rtti.odin @@ -1,4 +1,4 @@ -//+private +#+private package flags import "base:intrinsics" diff --git a/core/flags/internal_rtti_nonbsd.odin b/core/flags/internal_rtti_nonbsd.odin index 27fdb3b75..e1286186b 100644 --- a/core/flags/internal_rtti_nonbsd.odin +++ b/core/flags/internal_rtti_nonbsd.odin @@ -1,5 +1,6 @@ -//+private -//+build !netbsd !openbsd +#+private +#+build !netbsd +#+build !openbsd package flags import "core:net" diff --git a/core/flags/internal_validation.odin b/core/flags/internal_validation.odin index b71cf9fe7..afd05331c 100644 --- a/core/flags/internal_validation.odin +++ b/core/flags/internal_validation.odin @@ -1,4 +1,4 @@ -//+private +#+private package flags @require import "base:runtime" diff --git a/core/fmt/example.odin b/core/fmt/example.odin index 503e64f2b..6929e9be7 100644 --- a/core/fmt/example.odin +++ b/core/fmt/example.odin @@ -1,4 +1,4 @@ -//+build ignore +#+build ignore package custom_formatter_example import "core:fmt" import "core:io" diff --git a/core/fmt/fmt_js.odin b/core/fmt/fmt_js.odin index acf218eb5..ce90fbfe7 100644 --- a/core/fmt/fmt_js.odin +++ b/core/fmt/fmt_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package fmt import "core:bufio" diff --git a/core/fmt/fmt_os.odin b/core/fmt/fmt_os.odin index 9de0d43be..a481061f1 100644 --- a/core/fmt/fmt_os.odin +++ b/core/fmt/fmt_os.odin @@ -1,6 +1,6 @@ -//+build !freestanding -//+build !js -//+build !orca +#+build !freestanding +#+build !js +#+build !orca package fmt import "base:runtime" diff --git a/core/hash/xxhash/common.odin b/core/hash/xxhash/common.odin index bbeb60db3..adfc1bac2 100644 --- a/core/hash/xxhash/common.odin +++ b/core/hash/xxhash/common.odin @@ -1,5 +1,4 @@ /* - An implementation of Yann Collet's [xxhash Fast Hash Algorithm](https://cyan4973.github.io/xxHash/). Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. Made available under Odin's BSD-3 license, based on the original C code. @@ -7,6 +6,8 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ + +// An implementation of Yann Collet's [[ xxhash Fast Hash Algorithm; https://cyan4973.github.io/xxHash/ ]]. package xxhash import "base:intrinsics" diff --git a/core/hash/xxhash/streaming.odin b/core/hash/xxhash/streaming.odin index f68862f67..f77edf3e9 100644 --- a/core/hash/xxhash/streaming.odin +++ b/core/hash/xxhash/streaming.odin @@ -7,6 +7,7 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ + package xxhash import "core:mem" @@ -371,4 +372,4 @@ XXH3_generate_secret :: proc(secret_buffer: []u8, custom_seed: []u8) { mem_copy(&secret_buffer[segment_start], &segment, size_of(segment)) } } -}
\ No newline at end of file +} diff --git a/core/hash/xxhash/xxhash_3.odin b/core/hash/xxhash/xxhash_3.odin index 9e159260b..293e98528 100644 --- a/core/hash/xxhash/xxhash_3.odin +++ b/core/hash/xxhash/xxhash_3.odin @@ -7,6 +7,7 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ + package xxhash import "base:intrinsics" diff --git a/core/hash/xxhash/xxhash_32.odin b/core/hash/xxhash/xxhash_32.odin index 3ea1c3cf2..28cd4e177 100644 --- a/core/hash/xxhash/xxhash_32.odin +++ b/core/hash/xxhash/xxhash_32.odin @@ -7,6 +7,7 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ + package xxhash import "base:intrinsics" diff --git a/core/hash/xxhash/xxhash_64.odin b/core/hash/xxhash/xxhash_64.odin index 3b24f20a1..87e8916d6 100644 --- a/core/hash/xxhash/xxhash_64.odin +++ b/core/hash/xxhash/xxhash_64.odin @@ -7,6 +7,7 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ + package xxhash import "base:intrinsics" @@ -19,15 +20,15 @@ xxh_u64 :: u64 XXH64_DEFAULT_SEED :: XXH64_hash(0) XXH64_state :: struct { - total_len: XXH64_hash, /*!< Total length hashed. This is always 64-bit. */ - v1: XXH64_hash, /*!< First accumulator lane */ - v2: XXH64_hash, /*!< Second accumulator lane */ - v3: XXH64_hash, /*!< Third accumulator lane */ - v4: XXH64_hash, /*!< Fourth accumulator lane */ - mem64: [4]XXH64_hash, /*!< Internal buffer for partial reads. Treated as unsigned char[32]. */ - memsize: XXH32_hash, /*!< Amount of data in @ref mem64 */ - reserved32: XXH32_hash, /*!< Reserved field, needed for padding anyways*/ - reserved64: XXH64_hash, /*!< Reserved field. Do not read or write to it, it may be removed. */ + total_len: XXH64_hash, /*!< Total length hashed. This is always 64-bit. */ + v1: XXH64_hash, /*!< First accumulator lane */ + v2: XXH64_hash, /*!< Second accumulator lane */ + v3: XXH64_hash, /*!< Third accumulator lane */ + v4: XXH64_hash, /*!< Fourth accumulator lane */ + mem64: [4]XXH64_hash, /*!< Internal buffer for partial reads. Treated as unsigned char[32]. */ + memsize: XXH32_hash, /*!< Amount of data in @ref mem64 */ + reserved32: XXH32_hash, /*!< Reserved field, needed for padding anyways*/ + reserved64: XXH64_hash, /*!< Reserved field. Do not read or write to it, it may be removed. */ } XXH64_canonical :: struct { diff --git a/core/image/bmp/bmp_js.odin b/core/image/bmp/bmp_js.odin index d87a7d2d5..fa5d59095 100644 --- a/core/image/bmp/bmp_js.odin +++ b/core/image/bmp/bmp_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package core_image_bmp load :: proc{load_from_bytes, load_from_context} diff --git a/core/image/bmp/bmp_os.odin b/core/image/bmp/bmp_os.odin index d20abc685..70a85a784 100644 --- a/core/image/bmp/bmp_os.odin +++ b/core/image/bmp/bmp_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package core_image_bmp import "core:os" diff --git a/core/image/common.odin b/core/image/common.odin index 62deb60a9..690ebc045 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -1393,6 +1393,7 @@ expand_grayscale :: proc(img: ^Image, allocator := context.allocator) -> (ok: bo for p in inp { out[0].rgb = p.r // Gray component. out[0].a = p.g // Alpha component. + out = out[1:] } case: @@ -1417,6 +1418,7 @@ expand_grayscale :: proc(img: ^Image, allocator := context.allocator) -> (ok: bo for p in inp { out[0].rgb = p.r // Gray component. out[0].a = p.g // Alpha component. + out = out[1:] } case: diff --git a/core/image/general_js.odin b/core/image/general_js.odin index 841d9c200..abf9812c0 100644 --- a/core/image/general_js.odin +++ b/core/image/general_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package image load :: proc{ diff --git a/core/image/general_os.odin b/core/image/general_os.odin index e1fe440a4..98eb5bdbe 100644 --- a/core/image/general_os.odin +++ b/core/image/general_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package image import "core:os" diff --git a/core/image/netpbm/netpbm.odin b/core/image/netpbm/netpbm.odin index ab3945ad7..a9dc6599a 100644 --- a/core/image/netpbm/netpbm.odin +++ b/core/image/netpbm/netpbm.odin @@ -1,4 +1,4 @@ -//+vet !using-stmt +#+vet !using-stmt package netpbm import "core:bytes" diff --git a/core/image/netpbm/netpbm_js.odin b/core/image/netpbm/netpbm_js.odin index 7db17a05d..7d475cf62 100644 --- a/core/image/netpbm/netpbm_js.odin +++ b/core/image/netpbm/netpbm_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package netpbm load :: proc { diff --git a/core/image/netpbm/netpbm_os.odin b/core/image/netpbm/netpbm_os.odin index 609f1ea1f..2cf2439ac 100644 --- a/core/image/netpbm/netpbm_os.odin +++ b/core/image/netpbm/netpbm_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package netpbm import "core:os" diff --git a/core/image/png/doc.odin b/core/image/png/doc.odin new file mode 100644 index 000000000..623c13077 --- /dev/null +++ b/core/image/png/doc.odin @@ -0,0 +1,348 @@ +/* +package png implements a PNG image reader + +The PNG specification is at [[ https://www.w3.org/TR/PNG/ ]]. + +Example: + package main + + import "core:image" + // import "core:image/png" + import "core:bytes" + import "core:fmt" + + // For PPM writer + import "core:mem" + import "core:os" + + main :: proc() { + track := mem.Tracking_Allocator{} + mem.tracking_allocator_init(&track, context.allocator) + + context.allocator = mem.tracking_allocator(&track) + + demo() + + if len(track.allocation_map) > 0 { + fmt.println("Leaks:") + for _, v in track.allocation_map { + fmt.printf("\t%v\n\n", v) + } + } + } + + demo :: proc() { + file: string + + options := image.Options{.return_metadata} + err: image.Error + img: ^image.Image + + file = "../../../misc/logo-slim.png" + + img, err = load(file, options) + defer destroy(img) + + if err != nil { + fmt.printf("Trying to read PNG file %v returned %v\n", file, err) + } else { + fmt.printf("Image: %vx%vx%v, %v-bit.\n", img.width, img.height, img.channels, img.depth) + + if v, ok := img.metadata.(^image.PNG_Info); ok { + // Handle ancillary chunks as you wish. + // We provide helper functions for a few types. + for c in v.chunks { + #partial switch c.header.type { + case .tIME: + if t, t_ok := core_time(c); t_ok { + fmt.printf("[tIME]: %v\n", t) + } + case .gAMA: + if gama, gama_ok := gamma(c); gama_ok { + fmt.printf("[gAMA]: %v\n", gama) + } + case .pHYs: + if phys, phys_ok := phys(c); phys_ok { + if phys.unit == .Meter { + xm := f32(img.width) / f32(phys.ppu_x) + ym := f32(img.height) / f32(phys.ppu_y) + dpi_x, dpi_y := phys_to_dpi(phys) + fmt.printf("[pHYs] Image resolution is %v x %v pixels per meter.\n", phys.ppu_x, phys.ppu_y) + fmt.printf("[pHYs] Image resolution is %v x %v DPI.\n", dpi_x, dpi_y) + fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym) + } else { + fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y) + } + } + case .iTXt, .zTXt, .tEXt: + res, ok_text := text(c) + if ok_text { + if c.header.type == .iTXt { + fmt.printf("[iTXt] %v (%v:%v): %v\n", res.keyword, res.language, res.keyword_localized, res.text) + } else { + fmt.printf("[tEXt/zTXt] %v: %v\n", res.keyword, res.text) + } + } + defer text_destroy(res) + case .bKGD: + fmt.printf("[bKGD] %v\n", img.background) + case .eXIf: + if res, ok_exif := exif(c); ok_exif { + /* + Other than checking the signature and byte order, we don't handle Exif data. + If you wish to interpret it, pass it to an Exif parser. + */ + fmt.printf("[eXIf] %v\n", res) + } + case .PLTE: + if plte, plte_ok := plte(c); plte_ok { + fmt.printf("[PLTE] %v\n", plte) + } else { + fmt.printf("[PLTE] Error\n") + } + case .hIST: + if res, ok_hist := hist(c); ok_hist { + fmt.printf("[hIST] %v\n", res) + } + case .cHRM: + if res, ok_chrm := chrm(c); ok_chrm { + fmt.printf("[cHRM] %v\n", res) + } + case .sPLT: + res, ok_splt := splt(c) + if ok_splt { + fmt.printf("[sPLT] %v\n", res) + } + splt_destroy(res) + case .sBIT: + if res, ok_sbit := sbit(c); ok_sbit { + fmt.printf("[sBIT] %v\n", res) + } + case .iCCP: + res, ok_iccp := iccp(c) + if ok_iccp { + fmt.printf("[iCCP] %v\n", res) + } + iccp_destroy(res) + case .sRGB: + if res, ok_srgb := srgb(c); ok_srgb { + fmt.printf("[sRGB] Rendering intent: %v\n", res) + } + case: + type := c.header.type + name := chunk_type_to_name(&type) + fmt.printf("[%v]: %v\n", name, c.data) + } + } + } + } + + fmt.printf("Done parsing metadata.\n") + + if err == nil && .do_not_decompress_image not_in options && .info not_in options { + if ok := write_image_as_ppm("out.ppm", img); ok { + fmt.println("Saved decoded image.") + } else { + fmt.println("Error saving out.ppm.") + fmt.println(img) + } + } + } + + // Crappy PPM writer used during testing. Don't use in production. + write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: bool) { + + _bg :: proc(bg: Maybe([3]u16), x, y: int, high := true) -> (res: [3]u16) { + if v, ok := bg.?; ok { + res = v + } else { + if high { + l := u16(30 * 256 + 30) + + if (x & 4 == 0) ~ (y & 4 == 0) { + res = [3]u16{l, 0, l} + } else { + res = [3]u16{l >> 1, 0, l >> 1} + } + } else { + if (x & 4 == 0) ~ (y & 4 == 0) { + res = [3]u16{30, 30, 30} + } else { + res = [3]u16{15, 15, 15} + } + } + } + return + } + + // profiler.timed_proc(); + using image + using os + + flags: int = O_WRONLY|O_CREATE|O_TRUNC + + img := image + + // PBM 16-bit images are big endian + when ODIN_ENDIAN == .Little { + if img.depth == 16 { + // The pixel components are in Big Endian. Let's byteswap back. + input := mem.slice_data_cast([]u16, img.pixels.buf[:]) + output := mem.slice_data_cast([]u16be, img.pixels.buf[:]) + #no_bounds_check for v, i in input { + output[i] = u16be(v) + } + } + } + + pix := bytes.buffer_to_bytes(&img.pixels) + + if len(pix) == 0 || len(pix) < image.width * image.height * int(image.channels) { + return false + } + + mode: int = 0 + when ODIN_OS == .Linux || ODIN_OS == .Darwin { + // NOTE(justasd): 644 (owner read, write; group read; others read) + mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH + } + + fd, err := open(filename, flags, mode) + if err != nil { + return false + } + defer close(fd) + + write_string(fd, + fmt.tprintf("P6\n%v %v\n%v\n", width, height, uint(1 << uint(depth) - 1)), + ) + + if channels == 3 { + // We don't handle transparency here... + write_ptr(fd, raw_data(pix), len(pix)) + } else { + bpp := depth == 16 ? 2 : 1 + bytes_needed := width * height * 3 * bpp + + op := bytes.Buffer{} + bytes.buffer_init_allocator(&op, bytes_needed, bytes_needed) + defer bytes.buffer_destroy(&op) + + if channels == 1 { + if depth == 16 { + assert(len(pix) == width * height * 2) + p16 := mem.slice_data_cast([]u16, pix) + o16 := mem.slice_data_cast([]u16, op.buf[:]) + #no_bounds_check for len(p16) != 0 { + r := u16(p16[0]) + o16[0] = r + o16[1] = r + o16[2] = r + p16 = p16[1:] + o16 = o16[3:] + } + } else { + o := 0 + for i := 0; i < len(pix); i += 1 { + r := pix[i] + op.buf[o ] = r + op.buf[o+1] = r + op.buf[o+2] = r + o += 3 + } + } + write_ptr(fd, raw_data(op.buf), len(op.buf)) + } else if channels == 2 { + if depth == 16 { + p16 := mem.slice_data_cast([]u16, pix) + o16 := mem.slice_data_cast([]u16, op.buf[:]) + + bgcol := img.background + + #no_bounds_check for len(p16) != 0 { + r := f64(u16(p16[0])) + bg: f64 + if bgcol != nil { + v := bgcol.([3]u16)[0] + bg = f64(v) + } + a := f64(u16(p16[1])) / 65535.0 + l := (a * r) + (1 - a) * bg + + o16[0] = u16(l) + o16[1] = u16(l) + o16[2] = u16(l) + + p16 = p16[2:] + o16 = o16[3:] + } + } else { + o := 0 + for i := 0; i < len(pix); i += 2 { + r := pix[i]; a := pix[i+1]; a1 := f32(a) / 255.0 + c := u8(f32(r) * a1) + op.buf[o ] = c + op.buf[o+1] = c + op.buf[o+2] = c + o += 3 + } + } + write_ptr(fd, raw_data(op.buf), len(op.buf)) + } else if channels == 4 { + if depth == 16 { + p16 := mem.slice_data_cast([]u16be, pix) + o16 := mem.slice_data_cast([]u16be, op.buf[:]) + + #no_bounds_check for len(p16) != 0 { + + bg := _bg(img.background, 0, 0) + r := f32(p16[0]) + g := f32(p16[1]) + b := f32(p16[2]) + a := f32(p16[3]) / 65535.0 + + lr := (a * r) + (1 - a) * f32(bg[0]) + lg := (a * g) + (1 - a) * f32(bg[1]) + lb := (a * b) + (1 - a) * f32(bg[2]) + + o16[0] = u16be(lr) + o16[1] = u16be(lg) + o16[2] = u16be(lb) + + p16 = p16[4:] + o16 = o16[3:] + } + } else { + o := 0 + + for i := 0; i < len(pix); i += 4 { + + x := (i / 4) % width + y := i / width / 4 + + _b := _bg(img.background, x, y, false) + bgcol := [3]u8{u8(_b[0]), u8(_b[1]), u8(_b[2])} + + r := f32(pix[i]) + g := f32(pix[i+1]) + b := f32(pix[i+2]) + a := f32(pix[i+3]) / 255.0 + + lr := u8(f32(r) * a + (1 - a) * f32(bgcol[0])) + lg := u8(f32(g) * a + (1 - a) * f32(bgcol[1])) + lb := u8(f32(b) * a + (1 - a) * f32(bgcol[2])) + op.buf[o ] = lr + op.buf[o+1] = lg + op.buf[o+2] = lb + o += 3 + } + } + write_ptr(fd, raw_data(op.buf), len(op.buf)) + } else { + return false + } + } + return true + } +*/ +package png diff --git a/core/image/png/example.odin b/core/image/png/example.odin deleted file mode 100644 index ce491978b..000000000 --- a/core/image/png/example.odin +++ /dev/null @@ -1,351 +0,0 @@ -/* - Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. - Made available under Odin's BSD-3 license. - - List of contributors: - Jeroen van Rijn: Initial implementation. - Ginger Bill: Cosmetic changes. - - An example of how to use `load`. -*/ -//+build ignore -package png - -import "core:image" -// import "core:image/png" -import "core:bytes" -import "core:fmt" - -// For PPM writer -import "core:mem" -import "core:os" - -main :: proc() { - track := mem.Tracking_Allocator{} - mem.tracking_allocator_init(&track, context.allocator) - - context.allocator = mem.tracking_allocator(&track) - - demo() - - if len(track.allocation_map) > 0 { - fmt.println("Leaks:") - for _, v in track.allocation_map { - fmt.printf("\t%v\n\n", v) - } - } -} - -demo :: proc() { - file: string - - options := image.Options{.return_metadata} - err: image.Error - img: ^image.Image - - file = "../../../misc/logo-slim.png" - - img, err = load(file, options) - defer destroy(img) - - if err != nil { - fmt.printf("Trying to read PNG file %v returned %v\n", file, err) - } else { - fmt.printf("Image: %vx%vx%v, %v-bit.\n", img.width, img.height, img.channels, img.depth) - - if v, ok := img.metadata.(^image.PNG_Info); ok { - // Handle ancillary chunks as you wish. - // We provide helper functions for a few types. - for c in v.chunks { - #partial switch c.header.type { - case .tIME: - if t, t_ok := core_time(c); t_ok { - fmt.printf("[tIME]: %v\n", t) - } - case .gAMA: - if gama, gama_ok := gamma(c); gama_ok { - fmt.printf("[gAMA]: %v\n", gama) - } - case .pHYs: - if phys, phys_ok := phys(c); phys_ok { - if phys.unit == .Meter { - xm := f32(img.width) / f32(phys.ppu_x) - ym := f32(img.height) / f32(phys.ppu_y) - dpi_x, dpi_y := phys_to_dpi(phys) - fmt.printf("[pHYs] Image resolution is %v x %v pixels per meter.\n", phys.ppu_x, phys.ppu_y) - fmt.printf("[pHYs] Image resolution is %v x %v DPI.\n", dpi_x, dpi_y) - fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym) - } else { - fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y) - } - } - case .iTXt, .zTXt, .tEXt: - res, ok_text := text(c) - if ok_text { - if c.header.type == .iTXt { - fmt.printf("[iTXt] %v (%v:%v): %v\n", res.keyword, res.language, res.keyword_localized, res.text) - } else { - fmt.printf("[tEXt/zTXt] %v: %v\n", res.keyword, res.text) - } - } - defer text_destroy(res) - case .bKGD: - fmt.printf("[bKGD] %v\n", img.background) - case .eXIf: - if res, ok_exif := exif(c); ok_exif { - /* - Other than checking the signature and byte order, we don't handle Exif data. - If you wish to interpret it, pass it to an Exif parser. - */ - fmt.printf("[eXIf] %v\n", res) - } - case .PLTE: - if plte, plte_ok := plte(c); plte_ok { - fmt.printf("[PLTE] %v\n", plte) - } else { - fmt.printf("[PLTE] Error\n") - } - case .hIST: - if res, ok_hist := hist(c); ok_hist { - fmt.printf("[hIST] %v\n", res) - } - case .cHRM: - if res, ok_chrm := chrm(c); ok_chrm { - fmt.printf("[cHRM] %v\n", res) - } - case .sPLT: - res, ok_splt := splt(c) - if ok_splt { - fmt.printf("[sPLT] %v\n", res) - } - splt_destroy(res) - case .sBIT: - if res, ok_sbit := sbit(c); ok_sbit { - fmt.printf("[sBIT] %v\n", res) - } - case .iCCP: - res, ok_iccp := iccp(c) - if ok_iccp { - fmt.printf("[iCCP] %v\n", res) - } - iccp_destroy(res) - case .sRGB: - if res, ok_srgb := srgb(c); ok_srgb { - fmt.printf("[sRGB] Rendering intent: %v\n", res) - } - case: - type := c.header.type - name := chunk_type_to_name(&type) - fmt.printf("[%v]: %v\n", name, c.data) - } - } - } - } - - fmt.printf("Done parsing metadata.\n") - - if err == nil && .do_not_decompress_image not_in options && .info not_in options { - if ok := write_image_as_ppm("out.ppm", img); ok { - fmt.println("Saved decoded image.") - } else { - fmt.println("Error saving out.ppm.") - fmt.println(img) - } - } -} - -// Crappy PPM writer used during testing. Don't use in production. -write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: bool) { - - _bg :: proc(bg: Maybe([3]u16), x, y: int, high := true) -> (res: [3]u16) { - if v, ok := bg.?; ok { - res = v - } else { - if high { - l := u16(30 * 256 + 30) - - if (x & 4 == 0) ~ (y & 4 == 0) { - res = [3]u16{l, 0, l} - } else { - res = [3]u16{l >> 1, 0, l >> 1} - } - } else { - if (x & 4 == 0) ~ (y & 4 == 0) { - res = [3]u16{30, 30, 30} - } else { - res = [3]u16{15, 15, 15} - } - } - } - return - } - - // profiler.timed_proc(); - using image - using os - - flags: int = O_WRONLY|O_CREATE|O_TRUNC - - img := image - - // PBM 16-bit images are big endian - when ODIN_ENDIAN == .Little { - if img.depth == 16 { - // The pixel components are in Big Endian. Let's byteswap back. - input := mem.slice_data_cast([]u16, img.pixels.buf[:]) - output := mem.slice_data_cast([]u16be, img.pixels.buf[:]) - #no_bounds_check for v, i in input { - output[i] = u16be(v) - } - } - } - - pix := bytes.buffer_to_bytes(&img.pixels) - - if len(pix) == 0 || len(pix) < image.width * image.height * int(image.channels) { - return false - } - - mode: int = 0 - when ODIN_OS == .Linux || ODIN_OS == .Darwin { - // NOTE(justasd): 644 (owner read, write; group read; others read) - mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH - } - - fd, err := open(filename, flags, mode) - if err != nil { - return false - } - defer close(fd) - - write_string(fd, - fmt.tprintf("P6\n%v %v\n%v\n", width, height, uint(1 << uint(depth) - 1)), - ) - - if channels == 3 { - // We don't handle transparency here... - write_ptr(fd, raw_data(pix), len(pix)) - } else { - bpp := depth == 16 ? 2 : 1 - bytes_needed := width * height * 3 * bpp - - op := bytes.Buffer{} - bytes.buffer_init_allocator(&op, bytes_needed, bytes_needed) - defer bytes.buffer_destroy(&op) - - if channels == 1 { - if depth == 16 { - assert(len(pix) == width * height * 2) - p16 := mem.slice_data_cast([]u16, pix) - o16 := mem.slice_data_cast([]u16, op.buf[:]) - #no_bounds_check for len(p16) != 0 { - r := u16(p16[0]) - o16[0] = r - o16[1] = r - o16[2] = r - p16 = p16[1:] - o16 = o16[3:] - } - } else { - o := 0 - for i := 0; i < len(pix); i += 1 { - r := pix[i] - op.buf[o ] = r - op.buf[o+1] = r - op.buf[o+2] = r - o += 3 - } - } - write_ptr(fd, raw_data(op.buf), len(op.buf)) - } else if channels == 2 { - if depth == 16 { - p16 := mem.slice_data_cast([]u16, pix) - o16 := mem.slice_data_cast([]u16, op.buf[:]) - - bgcol := img.background - - #no_bounds_check for len(p16) != 0 { - r := f64(u16(p16[0])) - bg: f64 - if bgcol != nil { - v := bgcol.([3]u16)[0] - bg = f64(v) - } - a := f64(u16(p16[1])) / 65535.0 - l := (a * r) + (1 - a) * bg - - o16[0] = u16(l) - o16[1] = u16(l) - o16[2] = u16(l) - - p16 = p16[2:] - o16 = o16[3:] - } - } else { - o := 0 - for i := 0; i < len(pix); i += 2 { - r := pix[i]; a := pix[i+1]; a1 := f32(a) / 255.0 - c := u8(f32(r) * a1) - op.buf[o ] = c - op.buf[o+1] = c - op.buf[o+2] = c - o += 3 - } - } - write_ptr(fd, raw_data(op.buf), len(op.buf)) - } else if channels == 4 { - if depth == 16 { - p16 := mem.slice_data_cast([]u16be, pix) - o16 := mem.slice_data_cast([]u16be, op.buf[:]) - - #no_bounds_check for len(p16) != 0 { - - bg := _bg(img.background, 0, 0) - r := f32(p16[0]) - g := f32(p16[1]) - b := f32(p16[2]) - a := f32(p16[3]) / 65535.0 - - lr := (a * r) + (1 - a) * f32(bg[0]) - lg := (a * g) + (1 - a) * f32(bg[1]) - lb := (a * b) + (1 - a) * f32(bg[2]) - - o16[0] = u16be(lr) - o16[1] = u16be(lg) - o16[2] = u16be(lb) - - p16 = p16[4:] - o16 = o16[3:] - } - } else { - o := 0 - - for i := 0; i < len(pix); i += 4 { - - x := (i / 4) % width - y := i / width / 4 - - _b := _bg(img.background, x, y, false) - bgcol := [3]u8{u8(_b[0]), u8(_b[1]), u8(_b[2])} - - r := f32(pix[i]) - g := f32(pix[i+1]) - b := f32(pix[i+2]) - a := f32(pix[i+3]) / 255.0 - - lr := u8(f32(r) * a + (1 - a) * f32(bgcol[0])) - lg := u8(f32(g) * a + (1 - a) * f32(bgcol[1])) - lb := u8(f32(b) * a + (1 - a) * f32(bgcol[2])) - op.buf[o ] = lr - op.buf[o+1] = lg - op.buf[o+2] = lb - o += 3 - } - } - write_ptr(fd, raw_data(op.buf), len(op.buf)) - } else { - return false - } - } - return true -} diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index 7b6367694..f094b54a9 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -8,6 +8,7 @@ These are a few useful utility functions to work with PNG images. */ + package png import "core:image" diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 177269722..2d3665e94 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -8,10 +8,7 @@ */ -// package png implements a PNG image reader -// -// The PNG specification is at https://www.w3.org/TR/PNG/. -//+vet !using-stmt +#+vet !using-stmt package png import "core:compress" @@ -1619,4 +1616,4 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH @(init, private) _register :: proc() { image.register(.PNG, load_from_bytes, destroy) -}
\ No newline at end of file +} diff --git a/core/image/png/png_js.odin b/core/image/png/png_js.odin index 57c27fc64..dd9e74526 100644 --- a/core/image/png/png_js.odin +++ b/core/image/png/png_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package png load :: proc{load_from_bytes, load_from_context} diff --git a/core/image/png/png_os.odin b/core/image/png/png_os.odin index cc65e7b42..8e0706206 100644 --- a/core/image/png/png_os.odin +++ b/core/image/png/png_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package png import "core:os" diff --git a/core/image/qoi/qoi.odin b/core/image/qoi/qoi.odin index 5cf252fcc..6b6149e60 100644 --- a/core/image/qoi/qoi.odin +++ b/core/image/qoi/qoi.odin @@ -9,7 +9,7 @@ // package qoi implements a QOI image reader // -// The QOI specification is at https://qoiformat.org. +// The QOI specification is at [[ https://qoiformat.org ]]. package qoi import "core:image" diff --git a/core/image/qoi/qoi_js.odin b/core/image/qoi/qoi_js.odin index 2c23cc17a..4a69e98a0 100644 --- a/core/image/qoi/qoi_js.odin +++ b/core/image/qoi/qoi_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package qoi save :: proc{save_to_buffer} diff --git a/core/image/qoi/qoi_os.odin b/core/image/qoi/qoi_os.odin index efcec6c52..c85fdd839 100644 --- a/core/image/qoi/qoi_os.odin +++ b/core/image/qoi/qoi_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package qoi import "core:os" diff --git a/core/image/tga/tga_js.odin b/core/image/tga/tga_js.odin index d98b241a7..9261be8c6 100644 --- a/core/image/tga/tga_js.odin +++ b/core/image/tga/tga_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package tga save :: proc{save_to_buffer} diff --git a/core/image/tga/tga_os.odin b/core/image/tga/tga_os.odin index 12747a684..a78998105 100644 --- a/core/image/tga/tga_os.odin +++ b/core/image/tga/tga_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package tga import "core:os" diff --git a/core/io/util.odin b/core/io/util.odin index c24eb99c5..e65a69fb3 100644 --- a/core/io/util.odin +++ b/core/io/util.odin @@ -340,6 +340,9 @@ _limited_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, l := (^Limited_Reader)(stream_data) #partial switch mode { case .Read: + if len(p) == 0 { + return 0, nil + } if l.n <= 0 { return 0, .EOF } @@ -376,11 +379,12 @@ Section_Reader :: struct { limit: i64, } -section_reader_init :: proc(s: ^Section_Reader, r: Reader_At, off: i64, n: i64) { +section_reader_init :: proc(s: ^Section_Reader, r: Reader_At, off: i64, n: i64) -> Reader { s.r = r + s.base = off s.off = off s.limit = off + n - return + return section_reader_to_stream(s) } section_reader_to_stream :: proc(s: ^Section_Reader) -> (out: Stream) { out.data = s @@ -393,6 +397,9 @@ _section_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, s := (^Section_Reader)(stream_data) #partial switch mode { case .Read: + if len(p) == 0 { + return 0, nil + } if s.off >= s.limit { return 0, .EOF } @@ -404,6 +411,9 @@ _section_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, s.off += i64(n) return case .Read_At: + if len(p) == 0 { + return 0, nil + } p, off := p, offset if off < 0 || off >= s.limit - s.base { diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index f05f7a258..e45f99523 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -1,5 +1,5 @@ -//+build !freestanding -//+build !orca +#+build !freestanding +#+build !orca package log import "core:encoding/ansi" diff --git a/core/math/big/common.odin b/core/math/big/common.odin index fabf39520..5428b00ee 100644 --- a/core/math/big/common.odin +++ b/core/math/big/common.odin @@ -195,7 +195,7 @@ Error_String :: #sparse[Error]string{ } Primality_Flag :: enum u8 { - Blum_Blum_Shub = 0, // Make prime congruent to 3 mod 4 + Blum_Blum_Shub = 0, // Make prime congruent to 3 mod 4 Safe = 1, // Make sure (p-1)/2 is prime as well (implies .Blum_Blum_Shub) Second_MSB_On = 3, // Make the 2nd highest bit one } diff --git a/core/math/big/tune.odin b/core/math/big/tune.odin index 5938dafde..eab36b951 100644 --- a/core/math/big/tune.odin +++ b/core/math/big/tune.odin @@ -7,7 +7,7 @@ The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks. */ -//+build ignore +#+build ignore package math_big import "core:time" diff --git a/core/math/linalg/glsl/linalg_glsl.odin b/core/math/linalg/glsl/linalg_glsl.odin index 363a95887..5444f89e2 100644 --- a/core/math/linalg/glsl/linalg_glsl.odin +++ b/core/math/linalg/glsl/linalg_glsl.odin @@ -22,9 +22,9 @@ F32_EPSILON :: 1e-7 F64_EPSILON :: 1e-15 // Odin matrices are stored internally as Column-Major, which matches OpenGL/GLSL by default -mat2 :: distinct matrix[2, 2]f32 -mat3 :: distinct matrix[3, 3]f32 -mat4 :: distinct matrix[4, 4]f32 +mat2 :: matrix[2, 2]f32 +mat3 :: matrix[3, 3]f32 +mat4 :: matrix[4, 4]f32 mat2x2 :: mat2 mat3x3 :: mat3 mat4x4 :: mat4 @@ -33,52 +33,52 @@ mat4x4 :: mat4 // but they match how GLSL and OpenGL defines them in name // Odin: matrix[R, C]f32 // GLSL: matCxR -mat3x2 :: distinct matrix[2, 3]f32 -mat4x2 :: distinct matrix[2, 4]f32 -mat2x3 :: distinct matrix[3, 2]f32 -mat4x3 :: distinct matrix[3, 4]f32 -mat2x4 :: distinct matrix[4, 2]f32 -mat3x4 :: distinct matrix[4, 3]f32 +mat3x2 :: matrix[2, 3]f32 +mat4x2 :: matrix[2, 4]f32 +mat2x3 :: matrix[3, 2]f32 +mat4x3 :: matrix[3, 4]f32 +mat2x4 :: matrix[4, 2]f32 +mat3x4 :: matrix[4, 3]f32 -vec2 :: distinct [2]f32 -vec3 :: distinct [3]f32 -vec4 :: distinct [4]f32 +vec2 :: [2]f32 +vec3 :: [3]f32 +vec4 :: [4]f32 -ivec2 :: distinct [2]i32 -ivec3 :: distinct [3]i32 -ivec4 :: distinct [4]i32 +ivec2 :: [2]i32 +ivec3 :: [3]i32 +ivec4 :: [4]i32 -uvec2 :: distinct [2]u32 -uvec3 :: distinct [3]u32 -uvec4 :: distinct [4]u32 +uvec2 :: [2]u32 +uvec3 :: [3]u32 +uvec4 :: [4]u32 -bvec2 :: distinct [2]bool -bvec3 :: distinct [3]bool -bvec4 :: distinct [4]bool +bvec2 :: [2]bool +bvec3 :: [3]bool +bvec4 :: [4]bool -quat :: distinct quaternion128 +quat :: quaternion128 // Double Precision (f64) Floating Point Types -dmat2 :: distinct matrix[2, 2]f64 -dmat3 :: distinct matrix[3, 3]f64 -dmat4 :: distinct matrix[4, 4]f64 +dmat2 :: matrix[2, 2]f64 +dmat3 :: matrix[3, 3]f64 +dmat4 :: matrix[4, 4]f64 dmat2x2 :: dmat2 dmat3x3 :: dmat3 dmat4x4 :: dmat4 -dmat3x2 :: distinct matrix[2, 3]f64 -dmat4x2 :: distinct matrix[2, 4]f64 -dmat2x3 :: distinct matrix[3, 2]f64 -dmat4x3 :: distinct matrix[3, 4]f64 -dmat2x4 :: distinct matrix[4, 2]f64 -dmat3x4 :: distinct matrix[4, 3]f64 +dmat3x2 :: matrix[2, 3]f64 +dmat4x2 :: matrix[2, 4]f64 +dmat2x3 :: matrix[3, 2]f64 +dmat4x3 :: matrix[3, 4]f64 +dmat2x4 :: matrix[4, 2]f64 +dmat3x4 :: matrix[4, 3]f64 -dvec2 :: distinct [2]f64 -dvec3 :: distinct [3]f64 -dvec4 :: distinct [4]f64 +dvec2 :: [2]f64 +dvec3 :: [3]f64 +dvec4 :: [4]f64 -dquat :: distinct quaternion256 +dquat :: quaternion256 cos :: proc{ cos_f32, diff --git a/core/math/linalg/hlsl/linalg_hlsl.odin b/core/math/linalg/hlsl/linalg_hlsl.odin index f5e8bf147..a89fdddd3 100644 --- a/core/math/linalg/hlsl/linalg_hlsl.odin +++ b/core/math/linalg/hlsl/linalg_hlsl.odin @@ -21,89 +21,89 @@ LN10 :: 2.30258509299404568401799145468436421 FLOAT_EPSILON :: 1e-7 DOUBLE_EPSILON :: 1e-15 -// Aliases (not distinct) of types +// Aliases (not distict) of types float :: f32 double :: f64 int :: builtin.i32 uint :: builtin.u32 // Odin matrices are stored internally as Column-Major, which matches the internal layout of HLSL by default -float1x1 :: distinct matrix[1, 1]float -float2x2 :: distinct matrix[2, 2]float -float3x3 :: distinct matrix[3, 3]float -float4x4 :: distinct matrix[4, 4]float - -float1x2 :: distinct matrix[1, 2]float -float1x3 :: distinct matrix[1, 3]float -float1x4 :: distinct matrix[1, 4]float -float2x1 :: distinct matrix[2, 1]float -float2x3 :: distinct matrix[2, 3]float -float2x4 :: distinct matrix[2, 4]float -float3x1 :: distinct matrix[3, 1]float -float3x2 :: distinct matrix[3, 2]float -float3x4 :: distinct matrix[3, 4]float -float4x1 :: distinct matrix[4, 1]float -float4x2 :: distinct matrix[4, 2]float -float4x3 :: distinct matrix[4, 3]float - -float2 :: distinct [2]float -float3 :: distinct [3]float -float4 :: distinct [4]float - -int2 :: distinct [2]int -int3 :: distinct [3]int -int4 :: distinct [4]int - -uint2 :: distinct [2]uint -uint3 :: distinct [3]uint -uint4 :: distinct [4]uint - -bool2 :: distinct [2]bool -bool3 :: distinct [3]bool -bool4 :: distinct [4]bool +float1x1 :: matrix[1, 1]float +float2x2 :: matrix[2, 2]float +float3x3 :: matrix[3, 3]float +float4x4 :: matrix[4, 4]float + +float1x2 :: matrix[1, 2]float +float1x3 :: matrix[1, 3]float +float1x4 :: matrix[1, 4]float +float2x1 :: matrix[2, 1]float +float2x3 :: matrix[2, 3]float +float2x4 :: matrix[2, 4]float +float3x1 :: matrix[3, 1]float +float3x2 :: matrix[3, 2]float +float3x4 :: matrix[3, 4]float +float4x1 :: matrix[4, 1]float +float4x2 :: matrix[4, 2]float +float4x3 :: matrix[4, 3]float + +float2 :: [2]float +float3 :: [3]float +float4 :: [4]float + +int2 :: [2]int +int3 :: [3]int +int4 :: [4]int + +uint2 :: [2]uint +uint3 :: [3]uint +uint4 :: [4]uint + +bool2 :: [2]bool +bool3 :: [3]bool +bool4 :: [4]bool // Double Precision (double) Floating Point Types -double1x1 :: distinct matrix[1, 1]double -double2x2 :: distinct matrix[2, 2]double -double3x3 :: distinct matrix[3, 3]double -double4x4 :: distinct matrix[4, 4]double - -double1x2 :: distinct matrix[1, 2]double -double1x3 :: distinct matrix[1, 3]double -double1x4 :: distinct matrix[1, 4]double -double2x1 :: distinct matrix[2, 1]double -double2x3 :: distinct matrix[2, 3]double -double2x4 :: distinct matrix[2, 4]double -double3x1 :: distinct matrix[3, 1]double -double3x2 :: distinct matrix[3, 2]double -double3x4 :: distinct matrix[3, 4]double -double4x1 :: distinct matrix[4, 1]double -double4x2 :: distinct matrix[4, 2]double -double4x3 :: distinct matrix[4, 3]double - -double2 :: distinct [2]double -double3 :: distinct [3]double -double4 :: distinct [4]double - - -int1x1 :: distinct matrix[1, 1]int -int2x2 :: distinct matrix[2, 2]int -int3x3 :: distinct matrix[3, 3]int -int4x4 :: distinct matrix[4, 4]int - -int1x2 :: distinct matrix[1, 2]int -int1x3 :: distinct matrix[1, 3]int -int1x4 :: distinct matrix[1, 4]int -int2x1 :: distinct matrix[2, 1]int -int2x3 :: distinct matrix[2, 3]int -int2x4 :: distinct matrix[2, 4]int -int3x1 :: distinct matrix[3, 1]int -int3x2 :: distinct matrix[3, 2]int -int3x4 :: distinct matrix[3, 4]int -int4x1 :: distinct matrix[4, 1]int -int4x2 :: distinct matrix[4, 2]int -int4x3 :: distinct matrix[4, 3]int +double1x1 :: matrix[1, 1]double +double2x2 :: matrix[2, 2]double +double3x3 :: matrix[3, 3]double +double4x4 :: matrix[4, 4]double + +double1x2 :: matrix[1, 2]double +double1x3 :: matrix[1, 3]double +double1x4 :: matrix[1, 4]double +double2x1 :: matrix[2, 1]double +double2x3 :: matrix[2, 3]double +double2x4 :: matrix[2, 4]double +double3x1 :: matrix[3, 1]double +double3x2 :: matrix[3, 2]double +double3x4 :: matrix[3, 4]double +double4x1 :: matrix[4, 1]double +double4x2 :: matrix[4, 2]double +double4x3 :: matrix[4, 3]double + +double2 :: [2]double +double3 :: [3]double +double4 :: [4]double + + +int1x1 :: matrix[1, 1]int +int2x2 :: matrix[2, 2]int +int3x3 :: matrix[3, 3]int +int4x4 :: matrix[4, 4]int + +int1x2 :: matrix[1, 2]int +int1x3 :: matrix[1, 3]int +int1x4 :: matrix[1, 4]int +int2x1 :: matrix[2, 1]int +int2x3 :: matrix[2, 3]int +int2x4 :: matrix[2, 4]int +int3x1 :: matrix[3, 1]int +int3x2 :: matrix[3, 2]int +int3x4 :: matrix[3, 4]int +int4x1 :: matrix[4, 1]int +int4x2 :: matrix[4, 2]int +int4x3 :: matrix[4, 3]int cos :: proc{ cos_float, diff --git a/core/math/math.odin b/core/math/math.odin index 957e1672b..0e21afa67 100644 --- a/core/math/math.odin +++ b/core/math/math.odin @@ -406,6 +406,12 @@ remap :: proc "contextless" (old_value, old_min, old_max, new_min, new_max: $T) } @(require_results) +remap_clamped :: proc "contextless" (old_value, old_min, old_max, new_min, new_max: $T) -> (x: T) where intrinsics.type_is_numeric(T), !intrinsics.type_is_array(T) { + remapped := #force_inline remap(old_value, old_min, old_max, new_min, new_max) + return clamp(remapped, new_min, new_max) +} + +@(require_results) wrap :: proc "contextless" (x, y: $T) -> T where intrinsics.type_is_numeric(T), !intrinsics.type_is_array(T) { tmp := mod(x, y) return y + tmp if tmp < 0 else tmp @@ -438,11 +444,11 @@ bias :: proc "contextless" (t, b: $T) -> T where intrinsics.type_is_numeric(T) { return t / (((1/b) - 2) * (1 - t) + 1) } @(require_results) -gain :: proc "contextless" (t, g: $T) -> T where intrinsics.type_is_numeric(T) { +gain :: proc "contextless" (t, g: $T) -> T where intrinsics.type_is_float(T) { if t < 0.5 { - return bias(t*2, g)*0.5 + return bias(t*2, g) * 0.5 } - return bias(t*2 - 1, 1 - g)*0.5 + 0.5 + return bias(t*2 - 1, 1 - g) * 0.5 + 0.5 } diff --git a/core/math/math_basic.odin b/core/math/math_basic.odin index 041efd272..2584df71f 100644 --- a/core/math/math_basic.odin +++ b/core/math/math_basic.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package math import "base:intrinsics" diff --git a/core/math/math_basic_js.odin b/core/math/math_basic_js.odin index 5b9adabcd..2604ebc8b 100644 --- a/core/math/math_basic_js.odin +++ b/core/math/math_basic_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package math import "base:intrinsics" diff --git a/core/math/noise/internal.odin b/core/math/noise/internal.odin index bd97bd45c..f75c0ee87 100644 --- a/core/math/noise/internal.odin +++ b/core/math/noise/internal.odin @@ -4,7 +4,7 @@ Ported from https://github.com/KdotJPG/OpenSimplex2. Copyright 2022 Yuki2 (https://github.com/NoahR02) */ -//+private +#+private package math_noise /* diff --git a/core/math/noise/opensimplex2.odin b/core/math/noise/opensimplex2.odin index d28356f2c..634c32948 100644 --- a/core/math/noise/opensimplex2.odin +++ b/core/math/noise/opensimplex2.odin @@ -1,8 +1,8 @@ /* OpenSimplex2 noise implementation. - Ported from https://github.com/KdotJPG/OpenSimplex2. - Copyright 2022 Yuki2 (https://github.com/NoahR02) + Ported from [[ https://github.com/KdotJPG/OpenSimplex2 }]. + Copyright 2022 Yuki2 [[ https://github.com/NoahR02 ]] */ package math_noise @@ -177,4 +177,4 @@ noise_4d_fallback :: proc(seed: i64, coord: Vec4) -> (value: f32) { // Get points for A4 lattice skew := f64(SKEW_4D) * (coord.x + coord.y + coord.z + coord.w) return _internal_noise_4d_unskewed_base(seed, coord + skew) -}
\ No newline at end of file +} diff --git a/core/mem/alloc.odin b/core/mem/alloc.odin index acd77241f..fac58daaf 100644 --- a/core/mem/alloc.odin +++ b/core/mem/alloc.odin @@ -2,109 +2,711 @@ package mem import "base:runtime" -// NOTE(bill, 2019-12-31): These are defined in `package runtime` as they are used in the `context`. This is to prevent an import definition cycle. -Allocator_Mode :: runtime.Allocator_Mode +//NOTE(bill, 2019-12-31): These are defined in `package runtime` as they are used in the `context`. This is to prevent an import definition cycle. + /* -Allocator_Mode :: enum byte { - Alloc, - Free, - Free_All, - Resize, - Query_Features, - Alloc_Non_Zeroed, - Resize_Non_Zeroed, -} +A request to allocator procedure. + +This type represents a type of allocation request made to an allocator +procedure. There is one allocator procedure per allocator, and this value is +used to discriminate between different functions of the allocator. + +The type is defined as follows: + + Allocator_Mode :: enum byte { + Alloc, + Alloc_Non_Zeroed, + Free, + Free_All, + Resize, + Resize_Non_Zeroed, + Query_Features, + } + +Depending on which value is used, the allocator procedure will perform different +functions: + +- `Alloc`: Allocates a memory region with a given `size` and `alignment`. +- `Alloc_Non_Zeroed`: Same as `Alloc` without explicit zero-initialization of + the memory region. +- `Free`: Free a memory region located at address `ptr` with a given `size`. +- `Free_All`: Free all memory allocated using this allocator. +- `Resize`: Resize a memory region located at address `old_ptr` with size + `old_size` to be `size` bytes in length and have the specified `alignment`, + in case a re-alllocation occurs. +- `Resize_Non_Zeroed`: Same as `Resize`, without explicit zero-initialization. */ +Allocator_Mode :: runtime.Allocator_Mode -Allocator_Mode_Set :: runtime.Allocator_Mode_Set /* -Allocator_Mode_Set :: distinct bit_set[Allocator_Mode]; +A set of allocator features. + +This type represents values that contain a set of features an allocator has. +Currently the type is defined as follows: + + Allocator_Mode_Set :: distinct bit_set[Allocator_Mode]; */ +Allocator_Mode_Set :: runtime.Allocator_Mode_Set -Allocator_Query_Info :: runtime.Allocator_Query_Info /* -Allocator_Query_Info :: struct { - pointer: rawptr, - size: Maybe(int), - alignment: Maybe(int), -} +Allocator information. + +This type represents information about a given allocator at a specific point +in time. Currently the type is defined as follows: + + Allocator_Query_Info :: struct { + pointer: rawptr, + size: Maybe(int), + alignment: Maybe(int), + } + +- `pointer`: Pointer to a backing buffer. +- `size`: Size of the backing buffer. +- `alignment`: The allocator's alignment. + +If not applicable, any of these fields may be `nil`. */ +Allocator_Query_Info :: runtime.Allocator_Query_Info + +/* +An allocation request error. +This type represents error values the allocators may return upon requests. + + Allocator_Error :: enum byte { + None = 0, + Out_Of_Memory = 1, + Invalid_Pointer = 2, + Invalid_Argument = 3, + Mode_Not_Implemented = 4, + } + +The meaning of the errors is as follows: + +- `None`: No error. +- `Out_Of_Memory`: Either: + 1. The allocator has ran out of the backing buffer, or the requested + allocation size is too large to fit into a backing buffer. + 2. The operating system error during memory allocation. + 3. The backing allocator was used to allocate a new backing buffer and the + backing allocator returned Out_Of_Memory. +- `Invalid_Pointer`: The pointer referring to a memory region does not belong + to any of the allocators backing buffers or does not point to a valid start + of an allocation made in that allocator. +- `Invalid_Argument`: Can occur if one of the arguments makes it impossible to + satisfy a request (i.e. having alignment larger than the backing buffer + of the allocation). +- `Mode_Not_Implemented`: The allocator does not support the specified + operation. For example, an arena does not support freeing individual + allocations. +*/ Allocator_Error :: runtime.Allocator_Error + /* -Allocator_Error :: enum byte { - None = 0, - Out_Of_Memory = 1, - Invalid_Pointer = 2, - Invalid_Argument = 3, - Mode_Not_Implemented = 4, -} +The allocator procedure. + +This type represents allocation procedures. An allocation procedure is a single +procedure, implementing all allocator functions such as allocating the memory, +freeing the memory, etc. + +Currently the type is defined as follows: + + Allocator_Proc :: #type proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size: int, + alignment: int, + old_memory: rawptr, + old_size: int, + location: Source_Code_Location = #caller_location, + ) -> ([]byte, Allocator_Error); + +The function of this procedure and the meaning of parameters depends on the +value of the `mode` parameter. For any operation the following constraints +apply: + +- The `alignment` must be a power of two. +- The `size` must be a positive integer. + +## 1. `.Alloc`, `.Alloc_Non_Zeroed` + +Allocates a memory region of size `size`, aligned on a boundary specified by +`alignment`. + +**Inputs**: +- `allocator_data`: Pointer to the allocator data. +- `mode`: `.Alloc` or `.Alloc_Non_Zeroed`. +- `size`: The desired size of the memory region. +- `alignment`: The desired alignmnet of the allocation. +- `old_memory`: Unused, should be `nil`. +- `old_size`: Unused, should be 0. + +**Returns**: +1. The memory region, if allocated successfully, or `nil` otherwise. +2. An error, if allocation failed. + +**Note**: The nil allocator may return `nil`, even if no error is returned. +Always check both the error and the allocated buffer. + +**Note**: The `.Alloc` mode is required to be implemented for an allocator +and can not return a `.Mode_Not_Implemented` error. + +## 2. `Free` + +Frees a memory region located at the address specified by `old_memory`. If the +allocator does not track sizes of allocations, the size should be specified in +the `old_size` parameter. + +**Inputs**: +- `allocator_data`: Pointer to the allocator data. +- `mode`: `.Free`. +- `size`: Unused, should be 0. +- `alignment`: Unused, should be 0. +- `old_memory`: Pointer to the memory region to free. +- `old_size`: The size of the memory region to free. This parameter is optional + if the allocator keeps track of the sizes of allocations. + +**Returns**: +1. `nil` +2. Error, if freeing failed. + +## 3. `Free_All` + +Frees all allocations, associated with the allocator, making it available for +further allocations using the same backing buffers. + +**Inputs**: +- `allocator_data`: Pointer to the allocator data. +- `mode`: `.Free_All`. +- `size`: Unused, should be 0. +- `alignment`: Unused, should be 0. +- `old_memory`: Unused, should be `nil`. +- `old_size`: Unused, should be `0`. + +**Returns**: +1. `nil`. +2. Error, if freeing failed. + +## 4. `Resize`, `Resize_Non_Zeroed` + +Resizes the memory region, of the size `old_size` located at the address +specified by `old_memory` to have the new size `size`. The slice of the new +memory region is returned from the procedure. The allocator may attempt to +keep the new memory region at the same address as the previous allocation, +however no such guarantee is made. Do not assume the new memory region will +be at the same address as the old memory region. + +If `old_memory` pointer is `nil`, this function acts just like `.Alloc` or +`.Alloc_Non_Zeroed`, using `size` and `alignment` to allocate a new memory +region. + +If `new_size` is `nil`, the procedure acts just like `.Free`, freeing the +memory region `old_size` bytes in length, located at the address specified by +`old_memory`. + +If the `old_memory` pointer is not aligned to the boundary specified by +`alignment`, the procedure relocates the buffer such that the reallocated +buffer is aligned to the boundary specified by `alignment`. + +**Inputs**: +- `allocator_data`: Pointer to the allocator data. +- `mode`: `.Resize` or `.Resize_All`. +- `size`: The desired new size of the memory region. +- `alignment`: The alignment of the new memory region, if its allocated +- `old_memory`: The pointer to the memory region to resize. +- `old_size`: The size of the memory region to resize. If the allocator + keeps track of the sizes of allocations, this parameter is optional. + +**Returns**: +1. The slice of the memory region after resize operation, if successfull, + `nil` otherwise. +2. An error, if the resize failed. + +**Note**: Some allocators may return `nil`, even if no error is returned. +Always check both the error and the allocated buffer. + +**Note**: if `old_size` is `0` and `old_memory` is `nil`, this operation is a +no-op, and should not return errors. */ Allocator_Proc :: runtime.Allocator_Proc + /* -Allocator_Proc :: #type proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, location: Source_Code_Location = #caller_location) -> ([]byte, Allocator_Error); -*/ +Allocator. + +This type represents generic interface for all allocators. Currently this type +is defined as follows: + Allocator :: struct { + procedure: Allocator_Proc, + data: rawptr, + } + +- `procedure`: Pointer to the allocation procedure. +- `data`: Pointer to the allocator data. +*/ Allocator :: runtime.Allocator + /* -Allocator :: struct { - procedure: Allocator_Proc, - data: rawptr, -} -*/ +Default alignment. +This value is the default alignment for all platforms that is used, if the +alignment is not specified explicitly. +*/ DEFAULT_ALIGNMENT :: 2*align_of(rawptr) +/* +Default page size. + +This value is the default page size for the current platform. +*/ DEFAULT_PAGE_SIZE :: 64 * 1024 when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 else 16 * 1024 when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 else 4 * 1024 +/* +Allocate memory. + +This function allocates `size` bytes of memory, aligned to a boundary specified +by `alignment` using the allocator specified by `allocator`. + +If the `size` parameter is `0`, the operation is a no-op. + +**Inputs**: +- `size`: The desired size of the allocated memory region. +- `alignment`: The desired alignment of the allocated memory region. +- `allocator`: The allocator to allocate from. + +**Returns**: +1. Pointer to the allocated memory, or `nil` if allocation failed. +2. Error, if the allocation failed. + +**Errors**: +- `None`: If no error occurred. +- `Out_Of_Memory`: Occurs when the allocator runs out of space in any of its + backing buffers, the backing allocator has ran out of space, or an operating + system failure occurred. +- `Invalid_Argument`: If the supplied `size` is negative, alignment is not a + power of two. +*/ @(require_results) -alloc :: proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (rawptr, Allocator_Error) { +alloc :: proc( + size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { data, err := runtime.mem_alloc(size, alignment, allocator, loc) return raw_data(data), err } +/* +Allocate memory. + +This function allocates `size` bytes of memory, aligned to a boundary specified +by `alignment` using the allocator specified by `allocator`. + +**Inputs**: +- `size`: The desired size of the allocated memory region. +- `alignment`: The desired alignment of the allocated memory region. +- `allocator`: The allocator to allocate from. + +**Returns**: +1. Slice of the allocated memory region, or `nil` if allocation failed. +2. Error, if the allocation failed. + +**Errors**: +- `None`: If no error occurred. +- `Out_Of_Memory`: Occurs when the allocator runs out of space in any of its + backing buffers, the backing allocator has ran out of space, or an operating + system failure occurred. +- `Invalid_Argument`: If the supplied `size` is negative, alignment is not a + power of two. +*/ @(require_results) -alloc_bytes :: proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +alloc_bytes :: proc( + size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { return runtime.mem_alloc(size, alignment, allocator, loc) } +/* +Allocate non-zeroed memory. + +This function allocates `size` bytes of memory, aligned to a boundary specified +by `alignment` using the allocator specified by `allocator`. This procedure +does not explicitly zero-initialize allocated memory region. + +**Inputs**: +- `size`: The desired size of the allocated memory region. +- `alignment`: The desired alignment of the allocated memory region. +- `allocator`: The allocator to allocate from. + +**Returns**: +1. Slice of the allocated memory region, or `nil` if allocation failed. +2. Error, if the allocation failed. + +**Errors**: +- `None`: If no error occurred. +- `Out_Of_Memory`: Occurs when the allocator runs out of space in any of its + backing buffers, the backing allocator has ran out of space, or an operating + system failure occurred. +- `Invalid_Argument`: If the supplied `size` is negative, alignment is not a + power of two. +*/ @(require_results) -alloc_bytes_non_zeroed :: proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +alloc_bytes_non_zeroed :: proc( + size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { return runtime.mem_alloc_non_zeroed(size, alignment, allocator, loc) } -free :: proc(ptr: rawptr, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { +/* +Free memory. + +This procedure frees memory region located at the address, specified by `ptr`, +allocated from the allocator specified by `allocator`. + +**Inputs**: +- `ptr`: Pointer to the memory region to free. +- `allocator`: The allocator to free to. + +**Returns**: +- The error, if freeing failed. + +**Errors**: +- `None`: When no error has occurred. +- `Invalid_Pointer`: The specified pointer is not owned by the specified allocator, + or does not point to a valid allocation. +- `Mode_Not_Implemented`: If the specified allocator does not support the `.Free` +mode. +*/ +free :: proc( + ptr: rawptr, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { return runtime.mem_free(ptr, allocator, loc) } -free_with_size :: proc(ptr: rawptr, byte_count: int, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - return runtime.mem_free_with_size(ptr, byte_count, allocator, loc) +/* +Free a memory region. + +This procedure frees `size` bytes of memory region located at the address, +specified by `ptr`, allocated from the allocator specified by `allocator`. + +If the `size` parameter is `0`, this call is equivalent to `free()`. + +**Inputs**: +- `ptr`: Pointer to the memory region to free. +- `size`: The size of the memory region to free. +- `allocator`: The allocator to free to. + +**Returns**: +- The error, if freeing failed. + +**Errors**: +- `None`: When no error has occurred. +- `Invalid_Pointer`: The specified pointer is not owned by the specified allocator, + or does not point to a valid allocation. +- `Mode_Not_Implemented`: If the specified allocator does not support the `.Free` +mode. +*/ +free_with_size :: proc( + ptr: rawptr, + size: int, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { + return runtime.mem_free_with_size(ptr, size, allocator, loc) } -free_bytes :: proc(bytes: []byte, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { +/* +Free a memory region. + +This procedure frees memory region, specified by `bytes`, allocated from the +allocator specified by `allocator`. + +If the length of the specified slice is zero, the `.Invalid_Argument` error +is returned. + +**Inputs**: +- `bytes`: The memory region to free. +- `allocator`: The allocator to free to. + +**Returns**: +- The error, if freeing failed. + +**Errors**: +- `None`: When no error has occurred. +- `Invalid_Pointer`: The specified pointer is not owned by the specified allocator, + or does not point to a valid allocation. +- `Mode_Not_Implemented`: If the specified allocator does not support the `.Free` +mode. +*/ +free_bytes :: proc( + bytes: []byte, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { return runtime.mem_free_bytes(bytes, allocator, loc) } +/* +Free all allocations. + +This procedure frees all allocations made on the allocator specified by +`allocator` to that allocator, making it available for further allocations. + +**Inputs**: +- `allocator`: The allocator to free to. + +**Errors**: +- `None`: When no error has occurred. +- `Mode_Not_Implemented`: If the specified allocator does not support the `.Free` +mode. +*/ free_all :: proc(allocator := context.allocator, loc := #caller_location) -> Allocator_Error { return runtime.mem_free_all(allocator, loc) } +/* +Resize a memory region. + +This procedure resizes a memory region, `old_size` bytes in size, located at +the address specified by `ptr`, such that it has a new size, specified by +`new_size` and and is aligned on a boundary specified by `alignment`. + +If the `ptr` parameter is `nil`, `resize()` acts just like `alloc()`, allocating +`new_size` bytes, aligned on a boundary specified by `alignment`. + +If the `new_size` parameter is `0`, `resize()` acts just like `free()`, freeing +the memory region `old_size` bytes in length, located at the address specified +by `ptr`. + +If the `old_memory` pointer is not aligned to the boundary specified by +`alignment`, the procedure relocates the buffer such that the reallocated +buffer is aligned to the boundary specified by `alignment`. + +**Inputs**: +- `ptr`: Pointer to the memory region to resize. +- `old_size`: Size of the memory region to resize. +- `new_size`: The desired size of the resized memory region. +- `alignment`: The desired alignment of the resized memory region. +- `allocator`: The owner of the memory region to resize. + +**Returns**: +1. The pointer to the resized memory region, if successfull, `nil` otherwise. +2. Error, if resize failed. + +**Errors**: +- `None`: No error. +- `Out_Of_Memory`: When the allocator's backing buffer or it's backing + allocator does not have enough space to fit in an allocation with the new + size, or an operating system failure occurs. +- `Invalid_Pointer`: The pointer referring to a memory region does not belong + to any of the allocators backing buffers or does not point to a valid start + of an allocation made in that allocator. +- `Invalid_Argument`: When `size` is negative, alignment is not a power of two, + or the `old_size` argument is incorrect. +- `Mode_Not_Implemented`: The allocator does not support the `.Realloc` mode. + +**Note**: if `old_size` is `0` and `old_memory` is `nil`, this operation is a +no-op, and should not return errors. +*/ @(require_results) -resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (rawptr, Allocator_Error) { +resize :: proc( + ptr: rawptr, + old_size: int, + new_size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { data, err := runtime.mem_resize(ptr, old_size, new_size, alignment, allocator, loc) return raw_data(data), err } +/* +Resize a memory region without zero-initialization. + +This procedure resizes a memory region, `old_size` bytes in size, located at +the address specified by `ptr`, such that it has a new size, specified by +`new_size` and and is aligned on a boundary specified by `alignment`. + +If the `ptr` parameter is `nil`, `resize()` acts just like `alloc()`, allocating +`new_size` bytes, aligned on a boundary specified by `alignment`. + +If the `new_size` parameter is `0`, `resize()` acts just like `free()`, freeing +the memory region `old_size` bytes in length, located at the address specified +by `ptr`. + +If the `old_memory` pointer is not aligned to the boundary specified by +`alignment`, the procedure relocates the buffer such that the reallocated +buffer is aligned to the boundary specified by `alignment`. + +Unlike `resize()`, this procedure does not explicitly zero-initialize any new +memory. + +**Inputs**: +- `ptr`: Pointer to the memory region to resize. +- `old_size`: Size of the memory region to resize. +- `new_size`: The desired size of the resized memory region. +- `alignment`: The desired alignment of the resized memory region. +- `allocator`: The owner of the memory region to resize. + +**Returns**: +1. The pointer to the resized memory region, if successfull, `nil` otherwise. +2. Error, if resize failed. + +**Errors**: +- `None`: No error. +- `Out_Of_Memory`: When the allocator's backing buffer or it's backing + allocator does not have enough space to fit in an allocation with the new + size, or an operating system failure occurs. +- `Invalid_Pointer`: The pointer referring to a memory region does not belong + to any of the allocators backing buffers or does not point to a valid start + of an allocation made in that allocator. +- `Invalid_Argument`: When `size` is negative, alignment is not a power of two, + or the `old_size` argument is incorrect. +- `Mode_Not_Implemented`: The allocator does not support the `.Realloc` mode. + +**Note**: if `old_size` is `0` and `old_memory` is `nil`, this operation is a +no-op, and should not return errors. +*/ @(require_results) -resize_bytes :: proc(old_data: []byte, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +resize_non_zeroed :: proc( + ptr: rawptr, + old_size: int, + new_size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + data, err := runtime.non_zero_mem_resize(ptr, old_size, new_size, alignment, allocator, loc) + return raw_data(data), err +} + +/* +Resize a memory region. + +This procedure resizes a memory region, specified by `old_data`, such that it +has a new size, specified by `new_size` and and is aligned on a boundary +specified by `alignment`. + +If the `old_data` parameter is `nil`, `resize_bytes()` acts just like +`alloc_bytes()`, allocating `new_size` bytes, aligned on a boundary specified +by `alignment`. + +If the `new_size` parameter is `0`, `resize_bytes()` acts just like +`free_bytes()`, freeing the memory region specified by `old_data`. + +If the `old_memory` pointer is not aligned to the boundary specified by +`alignment`, the procedure relocates the buffer such that the reallocated +buffer is aligned to the boundary specified by `alignment`. + +**Inputs**: +- `old_data`: Pointer to the memory region to resize. +- `new_size`: The desired size of the resized memory region. +- `alignment`: The desired alignment of the resized memory region. +- `allocator`: The owner of the memory region to resize. + +**Returns**: +1. The resized memory region, if successfull, `nil` otherwise. +2. Error, if resize failed. + +**Errors**: +- `None`: No error. +- `Out_Of_Memory`: When the allocator's backing buffer or it's backing + allocator does not have enough space to fit in an allocation with the new + size, or an operating system failure occurs. +- `Invalid_Pointer`: The pointer referring to a memory region does not belong + to any of the allocators backing buffers or does not point to a valid start + of an allocation made in that allocator. +- `Invalid_Argument`: When `size` is negative, alignment is not a power of two, + or the `old_size` argument is incorrect. +- `Mode_Not_Implemented`: The allocator does not support the `.Realloc` mode. + +**Note**: if `old_size` is `0` and `old_memory` is `nil`, this operation is a +no-op, and should not return errors. +*/ +@(require_results) +resize_bytes :: proc( + old_data: []byte, + new_size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { return runtime.mem_resize(raw_data(old_data), len(old_data), new_size, alignment, allocator, loc) } +/* +Resize a memory region. + +This procedure resizes a memory region, specified by `old_data`, such that it +has a new size, specified by `new_size` and and is aligned on a boundary +specified by `alignment`. + +If the `old_data` parameter is `nil`, `resize_bytes()` acts just like +`alloc_bytes()`, allocating `new_size` bytes, aligned on a boundary specified +by `alignment`. + +If the `new_size` parameter is `0`, `resize_bytes()` acts just like +`free_bytes()`, freeing the memory region specified by `old_data`. + +If the `old_memory` pointer is not aligned to the boundary specified by +`alignment`, the procedure relocates the buffer such that the reallocated +buffer is aligned to the boundary specified by `alignment`. + +Unlike `resize_bytes()`, this procedure does not explicitly zero-initialize +any new memory. + +**Inputs**: +- `old_data`: Pointer to the memory region to resize. +- `new_size`: The desired size of the resized memory region. +- `alignment`: The desired alignment of the resized memory region. +- `allocator`: The owner of the memory region to resize. + +**Returns**: +1. The resized memory region, if successfull, `nil` otherwise. +2. Error, if resize failed. + +**Errors**: +- `None`: No error. +- `Out_Of_Memory`: When the allocator's backing buffer or it's backing + allocator does not have enough space to fit in an allocation with the new + size, or an operating system failure occurs. +- `Invalid_Pointer`: The pointer referring to a memory region does not belong + to any of the allocators backing buffers or does not point to a valid start + of an allocation made in that allocator. +- `Invalid_Argument`: When `size` is negative, alignment is not a power of two, + or the `old_size` argument is incorrect. +- `Mode_Not_Implemented`: The allocator does not support the `.Realloc` mode. + +**Note**: if `old_size` is `0` and `old_memory` is `nil`, this operation is a +no-op, and should not return errors. +*/ +@(require_results) +resize_bytes_non_zeroed :: proc( + old_data: []byte, + new_size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + return runtime.non_zero_mem_resize(raw_data(old_data), len(old_data), new_size, alignment, allocator, loc) +} + +/* +Query allocator features. +*/ @(require_results) query_features :: proc(allocator: Allocator, loc := #caller_location) -> (set: Allocator_Mode_Set) { if allocator.procedure != nil { @@ -114,8 +716,15 @@ query_features :: proc(allocator: Allocator, loc := #caller_location) -> (set: A return nil } +/* +Query allocator information. +*/ @(require_results) -query_info :: proc(pointer: rawptr, allocator: Allocator, loc := #caller_location) -> (props: Allocator_Query_Info) { +query_info :: proc( + pointer: rawptr, + allocator: Allocator, + loc := #caller_location, +) -> (props: Allocator_Query_Info) { props.pointer = pointer if allocator.procedure != nil { allocator.procedure(allocator.data, .Query_Info, 0, 0, &props, 0, loc) @@ -123,25 +732,62 @@ query_info :: proc(pointer: rawptr, allocator: Allocator, loc := #caller_locatio return } - - -delete_string :: proc(str: string, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { +/* +Free a string. +*/ +delete_string :: proc( + str: string, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { return runtime.delete_string(str, allocator, loc) } -delete_cstring :: proc(str: cstring, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + +/* +Free a cstring. +*/ +delete_cstring :: proc( + str: cstring, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { return runtime.delete_cstring(str, allocator, loc) } -delete_dynamic_array :: proc(array: $T/[dynamic]$E, loc := #caller_location) -> Allocator_Error { + +/* +Free a dynamic array. +*/ +delete_dynamic_array :: proc( + array: $T/[dynamic]$E, + loc := #caller_location, +) -> Allocator_Error { return runtime.delete_dynamic_array(array, loc) } -delete_slice :: proc(array: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { + +/* +Free a slice. +*/ +delete_slice :: proc( + array: $T/[]$E, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { return runtime.delete_slice(array, allocator, loc) } -delete_map :: proc(m: $T/map[$K]$V, loc := #caller_location) -> Allocator_Error { + +/* +Free a map. +*/ +delete_map :: proc( + m: $T/map[$K]$V, + loc := #caller_location, +) -> Allocator_Error { return runtime.delete_map(m, loc) } - +/* +Free. +*/ delete :: proc{ delete_string, delete_cstring, @@ -150,49 +796,177 @@ delete :: proc{ delete_map, } +/* +Allocate a new object. +This procedure allocates a new object of type `T` using an allocator specified +by `allocator`, and returns a pointer to the allocated object, if allocated +successfully, or `nil` otherwise. +*/ @(require_results) -new :: proc($T: typeid, allocator := context.allocator, loc := #caller_location) -> (^T, Allocator_Error) { +new :: proc( + $T: typeid, + allocator := context.allocator, + loc := #caller_location, +) -> (^T, Allocator_Error) { return new_aligned(T, align_of(T), allocator, loc) } + +/* +Allocate a new object with alignment. + +This procedure allocates a new object of type `T` using an allocator specified +by `allocator`, and returns a pointer, aligned on a boundary specified by +`alignment` to the allocated object, if allocated successfully, or `nil` +otherwise. +*/ @(require_results) -new_aligned :: proc($T: typeid, alignment: int, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) { +new_aligned :: proc( + $T: typeid, + alignment: int, + allocator := context.allocator, + loc := #caller_location, +) -> (t: ^T, err: Allocator_Error) { return runtime.new_aligned(T, alignment, allocator, loc) } + +/* +Allocate a new object and initialize it with a value. + +This procedure allocates a new object of type `T` using an allocator specified +by `allocator`, and returns a pointer, aligned on a boundary specified by +`alignment` to the allocated object, if allocated successfully, or `nil` +otherwise. The allocated object is initialized with `data`. +*/ @(require_results) -new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) { +new_clone :: proc( + data: $T, + allocator := context.allocator, + loc := #caller_location, +) -> (t: ^T, err: Allocator_Error) { return runtime.new_clone(data, allocator, loc) } +/* +Allocate a new slice with alignment. + +This procedure allocates a new slice of type `T` with length `len`, aligned +on a boundary specified by `alignment` from an allocator specified by +`allocator`, and returns the allocated slice. +*/ @(require_results) -make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (slice: T, err: Allocator_Error) { +make_aligned :: proc( + $T: typeid/[]$E, + #any_int len: int, + alignment: int, + allocator := context.allocator, + loc := #caller_location, +) -> (slice: T, err: Allocator_Error) { return runtime.make_aligned(T, len, alignment, allocator, loc) } + +/* +Allocate a new slice. + +This procedure allocates a new slice of type `T` with length `len`, from an +allocator specified by `allocator`, and returns the allocated slice. +*/ @(require_results) -make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) { +make_slice :: proc( + $T: typeid/[]$E, + #any_int len: int, + allocator := context.allocator, + loc := #caller_location, +) -> (T, Allocator_Error) { return runtime.make_slice(T, len, allocator, loc) } + +/* +Allocate a dynamic array. + +This procedure creates a dynamic array of type `T`, with `allocator` as its +backing allocator, and initial length and capacity of `0`. +*/ @(require_results) -make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) { +make_dynamic_array :: proc( + $T: typeid/[dynamic]$E, + allocator := context.allocator, + loc := #caller_location, +) -> (T, Allocator_Error) { return runtime.make_dynamic_array(T, allocator, loc) } + +/* +Allocate a dynamic array with initial length. + +This procedure creates a dynamic array of type `T`, with `allocator` as its +backing allocator, and initial capacity of `0`, and initial length specified by +`len`. +*/ @(require_results) -make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) { - return runtime.make_dynamic_array(T, len, allocator, loc) +make_dynamic_array_len :: proc( + $T: typeid/[dynamic]$E, + #any_int len: int, + allocator := context.allocator, + loc := #caller_location, +) -> (T, Allocator_Error) { + return runtime.make_dynamic_array_len_cap(T, len, len, allocator, loc) } + +/* +Allocate a dynamic array with initial length and capacity. + +This procedure creates a dynamic array of type `T`, with `allocator` as its +backing allocator, and initial capacity specified by `cap`, and initial length +specified by `len`. +*/ @(require_results) -make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) { - return runtime.make_dynamic_array(T, len, cap, allocator, loc) +make_dynamic_array_len_cap :: proc( + $T: typeid/[dynamic]$E, + #any_int len: int, + #any_int cap: int, + allocator := context.allocator, + loc := #caller_location, +) -> (array: T, err: Allocator_Error) { + return runtime.make_dynamic_array_len_cap(T, len, cap, allocator, loc) } + +/* +Allocate a map. + +This procedure creates a map of type `T` with initial capacity specified by +`cap`, that is using an allocator specified by `allocator` as its backing +allocator. +*/ @(require_results) -make_map :: proc($T: typeid/map[$K]$E, #any_int cap: int = 1<<runtime.MAP_MIN_LOG2_CAPACITY, allocator := context.allocator, loc := #caller_location) -> (m: T, err: Allocator_Error) { +make_map :: proc( + $T: typeid/map[$K]$E, + #any_int cap: int = 1<<runtime.MAP_MIN_LOG2_CAPACITY, + allocator := context.allocator, + loc := #caller_location, +) -> (m: T, err: Allocator_Error) { return runtime.make_map(T, cap, allocator, loc) } + +/* +Allocate a multi pointer. + +This procedure allocates a multipointer of type `T` pointing to `len` elements, +from an allocator specified by `allocator`. +*/ @(require_results) -make_multi_pointer :: proc($T: typeid/[^]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (mp: T, err: Allocator_Error) { +make_multi_pointer :: proc( + $T: typeid/[^]$E, + #any_int len: int, + allocator := context.allocator, + loc := #caller_location +) -> (mp: T, err: Allocator_Error) { return runtime.make_multi_pointer(T, len, allocator, loc) } +/* +Allocate. +*/ make :: proc{ make_slice, make_dynamic_array, @@ -202,26 +976,112 @@ make :: proc{ make_multi_pointer, } +/* +Default resize procedure. + +When allocator does not support resize operation, but supports `.Alloc` and +`.Free`, this procedure is used to implement allocator's default behavior on +resize. +The behavior of the function is as follows: + +- If `new_size` is `0`, the function acts like `free()`, freeing the memory + region of `old_size` bytes located at `old_memory`. +- If `old_memory` is `nil`, the function acts like `alloc()`, allocating + `new_size` bytes of memory aligned on a boundary specified by `alignment`. +- Otherwise, a new memory region of size `new_size` is allocated, then the + data from the old memory region is copied and the old memory region is + freed. +*/ @(require_results) -default_resize_align :: proc(old_memory: rawptr, old_size, new_size, alignment: int, allocator := context.allocator, loc := #caller_location) -> (res: rawptr, err: Allocator_Error) { +default_resize_align :: proc( + old_memory: rawptr, + old_size: int, + new_size: int, + alignment: int, + allocator := context.allocator, + loc := #caller_location, +) -> (res: rawptr, err: Allocator_Error) { data: []byte - data, err = default_resize_bytes_align(([^]byte)(old_memory)[:old_size], new_size, alignment, allocator, loc) + data, err = default_resize_bytes_align( + ([^]byte) (old_memory)[:old_size], + new_size, + alignment, + allocator, + loc, + ) res = raw_data(data) return } +/* +Default resize procedure. + +When allocator does not support resize operation, but supports +`.Alloc_Non_Zeroed` and `.Free`, this procedure is used to implement allocator's +default behavior on resize. + +Unlike `default_resize_align` no new memory is being explicitly +zero-initialized. + +The behavior of the function is as follows: + +- If `new_size` is `0`, the function acts like `free()`, freeing the memory + region of `old_size` bytes located at `old_memory`. +- If `old_memory` is `nil`, the function acts like `alloc()`, allocating + `new_size` bytes of memory aligned on a boundary specified by `alignment`. +- Otherwise, a new memory region of size `new_size` is allocated, then the + data from the old memory region is copied and the old memory region is + freed. +*/ @(require_results) -default_resize_bytes_align_non_zeroed :: proc(old_data: []byte, new_size, alignment: int, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +default_resize_bytes_align_non_zeroed :: proc( + old_data: []byte, + new_size: int, + alignment: int, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { return _default_resize_bytes_align(old_data, new_size, alignment, false, allocator, loc) } + +/* +Default resize procedure. + +When allocator does not support resize operation, but supports `.Alloc` and +`.Free`, this procedure is used to implement allocator's default behavior on +resize. + +The behavior of the function is as follows: + +- If `new_size` is `0`, the function acts like `free()`, freeing the memory + region specified by `old_data`. +- If `old_data` is `nil`, the function acts like `alloc()`, allocating + `new_size` bytes of memory aligned on a boundary specified by `alignment`. +- Otherwise, a new memory region of size `new_size` is allocated, then the + data from the old memory region is copied and the old memory region is + freed. +*/ @(require_results) -default_resize_bytes_align :: proc(old_data: []byte, new_size, alignment: int, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +default_resize_bytes_align :: proc( + old_data: []byte, + new_size: int, + alignment: int, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { return _default_resize_bytes_align(old_data, new_size, alignment, true, allocator, loc) } @(require_results) -_default_resize_bytes_align :: #force_inline proc(old_data: []byte, new_size, alignment: int, should_zero: bool, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +_default_resize_bytes_align :: #force_inline proc( + old_data: []byte, + new_size: int, + alignment: int, + should_zero: bool, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { old_memory := raw_data(old_data) old_size := len(old_data) if old_memory == nil { @@ -231,16 +1091,13 @@ _default_resize_bytes_align :: #force_inline proc(old_data: []byte, new_size, al return alloc_bytes_non_zeroed(new_size, alignment, allocator, loc) } } - if new_size == 0 { err := free_bytes(old_data, allocator, loc) return nil, err } - - if new_size == old_size { + if new_size == old_size && is_aligned(old_memory, alignment) { return old_data, .None } - new_memory : []byte err : Allocator_Error if should_zero { @@ -251,7 +1108,6 @@ _default_resize_bytes_align :: #force_inline proc(old_data: []byte, new_size, al if new_memory == nil || err != nil { return nil, err } - runtime.copy(new_memory, old_data) free_bytes(old_data, allocator, loc) return new_memory, err diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index a5b93ad05..d729b902c 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -3,12 +3,14 @@ package mem import "base:intrinsics" import "base:runtime" -nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - return nil, nil -} +/* +Nil allocator. +The `nil` allocator returns `nil` on every allocation attempt. This type of +allocator can be used in scenarios where memory doesn't need to be allocated, +but an attempt to allocate memory is not an error. +*/ +@(require_results) nil_allocator :: proc() -> Allocator { return Allocator{ procedure = nil_allocator_proc, @@ -16,8 +18,81 @@ nil_allocator :: proc() -> Allocator { } } -// Custom allocators +nil_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + return nil, nil +} + + +/* +Panic allocator. + +The panic allocator is a type of allocator that panics on any allocation +attempt. This type of allocator can be used in scenarios where memory should +not be allocated, and an attempt to allocate memory is an error. +*/ +@(require_results) +panic_allocator :: proc() -> Allocator { + return Allocator{ + procedure = panic_allocator_proc, + data = nil, + } +} + +panic_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + switch mode { + case .Alloc: + if size > 0 { + panic("mem: panic allocator, .Alloc called", loc=loc) + } + case .Alloc_Non_Zeroed: + if size > 0 { + panic("mem: panic allocator, .Alloc_Non_Zeroed called", loc=loc) + } + case .Resize: + if size > 0 { + panic("mem: panic allocator, .Resize called", loc=loc) + } + case .Resize_Non_Zeroed: + if size > 0 { + panic("mem: panic allocator, .Resize_Non_Zeroed called", loc=loc) + } + case .Free: + if old_memory != nil { + panic("mem: panic allocator, .Free called", loc=loc) + } + case .Free_All: + panic("mem: panic allocator, .Free_All called", loc=loc) + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Query_Features} + } + return nil, nil + + case .Query_Info: + panic("mem: panic allocator, .Query_Info called", loc=loc) + } + return nil, nil +} + +/* +Arena allocator data. +*/ Arena :: struct { data: []byte, offset: int, @@ -25,12 +100,39 @@ Arena :: struct { temp_count: int, } -Arena_Temp_Memory :: struct { - arena: ^Arena, - prev_offset: int, +/* +Arena allocator. + +The arena allocator (also known as a linear allocator, bump allocator, +region allocator) is an allocator that uses a single backing buffer for +allocations. + +The buffer is being used contiguously, from start by end. Each subsequent +allocation occupies the next adjacent region of memory in the buffer. Since +arena allocator does not keep track of any metadata associated with the +allocations and their locations, it is impossible to free individual +allocations. + +The arena allocator can be used for temporary allocations in frame-based memory +management. Games are one example of such applications. A global arena can be +used for any temporary memory allocations, and at the end of each frame all +temporary allocations are freed. Since no temporary object is going to live +longer than a frame, no lifetimes are violated. +*/ +@(require_results) +arena_allocator :: proc(arena: ^Arena) -> Allocator { + return Allocator{ + procedure = arena_allocator_proc, + data = arena, + } } +/* +Initialize an arena. +This procedure initializes the arena `a` with memory region `data` as it's +backing buffer. +*/ arena_init :: proc(a: ^Arena, data: []byte) { a.data = data a.offset = 0 @@ -46,64 +148,157 @@ init_arena :: proc(a: ^Arena, data: []byte) { a.temp_count = 0 } +/* +Allocate memory from an arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from an arena `a`. The allocated memory is zero-initialized. +This procedure returns a pointer to the newly allocated memory region. +*/ @(require_results) -arena_allocator :: proc(arena: ^Arena) -> Allocator { - return Allocator{ - procedure = arena_allocator_proc, - data = arena, - } +arena_alloc :: proc( + a: ^Arena, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := arena_alloc_bytes(a, size, alignment, loc) + return raw_data(bytes), err } -arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, Allocator_Error) { - arena := cast(^Arena)allocator_data +/* +Allocate memory from an arena. - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - #no_bounds_check end := &arena.data[arena.offset] +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from an arena `a`. The allocated memory is zero-initialized. +This procedure returns a slice of the newly allocated memory region. +*/ +@(require_results) +arena_alloc_bytes :: proc( + a: ^Arena, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := arena_alloc_bytes_non_zeroed(a, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} - ptr := align_forward(end, uintptr(alignment)) +/* +Allocate non-initialized memory from an arena. - total_size := size + ptr_sub((^byte)(ptr), (^byte)(end)) +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from an arena `a`. The allocated memory is not explicitly +zero-initialized. This procedure returns a pointer to the newly allocated +memory region. +*/ +@(require_results) +arena_alloc_non_zeroed :: proc( + a: ^Arena, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := arena_alloc_bytes_non_zeroed(a, size, alignment, loc) + return raw_data(bytes), err +} - if arena.offset + total_size > len(arena.data) { - return nil, .Out_Of_Memory - } +/* +Allocate non-initialized memory from an arena. - arena.offset += total_size - arena.peak_used = max(arena.peak_used, arena.offset) - if mode != .Alloc_Non_Zeroed { - zero(ptr, size) - } - return byte_slice(ptr, size), nil +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from an arena `a`. The allocated memory is not explicitly +zero-initialized. This procedure returns a slice of the newly allocated +memory region. +*/ +@(require_results) +arena_alloc_bytes_non_zeroed :: proc( + a: ^Arena, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> ([]byte, Allocator_Error) { + if a.data == nil { + panic("Arena is not initialized", loc) + } + #no_bounds_check end := &a.data[a.offset] + ptr := align_forward(end, uintptr(alignment)) + total_size := size + ptr_sub((^byte)(ptr), (^byte)(end)) + if a.offset + total_size > len(a.data) { + return nil, .Out_Of_Memory + } + a.offset += total_size + a.peak_used = max(a.peak_used, a.offset) + return byte_slice(ptr, size), nil +} + +/* +Free all memory to an arena. +*/ +arena_free_all :: proc(a: ^Arena) { + a.offset = 0 +} +arena_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size: int, + alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + arena := cast(^Arena)allocator_data + switch mode { + case .Alloc: + return arena_alloc_bytes(arena, size, alignment, loc) + case .Alloc_Non_Zeroed: + return arena_alloc_bytes_non_zeroed(arena, size, alignment, loc) case .Free: return nil, .Mode_Not_Implemented - case .Free_All: - arena.offset = 0 - + arena_free_all(arena) case .Resize: - return default_resize_bytes_align(byte_slice(old_memory, old_size), size, alignment, arena_allocator(arena)) - + return default_resize_bytes_align(byte_slice(old_memory, old_size), size, alignment, arena_allocator(arena), loc) case .Resize_Non_Zeroed: - return default_resize_bytes_align_non_zeroed(byte_slice(old_memory, old_size), size, alignment, arena_allocator(arena)) - + return default_resize_bytes_align_non_zeroed(byte_slice(old_memory, old_size), size, alignment, arena_allocator(arena), loc) case .Query_Features: set := (^Allocator_Mode_Set)(old_memory) if set != nil { set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features} } return nil, nil - case .Query_Info: return nil, .Mode_Not_Implemented } - return nil, nil } +/* +Temporary memory region of arena. + +Temporary memory regions of arena act as "savepoints" for arena. When one is +created, the subsequent allocations are done inside the temporary memory +region. When `end_arena_temp_memory` is called, the arena is rolled back, and +all of the memory that was allocated from the arena will be freed. + +Multiple temporary memory regions can exist at the same time for an arena. +*/ +Arena_Temp_Memory :: struct { + arena: ^Arena, + prev_offset: int, +} + +/* +Start a temporary memory region. + +This procedure creates a temporary memory region. After a temporary memory +region is created, all allocations are said to be *inside* the temporary memory +region, until `end_arena_temp_memory` is called. +*/ @(require_results) begin_arena_temp_memory :: proc(a: ^Arena) -> Arena_Temp_Memory { tmp: Arena_Temp_Memory @@ -113,6 +308,12 @@ begin_arena_temp_memory :: proc(a: ^Arena) -> Arena_Temp_Memory { return tmp } +/* +End a temporary memory region. + +This procedure ends the temporary memory region for an arena. All of the +allocations *inside* the temporary memory region will be freed to the arena. +*/ end_arena_temp_memory :: proc(tmp: Arena_Temp_Memory) { assert(tmp.arena.offset >= tmp.prev_offset) assert(tmp.arena.temp_count > 0) @@ -120,9 +321,15 @@ end_arena_temp_memory :: proc(tmp: Arena_Temp_Memory) { tmp.arena.temp_count -= 1 } +/* Preserved for compatibility */ +Scratch_Allocator :: Scratch +scratch_allocator_init :: scratch_init +scratch_allocator_destroy :: scratch_destroy - -Scratch_Allocator :: struct { +/* +Scratch allocator data. +*/ +Scratch :: struct { data: []byte, curr_offset: int, prev_allocation: rawptr, @@ -130,7 +337,35 @@ Scratch_Allocator :: struct { leaked_allocations: [dynamic][]byte, } -scratch_allocator_init :: proc(s: ^Scratch_Allocator, size: int, backup_allocator := context.allocator) -> Allocator_Error { +/* +Scratch allocator. + +The scratch allocator works in a similar way to the `Arena` allocator. The +scratch allocator has a backing buffer, that is being allocated in +contiguous regions, from start to end. + +Each subsequent allocation will be the next adjacent region of memory in the +backing buffer. If the allocation doesn't fit into the remaining space of the +backing buffer, this allocation is put at the start of the buffer, and all +previous allocations will become invalidated. If the allocation doesn't fit +into the backing buffer as a whole, it will be allocated using a backing +allocator, and pointer to the allocated memory region will be put into the +`leaked_allocations` array. + +The `leaked_allocations` array is managed by the `context` allocator. +*/ +@(require_results) +scratch_allocator :: proc(allocator: ^Scratch) -> Allocator { + return Allocator{ + procedure = scratch_allocator_proc, + data = allocator, + } +} + +/* +Initialize scratch allocator. +*/ +scratch_init :: proc(s: ^Scratch, size: int, backup_allocator := context.allocator) -> Allocator_Error { s.data = make_aligned([]byte, size, 2*align_of(rawptr), backup_allocator) or_return s.curr_offset = 0 s.prev_allocation = nil @@ -139,7 +374,10 @@ scratch_allocator_init :: proc(s: ^Scratch_Allocator, size: int, backup_allocato return nil } -scratch_allocator_destroy :: proc(s: ^Scratch_Allocator) { +/* +Free all data associated with a scratch allocator. +*/ +scratch_destroy :: proc(s: ^Scratch) { if s == nil { return } @@ -151,60 +389,105 @@ scratch_allocator_destroy :: proc(s: ^Scratch_Allocator) { s^ = {} } -scratch_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { +/* +Allocate memory from scratch allocator. - s := (^Scratch_Allocator)(allocator_data) +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is zero-initialized. This procedure +returns a pointer to the allocated memory region. +*/ +@(require_results) +scratch_alloc :: proc( + s: ^Scratch, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := scratch_alloc_bytes(s, size, alignment, loc) + return raw_data(bytes), err +} - if s.data == nil { - DEFAULT_BACKING_SIZE :: 4 * Megabyte - if !(context.allocator.procedure != scratch_allocator_proc && - context.allocator.data != allocator_data) { - panic("cyclic initialization of the scratch allocator with itself") - } - scratch_allocator_init(s, DEFAULT_BACKING_SIZE) - } +/* +Allocate memory from scratch allocator. - size := size +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is zero-initialized. This procedure +returns a slice of the allocated memory region. +*/ +@(require_results) +scratch_alloc_bytes :: proc( + s: ^Scratch, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := scratch_alloc_bytes_non_zeroed(s, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - size = align_forward_int(size, alignment) - - switch { - case s.curr_offset+size <= len(s.data): - start := uintptr(raw_data(s.data)) - ptr := start + uintptr(s.curr_offset) - ptr = align_forward_uintptr(ptr, uintptr(alignment)) - if mode != .Alloc_Non_Zeroed { - zero(rawptr(ptr), size) - } +/* +Allocate non-initialized memory from scratch allocator. - s.prev_allocation = rawptr(ptr) - offset := int(ptr - start) - s.curr_offset = offset + size - return byte_slice(rawptr(ptr), size), nil +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is not explicitly zero-initialized. +This procedure returns a pointer to the allocated memory region. +*/ +@(require_results) +scratch_alloc_non_zeroed :: proc( + s: ^Scratch, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := scratch_alloc_bytes_non_zeroed(s, size, alignment, loc) + return raw_data(bytes), err +} - case size <= len(s.data): - start := uintptr(raw_data(s.data)) - ptr := align_forward_uintptr(start, uintptr(alignment)) - if mode != .Alloc_Non_Zeroed { - zero(rawptr(ptr), size) - } +/* +Allocate non-initialized memory from scratch allocator. - s.prev_allocation = rawptr(ptr) - offset := int(ptr - start) - s.curr_offset = offset + size - return byte_slice(rawptr(ptr), size), nil +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is not explicitly zero-initialized. +This procedure returns a slice of the allocated memory region. +*/ +@(require_results) +scratch_alloc_bytes_non_zeroed :: proc( + s: ^Scratch, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + if s.data == nil { + DEFAULT_BACKING_SIZE :: 4 * Megabyte + if !(context.allocator.procedure != scratch_allocator_proc && context.allocator.data != s) { + panic("cyclic initialization of the scratch allocator with itself", loc) } + scratch_init(s, DEFAULT_BACKING_SIZE) + } + size := size + size = align_forward_int(size, alignment) + if size <= len(s.data) { + offset := uintptr(0) + if s.curr_offset+size <= len(s.data) { + offset = uintptr(s.curr_offset) + } else { + offset = 0 + } + start := uintptr(raw_data(s.data)) + ptr := align_forward_uintptr(offset+start, uintptr(alignment)) + s.prev_allocation = rawptr(ptr) + s.curr_offset = int(offset) + size + return byte_slice(rawptr(ptr), size), nil + } else { a := s.backup_allocator if a.procedure == nil { a = context.allocator s.backup_allocator = a } - - ptr, err := alloc_bytes(size, alignment, a, loc) + ptr, err := alloc_bytes_non_zeroed(size, alignment, a, loc) if err != nil { return ptr, err } @@ -212,110 +495,290 @@ scratch_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, s.leaked_allocations, err = make([dynamic][]byte, a) } append(&s.leaked_allocations, ptr) - if logger := context.logger; logger.lowest_level <= .Warning { if logger.procedure != nil { - logger.procedure(logger.data, .Warning, "mem.Scratch_Allocator resorted to backup_allocator" , logger.options, loc) + logger.procedure(logger.data, .Warning, "mem.Scratch resorted to backup_allocator" , logger.options, loc) } } - return ptr, err + } +} - case .Free: - if old_memory == nil { - return nil, nil - } - start := uintptr(raw_data(s.data)) - end := start + uintptr(len(s.data)) - old_ptr := uintptr(old_memory) - - if s.prev_allocation == old_memory { - s.curr_offset = int(uintptr(s.prev_allocation) - start) - s.prev_allocation = nil - return nil, nil - } +/* +Free memory to the scratch allocator. - if start <= old_ptr && old_ptr < end { - // NOTE(bill): Cannot free this pointer but it is valid - return nil, nil - } +This procedure frees the memory region allocated at pointer `ptr`. - if len(s.leaked_allocations) != 0 { - for data, i in s.leaked_allocations { - ptr := raw_data(data) - if ptr == old_memory { - free_bytes(data, s.backup_allocator) - ordered_remove(&s.leaked_allocations, i) - return nil, nil - } +If `ptr` is not the latest allocation and is not a leaked allocation, this +operation is a no-op. +*/ +scratch_free :: proc(s: ^Scratch, ptr: rawptr, loc := #caller_location) -> Allocator_Error { + if s.data == nil { + panic("Free on an uninitialized scratch allocator", loc) + } + if ptr == nil { + return nil + } + start := uintptr(raw_data(s.data)) + end := start + uintptr(len(s.data)) + old_ptr := uintptr(ptr) + if s.prev_allocation == ptr { + s.curr_offset = int(uintptr(s.prev_allocation) - start) + s.prev_allocation = nil + return nil + } + if start <= old_ptr && old_ptr < end { + // NOTE(bill): Cannot free this pointer but it is valid + return nil + } + if len(s.leaked_allocations) != 0 { + for data, i in s.leaked_allocations { + ptr := raw_data(data) + if ptr == ptr { + free_bytes(data, s.backup_allocator, loc) + ordered_remove(&s.leaked_allocations, i, loc) + return nil } } - return nil, .Invalid_Pointer - // panic("invalid pointer passed to default_temp_allocator"); + } + return .Invalid_Pointer +} - case .Free_All: - s.curr_offset = 0 - s.prev_allocation = nil - for ptr in s.leaked_allocations { - free_bytes(ptr, s.backup_allocator) - } - clear(&s.leaked_allocations) +/* +Free all memory to the scratch allocator. +*/ +scratch_free_all :: proc(s: ^Scratch, loc := #caller_location) { + s.curr_offset = 0 + s.prev_allocation = nil + for ptr in s.leaked_allocations { + free_bytes(ptr, s.backup_allocator, loc) + } + clear(&s.leaked_allocations) +} - case .Resize, .Resize_Non_Zeroed: - begin := uintptr(raw_data(s.data)) - end := begin + uintptr(len(s.data)) - old_ptr := uintptr(old_memory) - if begin <= old_ptr && old_ptr < end && old_ptr+uintptr(size) < end { - s.curr_offset = int(old_ptr-begin)+size - return byte_slice(old_memory, size), nil - } - data, err := scratch_allocator_proc(allocator_data, .Alloc, size, alignment, old_memory, old_size, loc) - if err != nil { - return data, err +/* +Resize an allocation. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `scratch_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `scratch_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +scratch_resize :: proc( + s: ^Scratch, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> (rawptr, Allocator_Error) { + bytes, err := scratch_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation. + +This procedure resizes a memory region, specified by `old_data`, to have a size +`size` and alignment `alignment`. The newly allocated memory, if any is +zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `scratch_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `scratch_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +scratch_resize_bytes :: proc( + s: ^Scratch, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> ([]byte, Allocator_Error) { + bytes, err := scratch_resize_bytes_non_zeroed(s, old_data, size, alignment, loc) + if bytes != nil && size > len(old_data) { + zero_slice(bytes[size:]) + } + return bytes, err +} + +/* +Resize an allocation without zero-initialization. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is not explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `scratch_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `scratch_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +scratch_resize_non_zeroed :: proc( + s: ^Scratch, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> (rawptr, Allocator_Error) { + bytes, err := scratch_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation. + +This procedure resizes a memory region, specified by `old_data`, to have a size +`size` and alignment `alignment`. The newly allocated memory, if any is not +explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `scratch_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `scratch_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +scratch_resize_bytes_non_zeroed :: proc( + s: ^Scratch, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> ([]byte, Allocator_Error) { + old_memory := raw_data(old_data) + old_size := len(old_data) + if s.data == nil { + DEFAULT_BACKING_SIZE :: 4 * Megabyte + if !(context.allocator.procedure != scratch_allocator_proc && context.allocator.data != s) { + panic("cyclic initialization of the scratch allocator with itself", loc) } - runtime.copy(data, byte_slice(old_memory, old_size)) - _, err = scratch_allocator_proc(allocator_data, .Free, 0, alignment, old_memory, old_size, loc) + scratch_init(s, DEFAULT_BACKING_SIZE) + } + begin := uintptr(raw_data(s.data)) + end := begin + uintptr(len(s.data)) + old_ptr := uintptr(old_memory) + if begin <= old_ptr && old_ptr < end && old_ptr+uintptr(size) < end { + s.curr_offset = int(old_ptr-begin)+size + return byte_slice(old_memory, size), nil + } + data, err := scratch_alloc_bytes_non_zeroed(s, size, alignment, loc) + if err != nil { return data, err + } + runtime.copy(data, byte_slice(old_memory, old_size)) + err = scratch_free(s, old_memory, loc) + return data, err +} +scratch_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + s := (^Scratch)(allocator_data) + size := size + switch mode { + case .Alloc: + return scratch_alloc_bytes(s, size, alignment, loc) + case .Alloc_Non_Zeroed: + return scratch_alloc_bytes_non_zeroed(s, size, alignment, loc) + case .Free: + return nil, scratch_free(s, old_memory, loc) + case .Free_All: + scratch_free_all(s, loc) + case .Resize: + return scratch_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment, loc) + case .Resize_Non_Zeroed: + return scratch_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment, loc) case .Query_Features: set := (^Allocator_Mode_Set)(old_memory) if set != nil { set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features} } return nil, nil - case .Query_Info: return nil, .Mode_Not_Implemented } - return nil, nil } -@(require_results) -scratch_allocator :: proc(allocator: ^Scratch_Allocator) -> Allocator { - return Allocator{ - procedure = scratch_allocator_proc, - data = allocator, - } -} - - +/* +Stack allocator data. +*/ +Stack :: struct { + data: []byte, + prev_offset: int, + curr_offset: int, + peak_used: int, +} +/* +Header of a stack allocation. +*/ Stack_Allocation_Header :: struct { prev_offset: int, padding: int, } -// Stack is a stack-like allocator which has a strict memory freeing order -Stack :: struct { - data: []byte, - prev_offset: int, - curr_offset: int, - peak_used: int, +/* +Stack allocator. + +The stack allocator is an allocator that allocates data in the backing buffer +linearly, from start to end. Each subsequent allocation will get the next +adjacent memory region. + +Unlike arena allocator, the stack allocator saves allocation metadata and has +a strict freeing order. Only the last allocated element can be freed. After the +last allocated element is freed, the next previous allocated element becomes +available for freeing. + +The metadata is stored in the allocation headers, that are located before the +start of each allocated memory region. Each header points to the start of the +previous allocation header. +*/ +@(require_results) +stack_allocator :: proc(stack: ^Stack) -> Allocator { + return Allocator{ + procedure = stack_allocator_proc, + data = stack, + } } +/* +Initialize the stack allocator. + +This procedure initializes the stack allocator with a backing buffer specified +by `data` parameter. +*/ stack_init :: proc(s: ^Stack, data: []byte) { s.data = data s.prev_offset = 0 @@ -331,129 +794,333 @@ init_stack :: proc(s: ^Stack, data: []byte) { s.peak_used = 0 } +/* +Allocate memory from stack. + +This procedure allocates `size` bytes of memory, aligned to the boundary +specified by `alignment`. The allocated memory is zero-initialized. This +procedure returns the pointer to the allocated memory. +*/ @(require_results) -stack_allocator :: proc(stack: ^Stack) -> Allocator { - return Allocator{ - procedure = stack_allocator_proc, - data = stack, +stack_alloc :: proc( + s: ^Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> (rawptr, Allocator_Error) { + bytes, err := stack_alloc_bytes(s, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate memory from stack. + +This procedure allocates `size` bytes of memory, aligned to the boundary +specified by `alignment`. The allocated memory is zero-initialized. This +procedure returns the slice of the allocated memory. +*/ +@(require_results) +stack_alloc_bytes :: proc( + s: ^Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> ([]byte, Allocator_Error) { + bytes, err := stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) } + return bytes, err } +/* +Allocate memory from stack. -stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, Allocator_Error) { - s := cast(^Stack)allocator_data +This procedure allocates `size` bytes of memory, aligned to the boundary +specified by `alignment`. The allocated memory is not explicitly +zero-initialized. This procedure returns the pointer to the allocated memory. +*/ +@(require_results) +stack_alloc_non_zeroed :: proc( + s: ^Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> (rawptr, Allocator_Error) { + bytes, err := stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate memory from stack. +This procedure allocates `size` bytes of memory, aligned to the boundary +specified by `alignment`. The allocated memory is not explicitly +zero-initialized. This procedure returns the slice of the allocated memory. +*/ +@(require_results) +stack_alloc_bytes_non_zeroed :: proc( + s: ^Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> ([]byte, Allocator_Error) { if s.data == nil { - return nil, .Invalid_Argument + panic("Stack allocation on an uninitialized stack allocator", loc) } + curr_addr := uintptr(raw_data(s.data)) + uintptr(s.curr_offset) + padding := calc_padding_with_header( + curr_addr, + uintptr(alignment), + size_of(Stack_Allocation_Header), + ) + if s.curr_offset + padding + size > len(s.data) { + return nil, .Out_Of_Memory + } + s.prev_offset = s.curr_offset + s.curr_offset += padding + next_addr := curr_addr + uintptr(padding) + header := (^Stack_Allocation_Header)(next_addr - size_of(Stack_Allocation_Header)) + header.padding = padding + header.prev_offset = s.prev_offset + s.curr_offset += size + s.peak_used = max(s.peak_used, s.curr_offset) + return byte_slice(rawptr(next_addr), size), nil +} - raw_alloc :: proc(s: ^Stack, size, alignment: int, zero_memory: bool) -> ([]byte, Allocator_Error) { - curr_addr := uintptr(raw_data(s.data)) + uintptr(s.curr_offset) - padding := calc_padding_with_header(curr_addr, uintptr(alignment), size_of(Stack_Allocation_Header)) - if s.curr_offset + padding + size > len(s.data) { - return nil, .Out_Of_Memory - } - s.prev_offset = s.curr_offset - s.curr_offset += padding - - next_addr := curr_addr + uintptr(padding) - header := (^Stack_Allocation_Header)(next_addr - size_of(Stack_Allocation_Header)) - header.padding = padding - header.prev_offset = s.prev_offset +/* +Free memory to the stack. + +This procedure frees the memory region starting at `old_memory` to the stack. +If the freeing does is an out of order freeing, the `.Invalid_Pointer` error +is returned. +*/ +stack_free :: proc( + s: ^Stack, + old_memory: rawptr, + loc := #caller_location, +) -> (Allocator_Error) { + if s.data == nil { + panic("Stack free on an uninitialized stack allocator", loc) + } + if old_memory == nil { + return nil + } + start := uintptr(raw_data(s.data)) + end := start + uintptr(len(s.data)) + curr_addr := uintptr(old_memory) + if !(start <= curr_addr && curr_addr < end) { + panic("Out of bounds memory address passed to stack allocator (free)", loc) + } + if curr_addr >= start+uintptr(s.curr_offset) { + // NOTE(bill): Allow double frees + return nil + } + header := (^Stack_Allocation_Header)(curr_addr - size_of(Stack_Allocation_Header)) + old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) + if old_offset != header.prev_offset { + // panic("Out of order stack allocator free"); + return .Invalid_Pointer + } + s.curr_offset = old_offset + s.prev_offset = header.prev_offset + return nil +} - s.curr_offset += size +/* +Free all allocations to the stack. +*/ +stack_free_all :: proc(s: ^Stack, loc := #caller_location) { + s.prev_offset = 0 + s.curr_offset = 0 +} - s.peak_used = max(s.peak_used, s.curr_offset) +/* +Resize an allocation. - if zero_memory { - zero(rawptr(next_addr), size) - } - return byte_slice(rawptr(next_addr), size), nil - } +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is zero-initialized. - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return raw_alloc(s, size, alignment, mode == .Alloc) - case .Free: - if old_memory == nil { - return nil, nil - } - start := uintptr(raw_data(s.data)) - end := start + uintptr(len(s.data)) - curr_addr := uintptr(old_memory) +If `old_memory` is `nil`, this procedure acts just like `stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. - if !(start <= curr_addr && curr_addr < end) { - panic("Out of bounds memory address passed to stack allocator (free)") - } +If `size` is 0, this procedure acts just like `stack_free()`, freeing the +memory region located at an address specified by `old_memory`. - if curr_addr >= start+uintptr(s.curr_offset) { - // NOTE(bill): Allow double frees - return nil, nil - } +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +stack_resize :: proc( + s: ^Stack, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := stack_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment) + return raw_data(bytes), err +} - header := (^Stack_Allocation_Header)(curr_addr - size_of(Stack_Allocation_Header)) - old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) +/* +Resize an allocation. - if old_offset != header.prev_offset { - // panic("Out of order stack allocator free"); - return nil, .Invalid_Pointer - } +This procedure resizes a memory region, specified by the `old_data` parameter +to have a size `size` and alignment `alignment`. The newly allocated memory, +if any is zero-initialized. - s.curr_offset = old_offset - s.prev_offset = header.prev_offset +If `old_memory` is `nil`, this procedure acts just like `stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. - case .Free_All: - s.prev_offset = 0 - s.curr_offset = 0 +If `size` is 0, this procedure acts just like `stack_free()`, freeing the +memory region located at an address specified by `old_memory`. - case .Resize, .Resize_Non_Zeroed: - if old_memory == nil { - return raw_alloc(s, size, alignment, mode == .Resize) - } - if size == 0 { - return nil, nil +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +stack_resize_bytes :: proc( + s: ^Stack, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if bytes != nil { + if old_data == nil { + zero_slice(bytes) + } else if size > len(old_data) { + zero_slice(bytes[len(old_data):]) } + } + return bytes, err +} - start := uintptr(raw_data(s.data)) - end := start + uintptr(len(s.data)) - curr_addr := uintptr(old_memory) - if !(start <= curr_addr && curr_addr < end) { - panic("Out of bounds memory address passed to stack allocator (resize)") - } +/* +Resize an allocation without zero-initialization. - if curr_addr >= start+uintptr(s.curr_offset) { - // NOTE(bill): Allow double frees - return nil, nil - } +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is not explicitly zero-initialized. - if old_size == size { - return byte_slice(old_memory, size), nil - } +If `old_memory` is `nil`, this procedure acts just like `stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. - header := (^Stack_Allocation_Header)(curr_addr - size_of(Stack_Allocation_Header)) - old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) +If `size` is 0, this procedure acts just like `stack_free()`, freeing the +memory region located at an address specified by `old_memory`. - if old_offset != header.prev_offset { - data, err := raw_alloc(s, size, alignment, mode == .Resize) - if err == nil { - runtime.copy(data, byte_slice(old_memory, old_size)) - } - return data, err - } +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +stack_resize_non_zeroed :: proc( + s: ^Stack, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := stack_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment) + return raw_data(bytes), err +} - old_memory_size := uintptr(s.curr_offset) - (curr_addr - start) - assert(old_memory_size == uintptr(old_size)) +/* +Resize an allocation without zero-initialization. - diff := size - old_size - s.curr_offset += diff // works for smaller sizes too - if diff > 0 { - zero(rawptr(curr_addr + uintptr(diff)), diff) - } +This procedure resizes a memory region, specified by the `old_data` parameter +to have a size `size` and alignment `alignment`. The newly allocated memory, +if any is not explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. +If `size` is 0, this procedure acts just like `stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +stack_resize_bytes_non_zeroed :: proc( + s: ^Stack, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + old_memory := raw_data(old_data) + old_size := len(old_data) + if s.data == nil { + panic("Stack free all on an uninitialized stack allocator", loc) + } + if old_memory == nil { + return stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + } + if size == 0 { + return nil, nil + } + start := uintptr(raw_data(s.data)) + end := start + uintptr(len(s.data)) + curr_addr := uintptr(old_memory) + if !(start <= curr_addr && curr_addr < end) { + panic("Out of bounds memory address passed to stack allocator (resize)") + } + if curr_addr >= start+uintptr(s.curr_offset) { + // NOTE(bill): Allow double frees + return nil, nil + } + if old_size == size { return byte_slice(old_memory, size), nil + } + header := (^Stack_Allocation_Header)(curr_addr - size_of(Stack_Allocation_Header)) + old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) + if old_offset != header.prev_offset { + data, err := stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if err == nil { + runtime.copy(data, byte_slice(old_memory, old_size)) + } + return data, err + } + old_memory_size := uintptr(s.curr_offset) - (curr_addr - start) + assert(old_memory_size == uintptr(old_size)) + diff := size - old_size + s.curr_offset += diff // works for smaller sizes too + if diff > 0 { + zero(rawptr(curr_addr + uintptr(diff)), diff) + } + return byte_slice(old_memory, size), nil +} +stack_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size: int, + alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + s := cast(^Stack)allocator_data + if s.data == nil { + return nil, .Invalid_Argument + } + switch mode { + case .Alloc: + return stack_alloc_bytes(s, size, alignment, loc) + case .Alloc_Non_Zeroed: + return stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + case .Free: + return nil, stack_free(s, old_memory, loc) + case .Free_All: + stack_free_all(s, loc) + case .Resize: + return stack_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment, loc) + case .Resize_Non_Zeroed: + return stack_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment, loc) case .Query_Features: set := (^Allocator_Mode_Set)(old_memory) if set != nil { @@ -463,27 +1130,32 @@ stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, case .Query_Info: return nil, .Mode_Not_Implemented } - return nil, nil } - - - - - +/* +Allocation header of the small stack allocator. +*/ Small_Stack_Allocation_Header :: struct { padding: u8, } -// Small_Stack is a stack-like allocator which uses the smallest possible header but at the cost of non-strict memory freeing order +/* +Small stack allocator data. +*/ Small_Stack :: struct { data: []byte, offset: int, peak_used: int, } +/* +Initialize small stack. + +This procedure initializes the small stack allocator with `data` as its backing +buffer. +*/ small_stack_init :: proc(s: ^Small_Stack, data: []byte) { s.data = data s.offset = 0 @@ -497,6 +1169,20 @@ init_small_stack :: proc(s: ^Small_Stack, data: []byte) { s.peak_used = 0 } +/* +Small stack allocator. + +The small stack allocator is just like a stack allocator, with the only +difference being an extremely small header size. Unlike the stack allocator, +small stack allows out-of order freeing of memory. + +The memory is allocated in the backing buffer linearly, from start to end. +Each subsequent allocation will get the next adjacent memory region. + +The metadata is stored in the allocation headers, that are located before the +start of each allocated memory region. Each header contains the amount of +padding bytes between that header and end of the previous allocation. +*/ @(require_results) small_stack_allocator :: proc(stack: ^Small_Stack) -> Allocator { return Allocator{ @@ -505,375 +1191,766 @@ small_stack_allocator :: proc(stack: ^Small_Stack) -> Allocator { } } -small_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, Allocator_Error) { - s := cast(^Small_Stack)allocator_data +/* +Allocate memory from small stack. - if s.data == nil { - return nil, .Invalid_Argument - } +This procedure allocates `size` bytes of memory aligned to a boundary specified +by `alignment`. The allocated memory is zero-initialized. This procedure +returns a pointer to the allocated memory region. +*/ +@(require_results) +small_stack_alloc :: proc( + s: ^Small_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := small_stack_alloc_bytes(s, size, alignment, loc) + return raw_data(bytes), err +} - align := clamp(alignment, 1, 8*size_of(Stack_Allocation_Header{}.padding)/2) +/* +Allocate memory from small stack. - raw_alloc :: proc(s: ^Small_Stack, size, alignment: int, zero_memory: bool) -> ([]byte, Allocator_Error) { - curr_addr := uintptr(raw_data(s.data)) + uintptr(s.offset) - padding := calc_padding_with_header(curr_addr, uintptr(alignment), size_of(Small_Stack_Allocation_Header)) - if s.offset + padding + size > len(s.data) { - return nil, .Out_Of_Memory - } - s.offset += padding +This procedure allocates `size` bytes of memory aligned to a boundary specified +by `alignment`. The allocated memory is zero-initialized. This procedure +returns a slice of the allocated memory region. +*/ +@(require_results) +small_stack_alloc_bytes :: proc( + s: ^Small_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} - next_addr := curr_addr + uintptr(padding) - header := (^Small_Stack_Allocation_Header)(next_addr - size_of(Small_Stack_Allocation_Header)) - header.padding = auto_cast padding +/* +Allocate memory from small stack. - s.offset += size +This procedure allocates `size` bytes of memory aligned to a boundary specified +by `alignment`. The allocated memory is not explicitly zero-initialized. This +procedure returns a pointer to the allocated memory region. +*/ +@(require_results) +small_stack_alloc_non_zeroed :: proc( + s: ^Small_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + return raw_data(bytes), err +} - s.peak_used = max(s.peak_used, s.offset) +/* +Allocate memory from small stack. - if zero_memory { - zero(rawptr(next_addr), size) - } - return byte_slice(rawptr(next_addr), size), nil +This procedure allocates `size` bytes of memory aligned to a boundary specified +by `alignment`. The allocated memory is not explicitly zero-initialized. This +procedure returns a slice of the allocated memory region. +*/ +@(require_results) +small_stack_alloc_bytes_non_zeroed :: proc( + s: ^Small_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + if s.data == nil { + panic("Small stack is not initialized", loc) } + alignment := alignment + alignment = clamp(alignment, 1, 8*size_of(Stack_Allocation_Header{}.padding)/2) + curr_addr := uintptr(raw_data(s.data)) + uintptr(s.offset) + padding := calc_padding_with_header(curr_addr, uintptr(alignment), size_of(Small_Stack_Allocation_Header)) + if s.offset + padding + size > len(s.data) { + return nil, .Out_Of_Memory + } + s.offset += padding + next_addr := curr_addr + uintptr(padding) + header := (^Small_Stack_Allocation_Header)(next_addr - size_of(Small_Stack_Allocation_Header)) + header.padding = auto_cast padding + s.offset += size + s.peak_used = max(s.peak_used, s.offset) + return byte_slice(rawptr(next_addr), size), nil +} - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return raw_alloc(s, size, align, mode == .Alloc) - case .Free: - if old_memory == nil { - return nil, nil - } - start := uintptr(raw_data(s.data)) - end := start + uintptr(len(s.data)) - curr_addr := uintptr(old_memory) +/* +Allocate memory from small stack. + +This procedure allocates `size` bytes of memory aligned to a boundary specified +by `alignment`. The allocated memory is not explicitly zero-initialized. This +procedure returns a slice of the allocated memory region. +*/ +small_stack_free :: proc( + s: ^Small_Stack, + old_memory: rawptr, + loc := #caller_location, +) -> Allocator_Error { + if s.data == nil { + panic("Small stack is not initialized", loc) + } + if old_memory == nil { + return nil + } + start := uintptr(raw_data(s.data)) + end := start + uintptr(len(s.data)) + curr_addr := uintptr(old_memory) + if !(start <= curr_addr && curr_addr < end) { + // panic("Out of bounds memory address passed to stack allocator (free)"); + return .Invalid_Pointer + } + if curr_addr >= start+uintptr(s.offset) { + // NOTE(bill): Allow double frees + return nil + } + header := (^Small_Stack_Allocation_Header)(curr_addr - size_of(Small_Stack_Allocation_Header)) + old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) + s.offset = old_offset + return nil +} - if !(start <= curr_addr && curr_addr < end) { - // panic("Out of bounds memory address passed to stack allocator (free)"); - return nil, .Invalid_Pointer - } +/* +Free all memory to small stack. +*/ +small_stack_free_all :: proc(s: ^Small_Stack) { + s.offset = 0 +} - if curr_addr >= start+uintptr(s.offset) { - // NOTE(bill): Allow double frees - return nil, nil - } +/* +Resize an allocation. - header := (^Small_Stack_Allocation_Header)(curr_addr - size_of(Small_Stack_Allocation_Header)) - old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is zero-initialized. - s.offset = old_offset +If `old_memory` is `nil`, this procedure acts just like `small_stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. - case .Free_All: - s.offset = 0 +If `size` is 0, this procedure acts just like `small_stack_free()`, freeing the +memory region located at an address specified by `old_memory`. - case .Resize, .Resize_Non_Zeroed: - if old_memory == nil { - return raw_alloc(s, size, align, mode == .Resize) - } - if size == 0 { - return nil, nil - } +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +small_stack_resize :: proc( + s: ^Small_Stack, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := small_stack_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment, loc) + return raw_data(bytes), err +} - start := uintptr(raw_data(s.data)) - end := start + uintptr(len(s.data)) - curr_addr := uintptr(old_memory) - if !(start <= curr_addr && curr_addr < end) { - // panic("Out of bounds memory address passed to stack allocator (resize)"); - return nil, .Invalid_Pointer - } +/* +Resize an allocation. - if curr_addr >= start+uintptr(s.offset) { - // NOTE(bill): Treat as a double free - return nil, nil - } +This procedure resizes a memory region, specified by the `old_data` parameter +to have a size `size` and alignment `alignment`. The newly allocated memory, +if any is zero-initialized. - if old_size == size { - return byte_slice(old_memory, size), nil - } +If `old_memory` is `nil`, this procedure acts just like `small_stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. - data, err := raw_alloc(s, size, align, mode == .Resize) - if err == nil { - runtime.copy(data, byte_slice(old_memory, old_size)) - } - return data, err +If `size` is 0, this procedure acts just like `small_stack_free()`, freeing the +memory region located at an address specified by `old_memory`. - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features} +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +small_stack_resize_bytes :: proc( + s: ^Small_Stack, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := small_stack_resize_bytes_non_zeroed(s, old_data, size, alignment, loc) + if bytes != nil { + if old_data == nil { + zero_slice(bytes) + } else if size > len(old_data) { + zero_slice(bytes[len(old_data):]) } - return nil, nil - - case .Query_Info: - return nil, .Mode_Not_Implemented } - - return nil, nil + return bytes, err } +/* +Resize an allocation without zero-initialization. +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is not explicitly zero-initialized. +If `old_memory` is `nil`, this procedure acts just like `small_stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. +If `size` is 0, this procedure acts just like `small_stack_free()`, freeing the +memory region located at an address specified by `old_memory`. -Dynamic_Pool :: struct { - block_size: int, - out_band_size: int, - alignment: int, - - unused_blocks: [dynamic]rawptr, - used_blocks: [dynamic]rawptr, - out_band_allocations: [dynamic]rawptr, - - current_block: rawptr, - current_pos: rawptr, - bytes_left: int, - - block_allocator: Allocator, +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +small_stack_resize_non_zeroed :: proc( + s: ^Small_Stack, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := small_stack_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment, loc) + return raw_data(bytes), err } +/* +Resize an allocation without zero-initialization. -DYNAMIC_POOL_BLOCK_SIZE_DEFAULT :: 65536 -DYNAMIC_POOL_OUT_OF_BAND_SIZE_DEFAULT :: 6554 +This procedure resizes a memory region, specified by the `old_data` parameter +to have a size `size` and alignment `alignment`. The newly allocated memory, +if any is not explicitly zero-initialized. +If `old_memory` is `nil`, this procedure acts just like `small_stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. +If `size` is 0, this procedure acts just like `small_stack_free()`, freeing the +memory region located at an address specified by `old_memory`. -dynamic_pool_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - pool := (^Dynamic_Pool)(allocator_data) +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +small_stack_resize_bytes_non_zeroed :: proc( + s: ^Small_Stack, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + if s.data == nil { + panic("Small stack is not initialized", loc) + } + old_memory := raw_data(old_data) + old_size := len(old_data) + alignment := alignment + alignment = clamp(alignment, 1, 8*size_of(Stack_Allocation_Header{}.padding)/2) + if old_memory == nil { + return small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + } + if size == 0 { + return nil, nil + } + start := uintptr(raw_data(s.data)) + end := start + uintptr(len(s.data)) + curr_addr := uintptr(old_memory) + if !(start <= curr_addr && curr_addr < end) { + // panic("Out of bounds memory address passed to stack allocator (resize)"); + return nil, .Invalid_Pointer + } + if curr_addr >= start+uintptr(s.offset) { + // NOTE(bill): Treat as a double free + return nil, nil + } + if old_size == size { + return byte_slice(old_memory, size), nil + } + data, err := small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if err == nil { + runtime.copy(data, byte_slice(old_memory, old_size)) + } + return data, err + +} +small_stack_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + s := cast(^Small_Stack)allocator_data + if s.data == nil { + return nil, .Invalid_Argument + } switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return dynamic_pool_alloc_bytes(pool, size) + case .Alloc: + return small_stack_alloc_bytes(s, size, alignment, loc) + case .Alloc_Non_Zeroed: + return small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) case .Free: - return nil, .Mode_Not_Implemented + return nil, small_stack_free(s, old_memory, loc) case .Free_All: - dynamic_pool_free_all(pool) - return nil, nil - case .Resize, .Resize_Non_Zeroed: - if old_size >= size { - return byte_slice(old_memory, size), nil - } - data, err := dynamic_pool_alloc_bytes(pool, size) - if err == nil { - runtime.copy(data, byte_slice(old_memory, old_size)) - } - return data, err - + small_stack_free_all(s) + case .Resize: + return small_stack_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment, loc) + case .Resize_Non_Zeroed: + return small_stack_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment, loc) case .Query_Features: set := (^Allocator_Mode_Set)(old_memory) if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features, .Query_Info} + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features} } return nil, nil - case .Query_Info: - info := (^Allocator_Query_Info)(old_memory) - if info != nil && info.pointer != nil { - info.size = pool.block_size - info.alignment = pool.alignment - return byte_slice(info, size_of(info^)), nil - } - return nil, nil + return nil, .Mode_Not_Implemented } return nil, nil } -@(require_results) -dynamic_pool_allocator :: proc(pool: ^Dynamic_Pool) -> Allocator { - return Allocator{ - procedure = dynamic_pool_allocator_proc, - data = pool, - } +/* Preserved for compatibility */ +Dynamic_Pool :: Dynamic_Arena +DYNAMIC_POOL_BLOCK_SIZE_DEFAULT :: DYNAMIC_ARENA_BLOCK_SIZE_DEFAULT +DYNAMIC_POOL_OUT_OF_BAND_SIZE_DEFAULT :: DYNAMIC_ARENA_OUT_OF_BAND_SIZE_DEFAULT +dynamic_pool_allocator_proc :: dynamic_arena_allocator_proc +dynamic_pool_free_all :: dynamic_arena_free_all +dynamic_pool_reset :: dynamic_arena_reset +dynamic_pool_alloc_bytes :: dynamic_arena_alloc_bytes +dynamic_pool_alloc :: dynamic_arena_alloc +dynamic_pool_init :: dynamic_arena_init +dynamic_pool_allocator :: dynamic_arena_allocator +dynamic_pool_destroy :: dynamic_arena_destroy + +/* +Default block size for dynamic arena. +*/ +DYNAMIC_ARENA_BLOCK_SIZE_DEFAULT :: 65536 + +/* +Default out-band size of the dynamic arena. +*/ +DYNAMIC_ARENA_OUT_OF_BAND_SIZE_DEFAULT :: 6554 + +/* +Dynamic arena allocator data. +*/ +Dynamic_Arena :: struct { + block_size: int, + out_band_size: int, + alignment: int, + unused_blocks: [dynamic]rawptr, + used_blocks: [dynamic]rawptr, + out_band_allocations: [dynamic]rawptr, + current_block: rawptr, + current_pos: rawptr, + bytes_left: int, + block_allocator: Allocator, } -dynamic_pool_init :: proc(pool: ^Dynamic_Pool, - block_allocator := context.allocator, - array_allocator := context.allocator, - block_size := DYNAMIC_POOL_BLOCK_SIZE_DEFAULT, - out_band_size := DYNAMIC_POOL_OUT_OF_BAND_SIZE_DEFAULT, - alignment := 8) { - pool.block_size = block_size - pool.out_band_size = out_band_size - pool.alignment = alignment +/* +Initialize a dynamic arena. + +This procedure initializes a dynamic arena. The specified `block_allocator` +will be used to allocate arena blocks, and `array_allocator` to allocate +arrays of blocks and out-band blocks. The blocks have the default size of +`block_size` and out-band threshold will be `out_band_size`. All allocations +will be aligned to a boundary specified by `alignment`. +*/ +dynamic_arena_init :: proc( + pool: ^Dynamic_Arena, + block_allocator := context.allocator, + array_allocator := context.allocator, + block_size := DYNAMIC_ARENA_BLOCK_SIZE_DEFAULT, + out_band_size := DYNAMIC_ARENA_OUT_OF_BAND_SIZE_DEFAULT, + alignment := DEFAULT_ALIGNMENT, +) { + pool.block_size = block_size + pool.out_band_size = out_band_size + pool.alignment = alignment pool.block_allocator = block_allocator pool.out_band_allocations.allocator = array_allocator - pool. unused_blocks.allocator = array_allocator - pool. used_blocks.allocator = array_allocator + pool.unused_blocks.allocator = array_allocator + pool.used_blocks.allocator = array_allocator } -dynamic_pool_destroy :: proc(pool: ^Dynamic_Pool) { - dynamic_pool_free_all(pool) - delete(pool.unused_blocks) - delete(pool.used_blocks) - delete(pool.out_band_allocations) +/* +Dynamic arena allocator. - zero(pool, size_of(pool^)) -} +The dynamic arena allocator uses blocks of a specific size, allocated on-demand +using the block allocator. This allocator acts similarly to arena. All +allocations in a block happen contiguously, from start to end. If an allocation +does not fit into the remaining space of the block, and its size is smaller +than the specified out-band size, a new block is allocated using the +`block_allocator` and the allocation is performed from a newly-allocated block. +If an allocation has bigger size than the specified out-band size, a new block +is allocated such that the allocation fits into this new block. This is referred +to as an *out-band allocation*. The out-band blocks are kept separately from +normal blocks. +Just like arena, the dynamic arena does not support freeing of individual +objects. +*/ @(require_results) -dynamic_pool_alloc :: proc(pool: ^Dynamic_Pool, bytes: int) -> (rawptr, Allocator_Error) { - data, err := dynamic_pool_alloc_bytes(pool, bytes) +dynamic_arena_allocator :: proc(a: ^Dynamic_Arena) -> Allocator { + return Allocator{ + procedure = dynamic_arena_allocator_proc, + data = a, + } +} + +/* +Destroy a dynamic arena. + +This procedure frees all allocations, made on a dynamic arena, including the +unused blocks, as well as the arrays for storing blocks. +*/ +dynamic_arena_destroy :: proc(a: ^Dynamic_Arena) { + dynamic_arena_free_all(a) + delete(a.unused_blocks) + delete(a.used_blocks) + delete(a.out_band_allocations) + zero(a, size_of(a^)) +} + +@(private="file") +_dynamic_arena_cycle_new_block :: proc(a: ^Dynamic_Arena, loc := #caller_location) -> (err: Allocator_Error) { + if a.block_allocator.procedure == nil { + panic("You must call arena_init on a Pool before using it", loc) + } + if a.current_block != nil { + append(&a.used_blocks, a.current_block, loc=loc) + } + new_block: rawptr + if len(a.unused_blocks) > 0 { + new_block = pop(&a.unused_blocks) + } else { + data: []byte + data, err = a.block_allocator.procedure( + a.block_allocator.data, + Allocator_Mode.Alloc, + a.block_size, + a.alignment, + nil, + 0, + ) + new_block = raw_data(data) + } + a.bytes_left = a.block_size + a.current_pos = new_block + a.current_block = new_block + return +} + +/* +Allocate memory from a dynamic arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from a dynamic arena `a`. The allocated memory is +zero-initialized. This procedure returns a pointer to the newly allocated memory +region. +*/ +@(private, require_results) +dynamic_arena_alloc :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> (rawptr, Allocator_Error) { + data, err := dynamic_arena_alloc_bytes(a, size, loc) return raw_data(data), err } +/* +Allocate memory from a dynamic arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from a dynamic arena `a`. The allocated memory is +zero-initialized. This procedure returns a slice of the newly allocated memory +region. +*/ @(require_results) -dynamic_pool_alloc_bytes :: proc(p: ^Dynamic_Pool, bytes: int) -> ([]byte, Allocator_Error) { - cycle_new_block :: proc(p: ^Dynamic_Pool) -> (err: Allocator_Error) { - if p.block_allocator.procedure == nil { - panic("You must call pool_init on a Pool before using it") - } +dynamic_arena_alloc_bytes :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + bytes, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} - if p.current_block != nil { - append(&p.used_blocks, p.current_block) - } +/* +Allocate non-initialized memory from a dynamic arena. - new_block: rawptr - if len(p.unused_blocks) > 0 { - new_block = pop(&p.unused_blocks) - } else { - data: []byte - data, err = p.block_allocator.procedure(p.block_allocator.data, Allocator_Mode.Alloc, - p.block_size, p.alignment, - nil, 0) - new_block = raw_data(data) - } +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from a dynamic arena `a`. The allocated memory is not explicitly +zero-initialized. This procedure returns a pointer to the newly allocated +memory region. +*/ +@(require_results) +dynamic_arena_alloc_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> (rawptr, Allocator_Error) { + data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc) + return raw_data(data), err +} - p.bytes_left = p.block_size - p.current_pos = new_block - p.current_block = new_block - return - } +/* +Allocate non-initialized memory from a dynamic arena. - n := align_formula(bytes, p.alignment) - if n > p.block_size { +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from a dynamic arena `a`. The allocated memory is not explicitly +zero-initialized. This procedure returns a slice of the newly allocated +memory region. +*/ +@(require_results) +dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + n := align_formula(size, a.alignment) + if n > a.block_size { return nil, .Invalid_Argument } - if n >= p.out_band_size { - assert(p.block_allocator.procedure != nil) - memory, err := p.block_allocator.procedure(p.block_allocator.data, Allocator_Mode.Alloc, - p.block_size, p.alignment, - nil, 0) + if n >= a.out_band_size { + assert(a.block_allocator.procedure != nil, "Backing block allocator must be initialized", loc=loc) + memory, err := alloc_bytes_non_zeroed(a.block_size, a.alignment, a.block_allocator, loc) if memory != nil { - append(&p.out_band_allocations, raw_data(memory)) + append(&a.out_band_allocations, raw_data(memory), loc = loc) } return memory, err } - - if p.bytes_left < n { - err := cycle_new_block(p) + if a.bytes_left < n { + err := _dynamic_arena_cycle_new_block(a, loc) if err != nil { return nil, err } - if p.current_block == nil { + if a.current_block == nil { return nil, .Out_Of_Memory } } - - memory := p.current_pos - p.current_pos = ([^]byte)(p.current_pos)[n:] - p.bytes_left -= n - return ([^]byte)(memory)[:bytes], nil + memory := a.current_pos + a.current_pos = ([^]byte)(a.current_pos)[n:] + a.bytes_left -= n + return ([^]byte)(memory)[:size], nil } +/* +Reset the dynamic arena. -dynamic_pool_reset :: proc(p: ^Dynamic_Pool) { - if p.current_block != nil { - append(&p.unused_blocks, p.current_block) - p.current_block = nil +This procedure frees all the allocations, owned by the dynamic arena, excluding +the unused blocks. +*/ +dynamic_arena_reset :: proc(a: ^Dynamic_Arena, loc := #caller_location) { + if a.current_block != nil { + append(&a.unused_blocks, a.current_block, loc=loc) + a.current_block = nil } - - for block in p.used_blocks { - append(&p.unused_blocks, block) + for block in a.used_blocks { + append(&a.unused_blocks, block, loc=loc) } - clear(&p.used_blocks) + clear(&a.used_blocks) + for allocation in a.out_band_allocations { + free(allocation, a.block_allocator, loc=loc) + } + clear(&a.out_band_allocations) + a.bytes_left = 0 // Make new allocations call `_dynamic_arena_cycle_new_block` again. +} + +/* +Free all memory from a dynamic arena. - for a in p.out_band_allocations { - free(a, p.block_allocator) +This procedure frees all the allocations, owned by the dynamic arena, including +the unused blocks. +*/ +dynamic_arena_free_all :: proc(a: ^Dynamic_Arena, loc := #caller_location) { + dynamic_arena_reset(a) + for block in a.unused_blocks { + free(block, a.block_allocator, loc) } - clear(&p.out_band_allocations) + clear(&a.unused_blocks) +} + +/* +Resize an allocation. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is zero-initialized. - p.bytes_left = 0 // Make new allocations call `cycle_new_block` again. +If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing +the memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +dynamic_arena_resize :: proc( + a: ^Dynamic_Arena, + old_memory: rawptr, + old_size: int, + size: int, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := dynamic_arena_resize_bytes(a, byte_slice(old_memory, old_size), size, loc) + return raw_data(bytes), err } -dynamic_pool_free_all :: proc(p: ^Dynamic_Pool) { - dynamic_pool_reset(p) +/* +Resize an allocation. + +This procedure resizes a memory region, specified by `old_data`, to have a size +`size` and alignment `alignment`. The newly allocated memory, if any is +zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. - for block in p.unused_blocks { - free(block, p.block_allocator) +If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +dynamic_arena_resize_bytes :: proc( + a: ^Dynamic_Arena, + old_data: []byte, + size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := dynamic_arena_resize_bytes_non_zeroed(a, old_data, size, loc) + if bytes != nil { + if old_data == nil { + zero_slice(bytes) + } else if size > len(old_data) { + zero_slice(bytes[len(old_data):]) + } } - clear(&p.unused_blocks) + return bytes, err } +/* +Resize an allocation without zero-initialization. -panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int,loc := #caller_location) -> ([]byte, Allocator_Error) { +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is not explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +dynamic_arena_resize_non_zeroed :: proc( + a: ^Dynamic_Arena, + old_memory: rawptr, + old_size: int, + size: int, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := dynamic_arena_resize_bytes_non_zeroed(a, byte_slice(old_memory, old_size), size, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation. +This procedure resizes a memory region, specified by `old_data`, to have a size +`size` and alignment `alignment`. The newly allocated memory, if any is not +explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing +the memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +dynamic_arena_resize_bytes_non_zeroed :: proc( + a: ^Dynamic_Arena, + old_data: []byte, + size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + old_memory := raw_data(old_data) + old_size := len(old_data) + if old_size >= size { + return byte_slice(old_memory, size), nil + } + data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc) + if err == nil { + runtime.copy(data, byte_slice(old_memory, old_size)) + } + return data, err +} + +dynamic_arena_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size: int, + alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + arena := (^Dynamic_Arena)(allocator_data) switch mode { case .Alloc: - if size > 0 { - panic("mem: panic allocator, .Alloc called", loc=loc) - } + return dynamic_arena_alloc_bytes(arena, size, loc) case .Alloc_Non_Zeroed: - if size > 0 { - panic("mem: panic allocator, .Alloc_Non_Zeroed called", loc=loc) - } - case .Resize: - if size > 0 { - panic("mem: panic allocator, .Resize called", loc=loc) - } - case .Resize_Non_Zeroed: - if size > 0 { - panic("mem: panic allocator, .Resize_Non_Zeroed called", loc=loc) - } + return dynamic_arena_alloc_bytes_non_zeroed(arena, size, loc) case .Free: - if old_memory != nil { - panic("mem: panic allocator, .Free called", loc=loc) - } + return nil, .Mode_Not_Implemented case .Free_All: - panic("mem: panic allocator, .Free_All called", loc=loc) - + dynamic_arena_free_all(arena, loc) + case .Resize: + return dynamic_arena_resize_bytes(arena, byte_slice(old_memory, old_size), size, loc) + case .Resize_Non_Zeroed: + return dynamic_arena_resize_bytes_non_zeroed(arena, byte_slice(old_memory, old_size), size, loc) case .Query_Features: set := (^Allocator_Mode_Set)(old_memory) if set != nil { - set^ = {.Query_Features} + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features, .Query_Info} } return nil, nil - case .Query_Info: - panic("mem: panic allocator, .Query_Info called", loc=loc) + info := (^Allocator_Query_Info)(old_memory) + if info != nil && info.pointer != nil { + info.size = arena.block_size + info.alignment = arena.alignment + return byte_slice(info, size_of(info^)), nil + } + return nil, nil } - return nil, nil } -@(require_results) -panic_allocator :: proc() -> Allocator { - return Allocator{ - procedure = panic_allocator_proc, - data = nil, - } -} - - - - - +/* +Header of the buddy block. +*/ Buddy_Block :: struct #align(align_of(uint)) { size: uint, is_free: bool, } +/* +Obtain the next buddy block. +*/ @(require_results) buddy_block_next :: proc(block: ^Buddy_Block) -> ^Buddy_Block { return (^Buddy_Block)(([^]byte)(block)[block.size:]) } +/* +Split the block into two, by truncating the given block to a given size. +*/ @(require_results) buddy_block_split :: proc(block: ^Buddy_Block, size: uint) -> ^Buddy_Block { block := block @@ -894,12 +1971,14 @@ buddy_block_split :: proc(block: ^Buddy_Block, size: uint) -> ^Buddy_Block { return nil } +/* +Coalesce contiguous blocks in a range of blocks into one. +*/ buddy_block_coalescence :: proc(head, tail: ^Buddy_Block) { for { // Keep looping until there are no more buddies to coalesce block := head buddy := buddy_block_next(block) - no_coalescence := true for block < tail && buddy < tail { // make sure the buddies are within the range if block.is_free && buddy.is_free && block.size == buddy.size { @@ -922,28 +2001,26 @@ buddy_block_coalescence :: proc(head, tail: ^Buddy_Block) { } } } - if no_coalescence { return } } } - +/* +Find the best block for storing a given size in a range of blocks. +*/ @(require_results) buddy_block_find_best :: proc(head, tail: ^Buddy_Block, size: uint) -> ^Buddy_Block { assert(size != 0) - best_block: ^Buddy_Block block := head // left buddy := buddy_block_next(block) // right - // The entire memory section between head and tail is free, // just call 'buddy_block_split' to get the allocation if buddy == tail && block.is_free { return buddy_block_split(block, size) } - // Find the block which is the 'best_block' to requested allocation sized for block < tail && buddy < tail { // make sure the buddies are within the range // If both buddies are free, coalesce them together @@ -954,7 +2031,6 @@ buddy_block_find_best :: proc(head, tail: ^Buddy_Block, size: uint) -> ^Buddy_Bl if size <= block.size && (best_block == nil || block.size <= best_block.size) { best_block = block } - block = buddy_block_next(buddy) if block < tail { // Delay the buddy block for the next iteration @@ -962,20 +2038,16 @@ buddy_block_find_best :: proc(head, tail: ^Buddy_Block, size: uint) -> ^Buddy_Bl } continue } - - if block.is_free && size <= block.size && (best_block == nil || block.size <= best_block.size) { best_block = block } - if buddy.is_free && size <= buddy.size && (best_block == nil || buddy.size < best_block.size) { // If each buddy are the same size, then it makes more sense // to pick the buddy as it "bounces around" less best_block = buddy } - if (block.size <= buddy.size) { block = buddy_block_next(buddy) if (block < tail) { @@ -988,23 +2060,33 @@ buddy_block_find_best :: proc(head, tail: ^Buddy_Block, size: uint) -> ^Buddy_Bl buddy = buddy_block_next(buddy) } } - if best_block != nil { // This will handle the case if the 'best_block' is also the perfect fit return buddy_block_split(best_block, size) } - // Maybe out of memory return nil } - +/* +The buddy allocator data. +*/ Buddy_Allocator :: struct { head: ^Buddy_Block, tail: ^Buddy_Block, alignment: uint, } +/* +Buddy allocator. + +The buddy allocator is a type of allocator that splits the backing buffer into +multiple regions called buddy blocks. Initially, the allocator only has one +block with the size of the backing buffer. Upon each allocation, the allocator +finds the smallest block that can fit the size of requested memory region, and +splits the block according to the allocation size. If no block can be found, +the contiguous free blocks are coalesced and the search is performed again. +*/ @(require_results) buddy_allocator :: proc(b: ^Buddy_Allocator) -> Allocator { return Allocator{ @@ -1013,48 +2095,97 @@ buddy_allocator :: proc(b: ^Buddy_Allocator) -> Allocator { } } -buddy_allocator_init :: proc(b: ^Buddy_Allocator, data: []byte, alignment: uint) { - assert(data != nil) - assert(is_power_of_two(uintptr(len(data)))) - assert(is_power_of_two(uintptr(alignment))) +/* +Initialize the buddy allocator. +This procedure initializes the buddy allocator `b` with a backing buffer `data` +and block alignment specified by `alignment`. +*/ +buddy_allocator_init :: proc(b: ^Buddy_Allocator, data: []byte, alignment: uint, loc := #caller_location) { + assert(data != nil) + assert(is_power_of_two(uintptr(len(data))), "Size of the backing buffer must be power of two", loc) + assert(is_power_of_two(uintptr(alignment)), "Alignment must be a power of two", loc) alignment := alignment if alignment < size_of(Buddy_Block) { alignment = size_of(Buddy_Block) } - ptr := raw_data(data) - assert(uintptr(ptr) % uintptr(alignment) == 0, "data is not aligned to minimum alignment") - + assert(uintptr(ptr) % uintptr(alignment) == 0, "data is not aligned to minimum alignment", loc) b.head = (^Buddy_Block)(ptr) - b.head.size = len(data) b.head.is_free = true - b.tail = buddy_block_next(b.head) - b.alignment = alignment } +/* +Get required block size to fit in the allocation as well as the alignment padding. +*/ @(require_results) buddy_block_size_required :: proc(b: ^Buddy_Allocator, size: uint) -> uint { size := size actual_size := b.alignment size += size_of(Buddy_Block) size = align_forward_uint(size, b.alignment) - for size > actual_size { actual_size <<= 1 } - return actual_size } +/* +Allocate memory from a buddy allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is zero-initialized. This procedure +returns a pointer to the allocated memory region. +*/ @(require_results) -buddy_allocator_alloc :: proc(b: ^Buddy_Allocator, size: uint, zeroed: bool) -> ([]byte, Allocator_Error) { +buddy_allocator_alloc :: proc(b: ^Buddy_Allocator, size: uint) -> (rawptr, Allocator_Error) { + bytes, err := buddy_allocator_alloc_bytes(b, size) + return raw_data(bytes), err +} + +/* +Allocate memory from a buddy allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is zero-initialized. This procedure +returns a slice of the allocated memory region. +*/ +@(require_results) +buddy_allocator_alloc_bytes :: proc(b: ^Buddy_Allocator, size: uint) -> ([]byte, Allocator_Error) { + bytes, err := buddy_allocator_alloc_bytes_non_zeroed(b, size) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} + +/* +Allocate non-initialized memory from a buddy allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is not explicitly zero-initialized. +This procedure returns a pointer to the allocated memory region. +*/ +@(require_results) +buddy_allocator_alloc_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint) -> (rawptr, Allocator_Error) { + bytes, err := buddy_allocator_alloc_bytes_non_zeroed(b, size) + return raw_data(bytes), err +} + +/* +Allocate non-initialized memory from a buddy allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is not explicitly zero-initialized. +This procedure returns a slice of the allocated memory region. +*/ +@(require_results) +buddy_allocator_alloc_bytes_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint) -> ([]byte, Allocator_Error) { if size != 0 { actual_size := buddy_block_size_required(b, size) - found := buddy_block_find_best(b.head, b.tail, actual_size) if found != nil { // Try to coalesce all the free buddy blocks and then search again @@ -1065,60 +2196,71 @@ buddy_allocator_alloc :: proc(b: ^Buddy_Allocator, size: uint, zeroed: bool) -> return nil, .Out_Of_Memory } found.is_free = false - data := ([^]byte)(found)[b.alignment:][:size] - if zeroed { - zero_slice(data) - } return data, nil } return nil, nil } +/* +Free memory to the buddy allocator. + +This procedure frees the memory region allocated at pointer `ptr`. + +If `ptr` is not the latest allocation and is not a leaked allocation, this +operation is a no-op. +*/ buddy_allocator_free :: proc(b: ^Buddy_Allocator, ptr: rawptr) -> Allocator_Error { if ptr != nil { if !(b.head <= ptr && ptr <= b.tail) { return .Invalid_Pointer } - block := (^Buddy_Block)(([^]byte)(ptr)[-b.alignment:]) block.is_free = true - buddy_block_coalescence(b.head, b.tail) } return nil } -buddy_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int,loc := #caller_location) -> ([]byte, Allocator_Error) { +/* +Free all memory to the buddy allocator. +*/ +buddy_allocator_free_all :: proc(b: ^Buddy_Allocator) { + alignment := b.alignment + head := ([^]byte)(b.head) + tail := ([^]byte)(b.tail) + data := head[:ptr_sub(tail, head)] + buddy_allocator_init(b, data, alignment) +} +buddy_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { b := (^Buddy_Allocator)(allocator_data) - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return buddy_allocator_alloc(b, uint(size), mode == .Alloc) + case .Alloc: + return buddy_allocator_alloc_bytes(b, uint(size)) + case .Alloc_Non_Zeroed: + return buddy_allocator_alloc_bytes_non_zeroed(b, uint(size)) case .Resize: - return default_resize_bytes_align(byte_slice(old_memory, old_size), size, alignment, buddy_allocator(b)) + return default_resize_bytes_align(byte_slice(old_memory, old_size), size, alignment, buddy_allocator(b), loc) case .Resize_Non_Zeroed: - return default_resize_bytes_align_non_zeroed(byte_slice(old_memory, old_size), size, alignment, buddy_allocator(b)) + return default_resize_bytes_align_non_zeroed(byte_slice(old_memory, old_size), size, alignment, buddy_allocator(b), loc) case .Free: return nil, buddy_allocator_free(b, old_memory) case .Free_All: - - alignment := b.alignment - head := ([^]byte)(b.head) - tail := ([^]byte)(b.tail) - data := head[:ptr_sub(tail, head)] - buddy_allocator_init(b, data, alignment) - + buddy_allocator_free_all(b) case .Query_Features: set := (^Allocator_Mode_Set)(old_memory) if set != nil { set^ = {.Query_Features, .Alloc, .Alloc_Non_Zeroed, .Resize, .Resize_Non_Zeroed, .Free, .Free_All, .Query_Info} } return nil, nil - case .Query_Info: info := (^Allocator_Query_Info)(old_memory) if info != nil && info.pointer != nil { @@ -1126,7 +2268,6 @@ buddy_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, if !(b.head <= ptr && ptr <= b.tail) { return nil, .Invalid_Pointer } - block := (^Buddy_Block)(([^]byte)(ptr)[-b.alignment:]) info.size = int(block.size) info.alignment = int(b.alignment) @@ -1134,6 +2275,83 @@ buddy_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, } return nil, nil } - return nil, nil } + +// An allocator that keeps track of allocation sizes and passes it along to resizes. +// This is useful if you are using a library that needs an equivalent of `realloc` but want to use +// the Odin allocator interface. +// +// You want to wrap your allocator into this one if you are trying to use any allocator that relies +// on the old size to work. +// +// The overhead of this allocator is an extra max(alignment, size_of(Header)) bytes allocated for each allocation, these bytes are +// used to store the size and original pointer. +Compat_Allocator :: struct { + parent: Allocator, +} + +compat_allocator_init :: proc(rra: ^Compat_Allocator, allocator := context.allocator) { + rra.parent = allocator +} + +compat_allocator :: proc(rra: ^Compat_Allocator) -> Allocator { + return Allocator{ + data = rra, + procedure = compat_allocator_proc, + } +} + +compat_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, + location := #caller_location) -> (data: []byte, err: Allocator_Error) { + size, old_size := size, old_size + + Header :: struct { + size: int, + ptr: rawptr, + } + + rra := (^Compat_Allocator)(allocator_data) + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + a := max(alignment, size_of(Header)) + size += a + assert(size >= 0, "overflow") + + allocation := rra.parent.procedure(rra.parent.data, mode, size, alignment, old_memory, old_size, location) or_return + #no_bounds_check data = allocation[a:] + + ([^]Header)(raw_data(data))[-1] = { + size = size, + ptr = raw_data(allocation), + } + return + + case .Free: + header := ([^]Header)(old_memory)[-1] + return rra.parent.procedure(rra.parent.data, mode, size, alignment, header.ptr, header.size, location) + + case .Resize, .Resize_Non_Zeroed: + header := ([^]Header)(old_memory)[-1] + + a := max(alignment, size_of(header)) + size += a + assert(size >= 0, "overflow") + + allocation := rra.parent.procedure(rra.parent.data, mode, size, alignment, header.ptr, header.size, location) or_return + #no_bounds_check data = allocation[a:] + + ([^]Header)(raw_data(data))[-1] = { + size = size, + ptr = raw_data(allocation), + } + return + + case .Free_All, .Query_Info, .Query_Features: + return rra.parent.procedure(rra.parent.data, mode, size, alignment, old_memory, old_size, location) + + case: unreachable() + } +} diff --git a/core/mem/doc.odin b/core/mem/doc.odin index 44c93f798..98755d797 100644 --- a/core/mem/doc.odin +++ b/core/mem/doc.odin @@ -1,34 +1,114 @@ /* -package mem implements various types of allocators. +The `mem` package implements various allocators and provides utility procedures +for dealing with memory, pointers and slices. +The documentation below describes basic concepts, applicable to the `mem` +package. -An example of how to use the `Tracking_Allocator` to track subsequent allocations -in your program and report leaks and bad frees: +## Pointers, multipointers, and slices -Example: - package foo +A *pointer* is an abstraction of an *address*, a numberic value representing the +location of an object in memory. That object is said to be *pointed to* by the +pointer. To obtain the address of a pointer, cast it to `uintptr`. - import "core:mem" - import "core:fmt" +A multipointer is a pointer that points to multiple objects. Unlike a pointer, +a multipointer can be indexed, but does not have a definite length. A slice is +a pointer that points to multiple objects equipped with the length, specifying +the amount of objects a slice points to. - _main :: proc() { - // do stuff - } +When object's values are read through a pointer, that operation is called a +*load* operation. When memory is read through a pointer, that operation is +called a *store* operation. Both of these operations can be called a *memory +access operation*. - main :: proc() { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - defer mem.tracking_allocator_destroy(&track) - context.allocator = mem.tracking_allocator(&track) +## Allocators - _main() +In C and C++ memory models, allocations of objects in memory are typically +treated individually with a generic allocator (The `malloc` procedure). Which in +some scenarios can lead to poor cache utilization, slowdowns on individual +objects' memory management and growing complexity of the code needing to keep +track of the pointers and their lifetimes. - for _, leak in track.allocation_map { - fmt.printf("%v leaked %m\n", leak.location, leak.size) - } - for bad_free in track.bad_free_array { - fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) - } - } +Using different kinds of *allocators* for different purposes can solve these +problems. The allocators are typically optimized for specific use-cases and +can potentially simplify the memory management code. + +For example, in the context of making a game, having an Arena allocator could +simplify allocations of any temporary memory, because the programmer doesn't +have to keep track of which objects need to be freed every time they are +allocated, because at the end of every frame the whole allocator is reset to +its initial state and all objects are freed at once. + +The allocators have different kinds of restrictions on object lifetimes, sizes, +alignment and can be a significant gain, if used properly. Odin supports +allocators on a language level. + +Operations such as `new`, `free` and `delete` by default will use +`context.allocator`, which can be overridden by the user. When an override +happens all called procedures will inherit the new context and use the same +allocator. + +We will define one concept to simplify the description of some allocator-related +procedures, which is ownership. If the memory was allocated via a specific +allocator, that allocator is said to be the *owner* of that memory region. To +note, unlike Rust, in Odin the memory ownership model is not strict. + +## Alignment + +An address is said to be *aligned to `N` bytes*, if the addresses's numeric +value is divisible by `N`. The number `N` in this case can be referred to as +the *alignment boundary*. Typically an alignment is a power of two integer +value. + +A *natural alignment* of an object is typically equal to its size. For example +a 16 bit integer has a natural alignment of 2 bytes. When an object is not +located on its natural alignment boundary, accesses to that object are +considered *unaligned*. + +Some machines issue a hardware **exception**, or experience **slowdowns** when a +memory access operation occurs from an unaligned address. Examples of such +operations are: + +- SIMD instructions on x86. These instructions require all memory accesses to be + on an address that is aligned to 16 bytes. +- On ARM unaligned loads have an extra cycle penalty. + +As such, many operations that allocate memory in this package allow to +explicitly specify the alignment of allocated pointers/slices. The default +alignment for all operations is specified in a constant `mem.DEFAULT_ALIGNMENT`. + +## Zero by default + +Whenever new memory is allocated, via an allocator, or on the stack, by default +Odin will zero-initialize that memory, even if it wasn't explicitly +initialized. This allows for some convenience in certain scenarios and ease of +debugging, which will not be described in detail here. + +However zero-initialization can be a cause of slowdowns, when allocating large +buffers. For this reason, allocators have `*_non_zeroed` modes of allocation +that allow the user to request for uninitialized memory and will avoid a +relatively expensive zero-filling of the buffer. + +## Naming conventions + +The word `size` is used to denote the **size in bytes**. The word `length` is +used to denote the count of objects. + +The allocation procedures use the following conventions: + +- If the name contains `alloc_bytes` or `resize_bytes`, then the procedure takes + in slice parameters and returns slices. +- If the procedure name contains `alloc` or `resize`, then the procedure takes + in a raw pointer and returns raw pointers. +- If the procedure name contains `free_bytes`, then the procedure takes in a + slice. +- If the procedure name contains `free`, then the procedure takes in a pointer. + +Higher-level allocation procedures follow the following naming scheme: + +- `new`: Allocates a single object +- `free`: Free a single object (opposite of `new`) +- `make`: Allocate a group of objects +- `delete`: Free a group of objects (opposite of `make`) */ package mem diff --git a/core/mem/mem.odin b/core/mem/mem.odin index d423cc1eb..67ed56c39 100644 --- a/core/mem/mem.odin +++ b/core/mem/mem.odin @@ -3,49 +3,185 @@ package mem import "base:runtime" import "base:intrinsics" -Byte :: runtime.Byte +/* +The size, in bytes, of a single byte. + +This constant is equal to the value of `1`. +*/ +Byte :: runtime.Byte + +/* +The size, in bytes, of one kilobyte. + +This constant is equal to the amount of bytes in one kilobyte (also known as +kibibyte), which is equal to 1024 bytes. +*/ Kilobyte :: runtime.Kilobyte + +/* +The size, in bytes, of one megabyte. + +This constant is equal to the amount of bytes in one megabyte (also known as +mebibyte), which is equal to 1024 kilobyte. +*/ Megabyte :: runtime.Megabyte + +/* +The size, in bytes, of one gigabyte. + +This constant is equal to the amount of bytes in one gigabyte (also known as +gibiibyte), which is equal to 1024 megabytes. +*/ Gigabyte :: runtime.Gigabyte + +/* +The size, in bytes, of one terabyte. + +This constant is equal to the amount of bytes in one terabyte (also known as +tebiibyte), which is equal to 1024 gigabytes. +*/ Terabyte :: runtime.Terabyte + +/* +The size, in bytes, of one petabyte. + +This constant is equal to the amount of bytes in one petabyte (also known as +pebiibyte), which is equal to 1024 terabytes. +*/ Petabyte :: runtime.Petabyte -Exabyte :: runtime.Exabyte +/* +The size, in bytes, of one exabyte. + +This constant is equal to the amount of bytes in one exabyte (also known as +exbibyte), which is equal to 1024 petabytes. +*/ +Exabyte :: runtime.Exabyte + +/* +Set each byte of a memory range to a specific value. + +This procedure copies value specified by the `value` parameter into each of the +`len` bytes of a memory range, located at address `data`. + +This procedure returns the pointer to `data`. +*/ set :: proc "contextless" (data: rawptr, value: byte, len: int) -> rawptr { return runtime.memset(data, i32(value), len) } + +/* +Set each byte of a memory range to zero. + +This procedure copies the value `0` into the `len` bytes of a memory range, +starting at address `data`. + +This procedure returns the pointer to `data`. +*/ zero :: proc "contextless" (data: rawptr, len: int) -> rawptr { intrinsics.mem_zero(data, len) return data } + +/* +Set each byte of a memory range to zero. + +This procedure copies the value `0` into the `len` bytes of a memory range, +starting at address `data`. + +This procedure returns the pointer to `data`. + +Unlike the `zero()` procedure, which can be optimized away or reordered by the +compiler under certain circumstances, `zero_explicit()` procedure can not be +optimized away or reordered with other memory access operations, and the +compiler assumes volatile semantics of the memory. +*/ zero_explicit :: proc "contextless" (data: rawptr, len: int) -> rawptr { // This routine tries to avoid the compiler optimizing away the call, - // so that it is always executed. It is intended to provided + // so that it is always executed. It is intended to provide // equivalent semantics to those provided by the C11 Annex K 3.7.4.1 // memset_s call. intrinsics.mem_zero_volatile(data, len) // Use the volatile mem_zero intrinsics.atomic_thread_fence(.Seq_Cst) // Prevent reordering return data } + +/* +Zero-fill the memory of an object. + +This procedure sets each byte of the object pointed to by the pointer `item` +to zero, and returns the pointer to `item`. +*/ zero_item :: proc "contextless" (item: $P/^$T) -> P { intrinsics.mem_zero(item, size_of(T)) return item } + +/* +Zero-fill the memory of the slice. + +This procedure sets each byte of the slice pointed to by the slice `data` +to zero, and returns the slice `data`. +*/ zero_slice :: proc "contextless" (data: $T/[]$E) -> T { zero(raw_data(data), size_of(E)*len(data)) return data } +/* +Copy bytes from one memory range to another. +This procedure copies `len` bytes of data, from the memory range pointed to by +the `src` pointer into the memory range pointed to by the `dst` pointer, and +returns the `dst` pointer. +*/ copy :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { intrinsics.mem_copy(dst, src, len) return dst } + +/* +Copy bytes between two non-overlapping memory ranges. + +This procedure copies `len` bytes of data, from the memory range pointed to by +the `src` pointer into the memory range pointed to by the `dst` pointer, and +returns the `dst` pointer. + +This is a slightly more optimized version of the `copy` procedure that requires +that memory ranges specified by the parameters to this procedure are not +overlapping. If the memory ranges specified by `dst` and `src` pointers overlap, +the behavior of this function may be unpredictable. +*/ copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { intrinsics.mem_copy_non_overlapping(dst, src, len) return dst } +/* +Compare two memory ranges defined by slices. + +This procedure performs a byte-by-byte comparison between memory ranges +specified by slices `a` and `b`, and returns a value, specifying their relative +ordering. + +If the return value is: +- Equal to `-1`, then `a` is "smaller" than `b`. +- Equal to `+1`, then `a` is "bigger" than `b`. +- Equal to `0`, then `a` and `b` are equal. + +The comparison is performed as follows: +1. Each byte, upto `min(len(a), len(b))` bytes is compared between `a` and `b`. + - If the byte in slice `a` is smaller than a byte in slice `b`, then comparison + stops and this procedure returns `-1`. + - If the byte in slice `a` is bigger than a byte in slice `b`, then comparison + stops and this procedure returns `+1`. + - Otherwise the comparison continues until `min(len(a), len(b))` are compared. +2. If all the bytes in the range are equal, then the lengths of the slices are + compared. + - If the length of slice `a` is smaller than the length of slice `b`, then `-1` is returned. + - If the length of slice `b` is smaller than the length of slice `b`, then `+1` is returned. + - Otherwise `0` is returned. +*/ @(require_results) compare :: proc "contextless" (a, b: []byte) -> int { res := compare_byte_ptrs(raw_data(a), raw_data(b), min(len(a), len(b))) @@ -57,16 +193,89 @@ compare :: proc "contextless" (a, b: []byte) -> int { return res } +/* +Compare two memory ranges defined by byte pointers. + +This procedure performs a byte-by-byte comparison between memory ranges of size +`n` located at addresses `a` and `b`, and returns a value, specifying their relative +ordering. + +If the return value is: +- Equal to `-1`, then `a` is "smaller" than `b`. +- Equal to `+1`, then `a` is "bigger" than `b`. +- Equal to `0`, then `a` and `b` are equal. + +The comparison is performed as follows: +1. Each byte, upto `n` bytes is compared between `a` and `b`. + - If the byte in `a` is smaller than a byte in `b`, then comparison stops + and this procedure returns `-1`. + - If the byte in `a` is bigger than a byte in `b`, then comparison stops + and this procedure returns `+1`. + - Otherwise the comparison continues until `n` bytes are compared. +2. If all the bytes in the range are equal, this procedure returns `0`. +*/ @(require_results) compare_byte_ptrs :: proc "contextless" (a, b: ^byte, n: int) -> int #no_bounds_check { return runtime.memory_compare(a, b, n) } +/* +Compare two memory ranges defined by pointers. + +This procedure performs a byte-by-byte comparison between memory ranges of size +`n` located at addresses `a` and `b`, and returns a value, specifying their relative +ordering. + +If the return value is: +- Equal to `-1`, then `a` is "smaller" than `b`. +- Equal to `+1`, then `a` is "bigger" than `b`. +- Equal to `0`, then `a` and `b` are equal. + +The comparison is performed as follows: +1. Each byte, upto `n` bytes is compared between `a` and `b`. + - If the byte in `a` is smaller than a byte in `b`, then comparison stops + and this procedure returns `-1`. + - If the byte in `a` is bigger than a byte in `b`, then comparison stops + and this procedure returns `+1`. + - Otherwise the comparison continues until `n` bytes are compared. +2. If all the bytes in the range are equal, this procedure returns `0`. +*/ +@(require_results) +compare_ptrs :: proc "contextless" (a, b: rawptr, n: int) -> int { + return compare_byte_ptrs((^byte)(a), (^byte)(b), n) +} + +/* +Check whether two objects are equal on binary level. + +This procedure checks whether the memory ranges occupied by objects `a` and +`b` are equal. See `compare_byte_ptrs()` for how this comparison is done. +*/ +@(require_results) +simple_equal :: proc "contextless" (a, b: $T) -> bool where intrinsics.type_is_simple_compare(T) { + a, b := a, b + return compare_byte_ptrs((^byte)(&a), (^byte)(&b), size_of(T)) == 0 +} + +/* +Check if the memory range defined by a slice is zero-filled. + +This procedure checks whether every byte, pointed to by the slice, specified +by the parameter `data`, is zero. If all bytes of the slice are zero, this +procedure returns `true`. Otherwise this procedure returns `false`. +*/ @(require_results) check_zero :: proc(data: []byte) -> bool { return check_zero_ptr(raw_data(data), len(data)) } +/* +Check if the memory range defined defined by a pointer is zero-filled. + +This procedure checks whether each of the `len` bytes, starting at address +`ptr` is zero. If all bytes of this range are zero, this procedure returns +`true`. Otherwise this procedure returns `false`. +*/ @(require_results) check_zero_ptr :: proc(ptr: rawptr, len: int) -> bool { switch { @@ -81,57 +290,99 @@ check_zero_ptr :: proc(ptr: rawptr, len: int) -> bool { case 4: return intrinsics.unaligned_load((^u32)(ptr)) == 0 case 8: return intrinsics.unaligned_load((^u64)(ptr)) == 0 } - start := uintptr(ptr) start_aligned := align_forward_uintptr(start, align_of(uintptr)) end := start + uintptr(len) end_aligned := align_backward_uintptr(end, align_of(uintptr)) - for b in start..<start_aligned { if (^byte)(b)^ != 0 { return false } } - for b := start_aligned; b < end_aligned; b += size_of(uintptr) { if (^uintptr)(b)^ != 0 { return false } } - for b in end_aligned..<end { if (^byte)(b)^ != 0 { return false } } - return true } -@(require_results) -simple_equal :: proc "contextless" (a, b: $T) -> bool where intrinsics.type_is_simple_compare(T) { - a, b := a, b - return compare_byte_ptrs((^byte)(&a), (^byte)(&b), size_of(T)) == 0 -} +/* +Offset a given pointer by a given amount. -@(require_results) -compare_ptrs :: proc "contextless" (a, b: rawptr, n: int) -> int { - return compare_byte_ptrs((^byte)(a), (^byte)(b), n) -} +This procedure offsets the pointer `ptr` to an object of type `T`, by the amount +of bytes specified by `offset*size_of(T)`, and returns the pointer `ptr`. +**Note**: Prefer to use multipointer types, if possible. +*/ ptr_offset :: intrinsics.ptr_offset + +/* +Offset a given pointer by a given amount backwards. + +This procedure offsets the pointer `ptr` to an object of type `T`, by the amount +of bytes specified by `offset*size_of(T)` in the negative direction, and +returns the pointer `ptr`. +*/ ptr_sub :: intrinsics.ptr_sub +/* +Construct a slice from pointer and length. + +This procedure creates a slice, that points to `len` amount of objects located +at an address, specified by `ptr`. +*/ @(require_results) slice_ptr :: proc "contextless" (ptr: ^$T, len: int) -> []T { return ([^]T)(ptr)[:len] } +/* +Construct a byte slice from raw pointer and length. + +This procedure creates a byte slice, that points to `len` amount of bytes +located at an address specified by `data`. +*/ @(require_results) byte_slice :: #force_inline proc "contextless" (data: rawptr, #any_int len: int) -> []byte { return ([^]u8)(data)[:max(len, 0)] } +/* +Create a byte slice from pointer and length. + +This procedure creates a byte slice, pointing to `len` objects, starting from +the address specified by `ptr`. +*/ +@(require_results) +ptr_to_bytes :: proc "contextless" (ptr: ^$T, len := 1) -> []byte { + return transmute([]byte)Raw_Slice{ptr, len*size_of(T)} +} + +/* +Obtain the slice, pointing to the contents of `any`. + +This procedure returns the slice, pointing to the contents of the specified +value of the `any` type. +*/ +@(require_results) +any_to_bytes :: proc "contextless" (val: any) -> []byte { + ti := type_info_of(val.id) + size := ti != nil ? ti.size : 0 + return transmute([]byte)Raw_Slice{val.data, size} +} + +/* +Obtain a byte slice from any slice. + +This procedure returns a slice, that points to the same bytes as the slice, +specified by `slice` and returns the resulting byte slice. +*/ @(require_results) slice_to_bytes :: proc "contextless" (slice: $E/[]$T) -> []byte { s := transmute(Raw_Slice)slice @@ -139,6 +390,15 @@ slice_to_bytes :: proc "contextless" (slice: $E/[]$T) -> []byte { return transmute([]byte)s } +/* +Transmute slice to a different type. + +This procedure performs an operation similar to transmute, returning a slice of +type `T` that points to the same bytes as the slice specified by `slice` +parameter. Unlike plain transmute operation, this procedure adjusts the length +of the resulting slice, such that the resulting slice points to the correct +amount of objects to cover the memory region pointed to by `slice`. +*/ @(require_results) slice_data_cast :: proc "contextless" ($T: typeid/[]$A, slice: $S/[]$B) -> T { when size_of(A) == 0 || size_of(B) == 0 { @@ -150,12 +410,25 @@ slice_data_cast :: proc "contextless" ($T: typeid/[]$A, slice: $S/[]$B) -> T { } } +/* +Obtain data and length of a slice. + +This procedure returns the pointer to the start of the memory region pointed to +by slice `slice` and the length of the slice. +*/ @(require_results) slice_to_components :: proc "contextless" (slice: $E/[]$T) -> (data: ^T, len: int) { s := transmute(Raw_Slice)slice return (^T)(s.data), s.len } +/* +Create a dynamic array from slice. + +This procedure creates a dynamic array, using slice `backing` as the backing +buffer for the dynamic array. The resulting dynamic array can not grow beyond +the size of the specified slice. +*/ @(require_results) buffer_from_slice :: proc "contextless" (backing: $T/[]$E) -> [dynamic]E { return transmute([dynamic]E)Raw_Dynamic_Array{ @@ -169,19 +442,12 @@ buffer_from_slice :: proc "contextless" (backing: $T/[]$E) -> [dynamic]E { } } -@(require_results) -ptr_to_bytes :: proc "contextless" (ptr: ^$T, len := 1) -> []byte { - return transmute([]byte)Raw_Slice{ptr, len*size_of(T)} -} - -@(require_results) -any_to_bytes :: proc "contextless" (val: any) -> []byte { - ti := type_info_of(val.id) - size := ti != nil ? ti.size : 0 - return transmute([]byte)Raw_Slice{val.data, size} -} - +/* +Check whether a number is a power of two. +This procedure checks whether a given pointer-sized unsigned integer contains +a power-of-two value. +*/ @(require_results) is_power_of_two :: proc "contextless" (x: uintptr) -> bool { if x <= 0 { @@ -190,66 +456,167 @@ is_power_of_two :: proc "contextless" (x: uintptr) -> bool { return (x & (x-1)) == 0 } -@(require_results) -align_forward :: proc(ptr: rawptr, align: uintptr) -> rawptr { - return rawptr(align_forward_uintptr(uintptr(ptr), align)) +/* +Check if a pointer is aligned. + +This procedure checks whether a pointer `x` is aligned to a boundary specified +by `align`, and returns `true` if the pointer is aligned, and false otherwise. +*/ +is_aligned :: proc "contextless" (x: rawptr, align: int) -> bool { + p := uintptr(x) + return (p & (1<<uintptr(align) - 1)) == 0 } +/* +Align uintptr forward. + +This procedure returns the next address after `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) align_forward_uintptr :: proc(ptr, align: uintptr) -> uintptr { assert(is_power_of_two(align)) + return (ptr + align-1) & ~(align-1) +} - p := ptr - modulo := p & (align-1) - if modulo != 0 { - p += align - modulo - } - return p +/* +Align pointer forward. + +This procedure returns the next address after `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ +@(require_results) +align_forward :: proc(ptr: rawptr, align: uintptr) -> rawptr { + return rawptr(align_forward_uintptr(uintptr(ptr), align)) } +/* +Align int forward. + +This procedure returns the next address after `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) align_forward_int :: proc(ptr, align: int) -> int { return int(align_forward_uintptr(uintptr(ptr), uintptr(align))) } + +/* +Align uint forward. + +This procedure returns the next address after `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) align_forward_uint :: proc(ptr, align: uint) -> uint { return uint(align_forward_uintptr(uintptr(ptr), uintptr(align))) } +/* +Align uintptr backwards. + +This procedure returns the previous address before `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) -align_backward :: proc(ptr: rawptr, align: uintptr) -> rawptr { - return rawptr(align_backward_uintptr(uintptr(ptr), align)) +align_backward_uintptr :: proc(ptr, align: uintptr) -> uintptr { + assert(is_power_of_two(align)) + return ptr & ~(align-1) } +/* +Align rawptr backwards. + +This procedure returns the previous address before `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) -align_backward_uintptr :: proc(ptr, align: uintptr) -> uintptr { - return align_forward_uintptr(ptr - align + 1, align) +align_backward :: proc(ptr: rawptr, align: uintptr) -> rawptr { + return rawptr(align_backward_uintptr(uintptr(ptr), align)) } +/* +Align int backwards. + +This procedure returns the previous address before `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) align_backward_int :: proc(ptr, align: int) -> int { return int(align_backward_uintptr(uintptr(ptr), uintptr(align))) } + +/* +Align uint backwards. + +This procedure returns the previous address before `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) align_backward_uint :: proc(ptr, align: uint) -> uint { return uint(align_backward_uintptr(uintptr(ptr), uintptr(align))) } +/* +Create a context with a given allocator. + +This procedure returns a copy of the current context with the allocator replaced +by the allocator `a`. +*/ @(require_results) context_from_allocator :: proc(a: Allocator) -> type_of(context) { context.allocator = a return context } +/* +Copy the value from a pointer into a value. + +This procedure copies the object of type `T` pointed to by the pointer `ptr` +into a new stack-allocated value and returns that value. +*/ @(require_results) reinterpret_copy :: proc "contextless" ($T: typeid, ptr: rawptr) -> (value: T) { copy(&value, ptr, size_of(T)) return } +/* +Dynamic array with a fixed capacity buffer. +This type represents dynamic arrays with a fixed-size backing buffer. Upon +allocating memory beyond reaching the maximum capacity, allocations from fixed +byte buffers return `nil` and no error. +*/ Fixed_Byte_Buffer :: distinct [dynamic]byte +/* +Create a fixed byte buffer from a slice. +*/ @(require_results) make_fixed_byte_buffer :: proc "contextless" (backing: []byte) -> Fixed_Byte_Buffer { s := transmute(Raw_Slice)backing @@ -264,40 +631,60 @@ make_fixed_byte_buffer :: proc "contextless" (backing: []byte) -> Fixed_Byte_Buf return transmute(Fixed_Byte_Buffer)d } +/* +General-purpose align formula. - +This procedure is equivalent to `align_forward`, but it does not require the +alignment to be a power of two. +*/ @(require_results) align_formula :: proc "contextless" (size, align: int) -> int { result := size + align-1 return result - result%align } +/* +Calculate the padding for header preceding aligned data. + +This procedure returns the padding, following the specified pointer `ptr` that +will be able to fit in a header of the size `header_size`, immediately +preceding the memory region, aligned on a boundary specified by `align`. See +the following diagram for a visual representation. + + header size + |<------>| + +---+--------+------------- - - - + | HEADER | DATA... + +---+--------+------------- - - - + ^ ^ + |<---------->| + | padding | + ptr aligned ptr + +The function takes in `ptr` and `header_size`, as well as the required +alignment for `DATA`. The return value of the function is the padding between +`ptr` and `aligned_ptr` that will be able to fit the header. +*/ @(require_results) calc_padding_with_header :: proc "contextless" (ptr: uintptr, align: uintptr, header_size: int) -> int { p, a := ptr, align modulo := p & (a-1) - padding := uintptr(0) if modulo != 0 { padding = a - modulo } - needed_space := uintptr(header_size) if padding < needed_space { needed_space -= padding - if needed_space & (a-1) > 0 { padding += align * (1+(needed_space/align)) } else { padding += align * (needed_space/align) } } - return int(padding) } - - @(require_results, deprecated="prefer 'slice.clone'") clone_slice :: proc(slice: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> (new_slice: T) { new_slice, _ = make(T, len(slice), allocator, loc) diff --git a/core/mem/mutex_allocator.odin b/core/mem/mutex_allocator.odin index 591703eab..b8062bca1 100644 --- a/core/mem/mutex_allocator.odin +++ b/core/mem/mutex_allocator.odin @@ -1,19 +1,33 @@ -//+build !freestanding +#+build !freestanding package mem import "core:sync" +/* +The data for mutex allocator. +*/ Mutex_Allocator :: struct { backing: Allocator, mutex: sync.Mutex, } +/* +Initialize the mutex allocator. + +This procedure initializes the mutex allocator using `backin_allocator` as the +allocator that will be used to pass all allocation requests through. +*/ mutex_allocator_init :: proc(m: ^Mutex_Allocator, backing_allocator: Allocator) { m.backing = backing_allocator m.mutex = {} } +/* +Mutex allocator. +The mutex allocator is a wrapper for allocators that is used to serialize all +allocator requests across multiple threads. +*/ @(require_results) mutex_allocator :: proc(m: ^Mutex_Allocator) -> Allocator { return Allocator{ @@ -22,11 +36,16 @@ mutex_allocator :: proc(m: ^Mutex_Allocator) -> Allocator { } } -mutex_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> (result: []byte, err: Allocator_Error) { +mutex_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size: int, + alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> (result: []byte, err: Allocator_Error) { m := (^Mutex_Allocator)(allocator_data) - sync.mutex_guard(&m.mutex) return m.backing.procedure(m.backing.data, mode, size, alignment, old_memory, old_size, loc) } diff --git a/core/mem/raw.odin b/core/mem/raw.odin index f56206957..41c91555e 100644 --- a/core/mem/raw.odin +++ b/core/mem/raw.odin @@ -3,26 +3,100 @@ package mem import "base:builtin" import "base:runtime" -Raw_Any :: runtime.Raw_Any -Raw_String :: runtime.Raw_String -Raw_Cstring :: runtime.Raw_Cstring -Raw_Slice :: runtime.Raw_Slice +/* +Memory layout of the `any` type. +*/ +Raw_Any :: runtime.Raw_Any + +/* +Memory layout of the `string` type. +*/ +Raw_String :: runtime.Raw_String + +/* +Memory layout of the `cstring` type. +*/ +Raw_Cstring :: runtime.Raw_Cstring + +/* +Memory layout of `[]T` types. +*/ +Raw_Slice :: runtime.Raw_Slice + +/* +Memory layout of `[dynamic]T` types. +*/ Raw_Dynamic_Array :: runtime.Raw_Dynamic_Array -Raw_Map :: runtime.Raw_Map -Raw_Soa_Pointer :: runtime.Raw_Soa_Pointer -Raw_Complex32 :: runtime.Raw_Complex32 -Raw_Complex64 :: runtime.Raw_Complex64 -Raw_Complex128 :: runtime.Raw_Complex128 -Raw_Quaternion64 :: runtime.Raw_Quaternion64 +/* +Memory layout of `map[K]V` types. +*/ +Raw_Map :: runtime.Raw_Map + +/* +Memory layout of `#soa []T` types. +*/ +Raw_Soa_Pointer :: runtime.Raw_Soa_Pointer + +/* +Memory layout of the `complex32` type. +*/ +Raw_Complex32 :: runtime.Raw_Complex32 + +/* +Memory layout of the `complex64` type. +*/ +Raw_Complex64 :: runtime.Raw_Complex64 + +/* +Memory layout of the `complex128` type. +*/ +Raw_Complex128 :: runtime.Raw_Complex128 + +/* +Memory layout of the `quaternion64` type. +*/ +Raw_Quaternion64 :: runtime.Raw_Quaternion64 + +/* +Memory layout of the `quaternion128` type. +*/ Raw_Quaternion128 :: runtime.Raw_Quaternion128 + +/* +Memory layout of the `quaternion256` type. +*/ Raw_Quaternion256 :: runtime.Raw_Quaternion256 -Raw_Quaternion64_Vector_Scalar :: runtime.Raw_Quaternion64_Vector_Scalar + +/* +Memory layout of the `quaternion64` type. +*/ +Raw_Quaternion64_Vector_Scalar :: runtime.Raw_Quaternion64_Vector_Scalar + +/* +Memory layout of the `quaternion128` type. +*/ Raw_Quaternion128_Vector_Scalar :: runtime.Raw_Quaternion128_Vector_Scalar + +/* +Memory layout of the `quaternion256` type. +*/ Raw_Quaternion256_Vector_Scalar :: runtime.Raw_Quaternion256_Vector_Scalar +/* +Create a value of the any type. + +This procedure creates a value with type `any` that points to an object with +typeid `id` located at an address specified by `data`. +*/ make_any :: proc "contextless" (data: rawptr, id: typeid) -> any { return transmute(any)Raw_Any{data, id} } +/* +Obtain pointer to the data. + +This procedure returns the pointer to the data of a slice, string, or a dynamic +array. +*/ raw_data :: builtin.raw_data diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index f5e428d87..43ef10fe9 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -1,52 +1,36 @@ package mem -// The Rollback Stack Allocator was designed for the test runner to be fast, -// able to grow, and respect the Tracking Allocator's requirement for -// individual frees. It is not overly concerned with fragmentation, however. -// -// It has support for expansion when configured with a block allocator and -// limited support for out-of-order frees. -// -// Allocation has constant-time best and usual case performance. -// At worst, it is linear according to the number of memory blocks. -// -// Allocation follows a first-fit strategy when there are multiple memory -// blocks. -// -// Freeing has constant-time best and usual case performance. -// At worst, it is linear according to the number of memory blocks and number -// of freed items preceding the last item in a block. -// -// Resizing has constant-time performance, if it's the last item in a block, or -// the new size is smaller. Naturally, this becomes linear-time if there are -// multiple blocks to search for the pointer's owning block. Otherwise, the -// allocator defaults to a combined alloc & free operation internally. -// -// Out-of-order freeing is accomplished by collapsing a run of freed items -// from the last allocation backwards. -// -// Each allocation has an overhead of 8 bytes and any extra bytes to satisfy -// the requested alignment. - import "base:runtime" +/* +Rollback stack default block size. +*/ ROLLBACK_STACK_DEFAULT_BLOCK_SIZE :: 4 * Megabyte -// This limitation is due to the size of `prev_ptr`, but it is only for the -// head block; any allocation in excess of the allocator's `block_size` is -// valid, so long as the block allocator can handle it. -// -// This is because allocations over the block size are not split up if the item -// within is freed; they are immediately returned to the block allocator. -ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE :: 2 * Gigabyte +/* +Rollback stack max head block size. +This limitation is due to the size of `prev_ptr`, but it is only for the +head block; any allocation in excess of the allocator's `block_size` is +valid, so long as the block allocator can handle it. + +This is because allocations over the block size are not split up if the item +within is freed; they are immediately returned to the block allocator. +*/ +ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE :: 2 * Gigabyte +/* +Allocation header of the rollback stack allocator. +*/ Rollback_Stack_Header :: bit_field u64 { prev_offset: uintptr | 32, is_free: bool | 1, prev_ptr: uintptr | 31, } +/* +Block header of the rollback stack allocator. +*/ Rollback_Stack_Block :: struct { next_block: ^Rollback_Stack_Block, last_alloc: rawptr, @@ -54,13 +38,15 @@ Rollback_Stack_Block :: struct { buffer: []byte, } +/* +Rollback stack allocator data. +*/ Rollback_Stack :: struct { head: ^Rollback_Stack_Block, block_size: int, block_allocator: Allocator, } - @(private="file", require_results) rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { start := raw_data(block.buffer) @@ -110,6 +96,9 @@ rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_ } } +/* +Free memory to a rollback stack allocator. +*/ @(private="file", require_results) rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error { parent, block, header := rb_find_ptr(stack, ptr) or_return @@ -128,6 +117,9 @@ rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error { return nil } +/* +Free all memory owned by the rollback stack allocator. +*/ @(private="file") rb_free_all :: proc(stack: ^Rollback_Stack) { for block := stack.head.next_block; block != nil; /**/ { @@ -141,45 +133,75 @@ rb_free_all :: proc(stack: ^Rollback_Stack) { stack.head.offset = 0 } -@(private="file", require_results) -rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment: int) -> (result: []byte, err: Allocator_Error) { - if ptr != nil { - if block, _, ok := rb_find_last_alloc(stack, ptr); ok { - // `block.offset` should never underflow because it is contingent - // on `old_size` in the first place, assuming sane arguments. - assert(block.offset >= cast(uintptr)old_size, "Rollback Stack Allocator received invalid `old_size`.") - - if block.offset + cast(uintptr)size - cast(uintptr)old_size < cast(uintptr)len(block.buffer) { - // Prevent singleton allocations from fragmenting by forbidding - // them to shrink, removing the possibility of overflow bugs. - if len(block.buffer) <= stack.block_size { - block.offset += cast(uintptr)size - cast(uintptr)old_size - } - #no_bounds_check return (cast([^]byte)ptr)[:size], nil - } - } +/* +Allocate memory using the rollback stack allocator. +*/ +@(require_results) +rb_alloc :: proc( + stack: ^Rollback_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := rb_alloc_bytes_non_zeroed(stack, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) } + return raw_data(bytes), err +} - result = rb_alloc(stack, size, alignment) or_return - runtime.mem_copy_non_overlapping(raw_data(result), ptr, old_size) - err = rb_free(stack, ptr) +/* +Allocate memory using the rollback stack allocator. +*/ +@(require_results) +rb_alloc_bytes :: proc( + stack: ^Rollback_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := rb_alloc_bytes_non_zeroed(stack, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} - return +/* +Allocate non-initialized memory using the rollback stack allocator. +*/ +@(require_results) +rb_alloc_non_zeroed :: proc( + stack: ^Rollback_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := rb_alloc_bytes_non_zeroed(stack, size, alignment, loc) + return raw_data(bytes), err } -@(private="file", require_results) -rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byte, err: Allocator_Error) { +/* +Allocate non-initialized memory using the rollback stack allocator. +*/ +@(require_results) +rb_alloc_bytes_non_zeroed :: proc( + stack: ^Rollback_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (result: []byte, err: Allocator_Error) { + assert(size >= 0, "Size must be positive or zero.", loc) + assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc) parent: ^Rollback_Stack_Block for block := stack.head; /**/; block = block.next_block { when !ODIN_DISABLE_ASSERT { allocated_new_block: bool } - if block == nil { if stack.block_allocator.procedure == nil { return nil, .Out_Of_Memory } - minimum_size_required := size_of(Rollback_Stack_Header) + size + alignment - 1 new_block_size := max(minimum_size_required, stack.block_size) block = rb_make_block(new_block_size, stack.block_allocator) or_return @@ -188,10 +210,8 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt allocated_new_block = true } } - start := raw_data(block.buffer)[block.offset:] padding := cast(uintptr)calc_padding_with_header(cast(uintptr)start, cast(uintptr)alignment, size_of(Rollback_Stack_Header)) - if block.offset + padding + cast(uintptr)size > cast(uintptr)len(block.buffer) { when !ODIN_DISABLE_ASSERT { if allocated_new_block { @@ -201,54 +221,150 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt parent = block continue } - header := cast(^Rollback_Stack_Header)(start[padding - size_of(Rollback_Stack_Header):]) ptr := start[padding:] - header^ = { prev_offset = block.offset, prev_ptr = uintptr(0) if block.last_alloc == nil else cast(uintptr)block.last_alloc - cast(uintptr)raw_data(block.buffer), is_free = false, } - block.last_alloc = ptr block.offset += padding + cast(uintptr)size - if len(block.buffer) > stack.block_size { // This block exceeds the allocator's standard block size and is considered a singleton. // Prevent any further allocations on it. block.offset = cast(uintptr)len(block.buffer) } - #no_bounds_check return ptr[:size], nil } - return nil, .Out_Of_Memory } +/* +Resize an allocation owned by rollback stack allocator. +*/ +@(require_results) +rb_resize :: proc( + stack: ^Rollback_Stack, + old_ptr: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := rb_resize_bytes_non_zeroed(stack, byte_slice(old_ptr, old_size), size, alignment, loc) + if bytes != nil { + if old_ptr == nil { + zero_slice(bytes) + } else if size > old_size { + zero_slice(bytes[old_size:]) + } + } + return raw_data(bytes), err +} + +/* +Resize an allocation owned by rollback stack allocator. +*/ +@(require_results) +rb_resize_bytes :: proc( + stack: ^Rollback_Stack, + old_memory: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]u8, Allocator_Error) { + bytes, err := rb_resize_bytes_non_zeroed(stack, old_memory, size, alignment, loc) + if bytes != nil { + if old_memory == nil { + zero_slice(bytes) + } else if size > len(old_memory) { + zero_slice(bytes[len(old_memory):]) + } + } + return bytes, err +} + +/* +Resize an allocation owned by rollback stack allocator without explicit +zero-initialization. +*/ +@(require_results) +rb_resize_non_zeroed :: proc( + stack: ^Rollback_Stack, + old_ptr: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := rb_resize_bytes_non_zeroed(stack, byte_slice(old_ptr, old_size), size, alignment, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation owned by rollback stack allocator without explicit +zero-initialization. +*/ +@(require_results) +rb_resize_bytes_non_zeroed :: proc( + stack: ^Rollback_Stack, + old_memory: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (result: []byte, err: Allocator_Error) { + old_size := len(old_memory) + ptr := raw_data(old_memory) + assert(size >= 0, "Size must be positive or zero.", loc) + assert(old_size >= 0, "Old size must be positive or zero.", loc) + assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc) + if ptr != nil { + if block, _, ok := rb_find_last_alloc(stack, ptr); ok { + // `block.offset` should never underflow because it is contingent + // on `old_size` in the first place, assuming sane arguments. + assert(block.offset >= cast(uintptr)old_size, "Rollback Stack Allocator received invalid `old_size`.") + if block.offset + cast(uintptr)size - cast(uintptr)old_size < cast(uintptr)len(block.buffer) { + // Prevent singleton allocations from fragmenting by forbidding + // them to shrink, removing the possibility of overflow bugs. + if len(block.buffer) <= stack.block_size { + block.offset += cast(uintptr)size - cast(uintptr)old_size + } + #no_bounds_check return (ptr)[:size], nil + } + } + } + result = rb_alloc_bytes_non_zeroed(stack, size, alignment) or_return + runtime.mem_copy_non_overlapping(raw_data(result), ptr, old_size) + err = rb_free(stack, ptr) + return +} + @(private="file", require_results) rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) { buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return - block = cast(^Rollback_Stack_Block)raw_data(buffer) #no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):] return } - +/* +Initialize the rollback stack allocator using a fixed backing buffer. +*/ rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) { MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr) assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location) - block := cast(^Rollback_Stack_Block)raw_data(buffer) block^ = {} #no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):] - stack^ = {} stack.head = block stack.block_size = len(block.buffer) } +/* +Initialize the rollback stack alocator using a backing block allocator. +*/ rollback_stack_init_dynamic :: proc( stack: ^Rollback_Stack, block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE, @@ -261,22 +377,25 @@ rollback_stack_init_dynamic :: proc( // size is insufficient; check only on platforms with big enough ints. assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.", location) } - block := rb_make_block(block_size, block_allocator) or_return - stack^ = {} stack.head = block stack.block_size = block_size stack.block_allocator = block_allocator - return nil } +/* +Initialize the rollback stack. +*/ rollback_stack_init :: proc { rollback_stack_init_buffered, rollback_stack_init_dynamic, } +/* +Destroy a rollback stack. +*/ rollback_stack_destroy :: proc(stack: ^Rollback_Stack) { if stack.block_allocator.procedure != nil { rb_free_all(stack) @@ -285,6 +404,37 @@ rollback_stack_destroy :: proc(stack: ^Rollback_Stack) { stack^ = {} } +/* +Rollback stack allocator. + +The Rollback Stack Allocator was designed for the test runner to be fast, +able to grow, and respect the Tracking Allocator's requirement for +individual frees. It is not overly concerned with fragmentation, however. + +It has support for expansion when configured with a block allocator and +limited support for out-of-order frees. + +Allocation has constant-time best and usual case performance. +At worst, it is linear according to the number of memory blocks. + +Allocation follows a first-fit strategy when there are multiple memory +blocks. + +Freeing has constant-time best and usual case performance. +At worst, it is linear according to the number of memory blocks and number +of freed items preceding the last item in a block. + +Resizing has constant-time performance, if it's the last item in a block, or +the new size is smaller. Naturally, this becomes linear-time if there are +multiple blocks to search for the pointer's owning block. Otherwise, the +allocator defaults to a combined alloc & free operation internally. + +Out-of-order freeing is accomplished by collapsing a run of freed items +from the last allocation backwards. + +Each allocation has an overhead of 8 bytes and any extra bytes to satisfy +the requested alignment. +*/ @(require_results) rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator { return Allocator { @@ -294,48 +444,37 @@ rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator { } @(require_results) -rollback_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, location := #caller_location, +rollback_stack_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, ) -> (result: []byte, err: Allocator_Error) { stack := cast(^Rollback_Stack)allocator_data - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - assert(size >= 0, "Size must be positive or zero.", location) - assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location) - result = rb_alloc(stack, size, alignment) or_return - - if mode == .Alloc { - zero_slice(result) - } - + case .Alloc: + return rb_alloc_bytes(stack, size, alignment, loc) + case .Alloc_Non_Zeroed: + return rb_alloc_bytes_non_zeroed(stack, size, alignment, loc) case .Free: - err = rb_free(stack, old_memory) - + return nil, rb_free(stack, old_memory) case .Free_All: rb_free_all(stack) - - case .Resize, .Resize_Non_Zeroed: - assert(size >= 0, "Size must be positive or zero.", location) - assert(old_size >= 0, "Old size must be positive or zero.", location) - assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location) - result = rb_resize(stack, old_memory, old_size, size, alignment) or_return - - #no_bounds_check if mode == .Resize && size > old_size { - zero_slice(result[old_size:]) - } - + return nil, nil + case .Resize: + return rb_resize_bytes(stack, byte_slice(old_memory, old_size), size, alignment, loc) + case .Resize_Non_Zeroed: + return rb_resize_bytes_non_zeroed(stack, byte_slice(old_memory, old_size), size, alignment, loc) case .Query_Features: set := (^Allocator_Mode_Set)(old_memory) if set != nil { set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed} } return nil, nil - case .Query_Info: return nil, .Mode_Not_Implemented } - return } diff --git a/core/mem/tracking_allocator.odin b/core/mem/tracking_allocator.odin index 1b57e5fb4..5da38e62f 100644 --- a/core/mem/tracking_allocator.odin +++ b/core/mem/tracking_allocator.odin @@ -1,53 +1,88 @@ -//+build !freestanding +#+build !freestanding package mem import "base:runtime" import "core:sync" +/* +Allocation entry for the tracking allocator. + +This structure stores the data related to an allocation. +*/ Tracking_Allocator_Entry :: struct { - memory: rawptr, - size: int, + // Pointer to an allocated region. + memory: rawptr, + // Size of the allocated memory region. + size: int, + // Requested alignment. alignment: int, - mode: Allocator_Mode, - err: Allocator_Error, + // Mode of the operation. + mode: Allocator_Mode, + // Error. + err: Allocator_Error, + // Location of the allocation. location: runtime.Source_Code_Location, } + +/* +Bad free entry for a tracking allocator. +*/ Tracking_Allocator_Bad_Free_Entry :: struct { - memory: rawptr, + // Pointer, on which free operation was called. + memory: rawptr, + // The source location of where the operation was called. location: runtime.Source_Code_Location, } + +/* +Tracking allocator data. +*/ Tracking_Allocator :: struct { - backing: Allocator, - allocation_map: map[rawptr]Tracking_Allocator_Entry, - bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry, - mutex: sync.Mutex, + backing: Allocator, + allocation_map: map[rawptr]Tracking_Allocator_Entry, + bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry, + mutex: sync.Mutex, clear_on_free_all: bool, - - total_memory_allocated: i64, - total_allocation_count: i64, - total_memory_freed: i64, - total_free_count: i64, - peak_memory_allocated: i64, + total_memory_allocated: i64, + total_allocation_count: i64, + total_memory_freed: i64, + total_free_count: i64, + peak_memory_allocated: i64, current_memory_allocated: i64, } +/* +Initialize the tracking allocator. + +This procedure initializes the tracking allocator `t` with a backing allocator +specified with `backing_allocator`. The `internals_allocator` will used to +allocate the tracked data. +*/ tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) { t.backing = backing_allocator t.allocation_map.allocator = internals_allocator t.bad_free_array.allocator = internals_allocator - if .Free_All in query_features(t.backing) { t.clear_on_free_all = true } } +/* +Destroy the tracking allocator. +*/ tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) { delete(t.allocation_map) delete(t.bad_free_array) } +/* +Clear the tracking allocator. + +This procedure clears the tracked data from a tracking allocator. -// Clear only the current allocation data while keeping the totals intact. +**Note**: This procedure clears only the current allocation data while keeping +the totals intact. +*/ tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { sync.mutex_lock(&t.mutex) clear(&t.allocation_map) @@ -56,7 +91,11 @@ tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { sync.mutex_unlock(&t.mutex) } -// Reset all of a Tracking Allocator's allocation data back to zero. +/* +Reset the tracking allocator. + +Reset all of a Tracking Allocator's allocation data back to zero. +*/ tracking_allocator_reset :: proc(t: ^Tracking_Allocator) { sync.mutex_lock(&t.mutex) clear(&t.allocation_map) @@ -70,6 +109,39 @@ tracking_allocator_reset :: proc(t: ^Tracking_Allocator) { sync.mutex_unlock(&t.mutex) } +/* +Tracking allocator. + +The tracking allocator is an allocator wrapper that tracks memory allocations. +This allocator stores all the allocations in a map. Whenever a pointer that's +not inside of the map is freed, the `bad_free_array` entry is added. + +An example of how to use the `Tracking_Allocator` to track subsequent allocations +in your program and report leaks and bad frees: + +Example: + + package foo + + import "core:mem" + import "core:fmt" + + main :: proc() { + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + defer mem.tracking_allocator_destroy(&track) + context.allocator = mem.tracking_allocator(&track) + + do_stuff() + + for _, leak in track.allocation_map { + fmt.printf("%v leaked %m\n", leak.location, leak.size) + } + for bad_free in track.bad_free_array { + fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) + } + } +*/ @(require_results) tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { return Allocator{ @@ -78,9 +150,14 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { } } -tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> (result: []byte, err: Allocator_Error) { +tracking_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> (result: []byte, err: Allocator_Error) { track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { data.total_memory_allocated += i64(entry.size) data.total_allocation_count += 1 diff --git a/core/mem/virtual/arena.odin b/core/mem/virtual/arena.odin index 80c231c31..79407d80d 100644 --- a/core/mem/virtual/arena.odin +++ b/core/mem/virtual/arena.odin @@ -49,6 +49,10 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING arena.curr_block = memory_block_alloc(0, reserved, {}) or_return arena.total_used = 0 arena.total_reserved = arena.curr_block.reserved + + if arena.minimum_block_size == 0 { + arena.minimum_block_size = reserved + } return } diff --git a/core/mem/virtual/virtual.odin b/core/mem/virtual/virtual.odin index 4e53aba66..4afc33813 100644 --- a/core/mem/virtual/virtual.odin +++ b/core/mem/virtual/virtual.odin @@ -7,6 +7,11 @@ _ :: runtime DEFAULT_PAGE_SIZE := uint(4096) +@(init, private) +platform_memory_init :: proc() { + _platform_memory_init() +} + Allocator_Error :: mem.Allocator_Error @(require_results) diff --git a/core/mem/virtual/virtual_linux.odin b/core/mem/virtual/virtual_linux.odin index 0b4532baa..3e0d7668b 100644 --- a/core/mem/virtual/virtual_linux.odin +++ b/core/mem/virtual/virtual_linux.odin @@ -1,5 +1,5 @@ -//+build linux -//+private +#+build linux +#+private package mem_virtual import "core:sys/linux" diff --git a/core/mem/virtual/virtual_other.odin b/core/mem/virtual/virtual_other.odin index 4fcb61b04..a57856975 100644 --- a/core/mem/virtual/virtual_other.odin +++ b/core/mem/virtual/virtual_other.odin @@ -1,10 +1,10 @@ -//+private -//+build !darwin -//+build !freebsd -//+build !openbsd -//+build !netbsd -//+build !linux -//+build !windows +#+private +#+build !darwin +#+build !freebsd +#+build !openbsd +#+build !netbsd +#+build !linux +#+build !windows package mem_virtual _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { diff --git a/core/mem/virtual/virtual_platform.odin b/core/mem/virtual/virtual_platform.odin index c2b505cd2..54c42ce4b 100644 --- a/core/mem/virtual/virtual_platform.odin +++ b/core/mem/virtual/virtual_platform.odin @@ -1,4 +1,4 @@ -//+private +#+private package mem_virtual Platform_Memory_Block :: struct { diff --git a/core/mem/virtual/virtual_posix.odin b/core/mem/virtual/virtual_posix.odin index fbe89abfa..105849774 100644 --- a/core/mem/virtual/virtual_posix.odin +++ b/core/mem/virtual/virtual_posix.odin @@ -1,5 +1,5 @@ -//+build darwin, netbsd, freebsd, openbsd -//+private +#+build darwin, netbsd, freebsd, openbsd +#+private package mem_virtual import "core:sys/posix" @@ -51,8 +51,10 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) } _platform_memory_init :: proc() { - DEFAULT_PAGE_SIZE = posix.PAGE_SIZE - + // NOTE: `posix.PAGESIZE` due to legacy reasons could be wrong so we use `sysconf`. + size := posix.sysconf(._PAGESIZE) + DEFAULT_PAGE_SIZE = uint(max(size, posix.PAGESIZE)) + // is power of two assert(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0) } diff --git a/core/mem/virtual/virtual_windows.odin b/core/mem/virtual/virtual_windows.odin index ee47a01a8..acd30ae33 100644 --- a/core/mem/virtual/virtual_windows.odin +++ b/core/mem/virtual/virtual_windows.odin @@ -1,5 +1,5 @@ -//+build windows -//+private +#+build windows +#+private package mem_virtual foreign import Kernel32 "system:Kernel32.lib" diff --git a/core/net/addr.odin b/core/net/addr.odin index 1972d8c22..c47c6f55e 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin, freebsd +#+build windows, linux, darwin, freebsd package net /* diff --git a/core/net/common.odin b/core/net/common.odin index ed255356e..263fc770f 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin, freebsd +#+build windows, linux, darwin, freebsd package net /* diff --git a/core/net/dns.odin b/core/net/dns.odin index e82b54262..ffb97fc5b 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin, freebsd +#+build windows, linux, darwin, freebsd package net /* diff --git a/core/net/dns_unix.odin b/core/net/dns_unix.odin index 0448b8d9e..e4336e410 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 package net /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. diff --git a/core/net/dns_windows.odin b/core/net/dns_windows.odin index b7af050b1..2f3831767 100644 --- a/core/net/dns_windows.odin +++ b/core/net/dns_windows.odin @@ -1,4 +1,4 @@ -//+build windows +#+build windows package net /* @@ -128,33 +128,37 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator : append(&recs, record) case .SRV: - target := strings.clone(string(r.Data.SRV.pNameTarget)) // The target hostname/address that the service can be found on - priority := int(r.Data.SRV.wPriority) - weight := int(r.Data.SRV.wWeight) - port := int(r.Data.SRV.wPort) - // NOTE(tetra): Srv record name should be of the form '_servicename._protocol.hostname' // The record name is the name of the record. // Not to be confused with the _target_ of the record, which is--in combination with the port--what we're looking up // by making this request in the first place. - // NOTE(Jeroen): Service Name and Protocol Name can probably just be string slices into the record name. - // It's already cloned, after all. I wouldn't put them on the temp allocator like this. + service_name, protocol_name: string + + s := base_record.record_name + i := strings.index_byte(s, '.') + if i > -1 { + service_name = s[:i] + s = s[len(service_name) + 1:] + } else { + continue + } - parts := strings.split_n(base_record.record_name, ".", 3, context.temp_allocator) - if len(parts) != 3 { + i = strings.index_byte(s, '.') + if i > -1 { + protocol_name = s[:i] + } else { continue } - service_name, protocol_name := parts[0], parts[1] append(&recs, DNS_Record_SRV { base = base_record, - target = target, - port = port, + target = strings.clone(string(r.Data.SRV.pNameTarget)), // The target hostname/address that the service can be found on + port = int(r.Data.SRV.wPort), service_name = service_name, protocol_name = protocol_name, - priority = priority, - weight = weight, + priority = int(r.Data.SRV.wPriority), + weight = int(r.Data.SRV.wWeight), }) } diff --git a/core/net/doc.odin b/core/net/doc.odin index 996f8147e..ed720c0ae 100644 --- a/core/net/doc.odin +++ b/core/net/doc.odin @@ -13,36 +13,34 @@ */ /* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. - - Features: - - Supports Windows, Linux and OSX. - - Opening and closing of TCP and UDP sockets. - - Sending to and receiving from these sockets. - - DNS name lookup, using either the OS or our own resolver. - - Planned: - - Nonblocking IO - - `Connection` struct - A "fat socket" struct that remembers how you opened it, etc, instead of just being a handle. - - IP Range structs, CIDR/class ranges, netmask calculator and associated helper procedures. - - Use `context.temp_allocator` instead of stack-based arenas? - And check it's the default temp allocator or can give us 4 MiB worth of memory - without punting to the main allocator by comparing their addresses in an @(init) procedure. - Panic if this assumption is not met. - - - Document assumptions about libc usage (or avoidance thereof) for each platform. - - Assumptions: - - For performance reasons this package relies on the `context.temp_allocator` in some places. - - You can replace the default `context.temp_allocator` with your own as long as it meets - this requirement: A minimum of 4 MiB of scratch space that's expected not to be freed. - - If this expectation is not met, the package's @(init) procedure will attempt to detect - this and panic to avoid temp allocations prematurely overwriting data and garbling results, - or worse. This means that should you replace the temp allocator with an insufficient one, - we'll do our best to loudly complain the first time you try it. +Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. +For other protocols and their features, see subdirectories of this package. + +Features: +- Supports Windows, Linux and OSX. +- Opening and closing of TCP and UDP sockets. +- Sending to and receiving from these sockets. +- DNS name lookup, using either the OS or our own resolver. + +Planned: +- Nonblocking IO +- `Connection` struct; A "fat socket" struct that remembers how you opened it, etc, instead of just being a handle. +- IP Range structs, CIDR/class ranges, netmask calculator and associated helper procedures. +- Use `context.temp_allocator` instead of stack-based arenas? +And check it's the default temp allocator or can give us 4 MiB worth of memory +without punting to the main allocator by comparing their addresses in an @(init) procedure. +Panic if this assumption is not met. +- Document assumptions about libc usage (or avoidance thereof) for each platform. + +Assumptions: +For performance reasons this package relies on the `context.temp_allocator` in some places. + +You can replace the default `context.temp_allocator` with your own as long as it meets +this requirement: A minimum of 4 MiB of scratch space that's expected not to be freed. + +If this expectation is not met, the package's @(init) procedure will attempt to detect +this and panic to avoid temp allocations prematurely overwriting data and garbling results, +or worse. This means that should you replace the temp allocator with an insufficient one, +we'll do our best to loudly complain the first time you try it. */ -package net
\ No newline at end of file +package net diff --git a/core/net/errors_darwin.odin b/core/net/errors_darwin.odin index f2a0d6262..2905b44bc 100644 --- a/core/net/errors_darwin.odin +++ b/core/net/errors_darwin.odin @@ -1,5 +1,5 @@ +#+build darwin package net -// +build darwin /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. diff --git a/core/net/errors_freebsd.odin b/core/net/errors_freebsd.odin index 4830d1c03..486732a95 100644 --- a/core/net/errors_freebsd.odin +++ b/core/net/errors_freebsd.odin @@ -1,4 +1,4 @@ -//+build freebsd +#+build freebsd package net /* diff --git a/core/net/errors_linux.odin b/core/net/errors_linux.odin index 9047b4020..3cd51e6fd 100644 --- a/core/net/errors_linux.odin +++ b/core/net/errors_linux.odin @@ -1,5 +1,5 @@ +#+build linux package net -// +build linux /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. diff --git a/core/net/errors_windows.odin b/core/net/errors_windows.odin index ae928a05c..f41bcf888 100644 --- a/core/net/errors_windows.odin +++ b/core/net/errors_windows.odin @@ -1,5 +1,5 @@ +#+build windows package net -// +build windows /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. diff --git a/core/net/interface.odin b/core/net/interface.odin index 90444fb63..775a812f3 100644 --- a/core/net/interface.odin +++ b/core/net/interface.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin, freebsd +#+build windows, linux, darwin, freebsd package net /* diff --git a/core/net/interface_darwin.odin b/core/net/interface_darwin.odin index 7a33682de..4921bc3fe 100644 --- a/core/net/interface_darwin.odin +++ b/core/net/interface_darwin.odin @@ -1,5 +1,5 @@ +#+build darwin package net -//+build darwin /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. diff --git a/core/net/interface_freebsd.odin b/core/net/interface_freebsd.odin index a9a125299..50e2d1a96 100644 --- a/core/net/interface_freebsd.odin +++ b/core/net/interface_freebsd.odin @@ -1,4 +1,4 @@ -//+build freebsd +#+build freebsd package net /* diff --git a/core/net/interface_linux.odin b/core/net/interface_linux.odin index c6df8f0a2..28724735b 100644 --- a/core/net/interface_linux.odin +++ b/core/net/interface_linux.odin @@ -1,5 +1,5 @@ +#+build linux package net -//+build linux /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. diff --git a/core/net/interface_windows.odin b/core/net/interface_windows.odin index 67da6d034..a6eb72846 100644 --- a/core/net/interface_windows.odin +++ b/core/net/interface_windows.odin @@ -1,5 +1,5 @@ +#+build windows package net -//+build windows /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. diff --git a/core/net/socket.odin b/core/net/socket.odin index e36c67d21..950c7ac11 100644 --- a/core/net/socket.odin +++ b/core/net/socket.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin, freebsd +#+build windows, linux, darwin, freebsd package net /* @@ -134,6 +134,13 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TC return _listen_tcp(interface_endpoint, backlog) } +/* + Returns the endpoint that the given socket is listening / bound on. +*/ +bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Network_Error) { + return _bound_endpoint(socket) +} + accept_tcp :: proc(socket: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { return _accept_tcp(socket, options) } diff --git a/core/net/socket_darwin.odin b/core/net/socket_darwin.odin index a56d36de6..ec9255c3b 100644 --- a/core/net/socket_darwin.odin +++ b/core/net/socket_darwin.odin @@ -1,5 +1,5 @@ +#+build darwin package net -// +build darwin /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -22,6 +22,7 @@ package net import "core:c" import "core:os" +import "core:sys/posix" import "core:time" Socket_Option :: enum c.int { @@ -139,6 +140,19 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_ } @(private) +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { + addr: posix.sockaddr_storage + addr_len := posix.socklen_t(size_of(addr)) + res := posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) + if res != .OK { + err = Listen_Error(posix.errno()) + return + } + ep = _sockaddr_to_endpoint((^os.SOCKADDR_STORAGE_LH)(&addr)) + return +} + +@(private) _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { sockaddr: os.SOCKADDR_STORAGE_LH sockaddrlen := c.int(size_of(sockaddr)) diff --git a/core/net/socket_freebsd.odin b/core/net/socket_freebsd.odin index 00da5ec06..0f3a85cbb 100644 --- a/core/net/socket_freebsd.odin +++ b/core/net/socket_freebsd.odin @@ -1,4 +1,4 @@ -//+build freebsd +#+build freebsd package net /* @@ -150,6 +150,20 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T } @(private) +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { + sockaddr: freebsd.Socket_Address_Storage + + errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr) + if errno != nil { + err = cast(Listen_Error)errno + return + } + + ep = _sockaddr_to_endpoint(&sockaddr) + return +} + +@(private) _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { sockaddr: freebsd.Socket_Address_Storage diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index 52f328814..a3853874a 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -1,5 +1,5 @@ +#+build linux package net -// +build linux /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -203,6 +203,19 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (TCP_Socket, Network } @(private) +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { + addr: linux.Sock_Addr_Any + errno := linux.getsockname(_unwrap_os_socket(sock), &addr) + if errno != .NONE { + err = Listen_Error(errno) + return + } + + ep = _wrap_os_addr(addr) + return +} + +@(private) _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (tcp_client: TCP_Socket, endpoint: Endpoint, err: Network_Error) { addr: linux.Sock_Addr_Any client_sock, errno := linux.accept(linux.Fd(sock), &addr) diff --git a/core/net/socket_windows.odin b/core/net/socket_windows.odin index 8ee75bc3b..20f17619d 100644 --- a/core/net/socket_windows.odin +++ b/core/net/socket_windows.odin @@ -1,5 +1,5 @@ +#+build windows package net -// +build windows /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -121,6 +121,19 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T } @(private) +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { + sockaddr: win.SOCKADDR_STORAGE_LH + sockaddrlen := c.int(size_of(sockaddr)) + if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR { + err = Listen_Error(win.WSAGetLastError()) + return + } + + ep = _sockaddr_to_endpoint(&sockaddr) + return +} + +@(private) _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { for { sockaddr: win.SOCKADDR_STORAGE_LH @@ -368,4 +381,4 @@ _sockaddr_to_endpoint :: proc(native_addr: ^win.SOCKADDR_STORAGE_LH) -> (ep: End panic("native_addr is neither IP4 or IP6 address") } return -}
\ No newline at end of file +} diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index 075a669de..d67eb31f3 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -27,6 +27,8 @@ Proc_Calling_Convention :: union { Node_State_Flag :: enum { Bounds_Check, No_Bounds_Check, + Type_Assert, + No_Type_Assert, } Node_State_Flags :: distinct bit_set[Node_State_Flag] @@ -67,6 +69,7 @@ File :: struct { fullpath: string, src: string, + tags: [dynamic]tokenizer.Token, docs: ^Comment_Group, pkg_decl: ^Package_Decl, diff --git a/core/odin/parser/file_tags.odin b/core/odin/parser/file_tags.odin new file mode 100644 index 000000000..84b172148 --- /dev/null +++ b/core/odin/parser/file_tags.odin @@ -0,0 +1,258 @@ +package odin_parser + +import "base:runtime" +import "core:strings" +import "core:reflect" + +import "../ast" + +Private_Flag :: enum { + Public, + Package, + File, +} + +Build_Kind :: struct { + os: runtime.Odin_OS_Types, + arch: runtime.Odin_Arch_Types, +} + +File_Tags :: struct { + build_project_name: [][]string, + build: []Build_Kind, + private: Private_Flag, + ignore: bool, + lazy: bool, + no_instrumentation: bool, +} + +@require_results +get_build_os_from_string :: proc(str: string) -> runtime.Odin_OS_Type { + fields := reflect.enum_fields_zipped(runtime.Odin_OS_Type) + for os in fields { + if strings.equal_fold(os.name, str) { + return runtime.Odin_OS_Type(os.value) + } + } + return .Unknown +} +@require_results +get_build_arch_from_string :: proc(str: string) -> runtime.Odin_Arch_Type { + fields := reflect.enum_fields_zipped(runtime.Odin_Arch_Type) + for os in fields { + if strings.equal_fold(os.name, str) { + return runtime.Odin_Arch_Type(os.value) + } + } + return .Unknown +} + +@require_results +parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags: File_Tags) { + context.allocator = allocator + + if file.docs == nil && file.tags == nil { + return + } + + next_char :: proc(src: string, i: ^int) -> (ch: u8) { + if i^ < len(src) { + ch = src[i^] + } + i^ += 1 + return + } + skip_whitespace :: proc(src: string, i: ^int) { + for { + switch next_char(src, i) { + case ' ', '\t': + continue + case: + i^ -= 1 + return + } + } + } + scan_value :: proc(src: string, i: ^int) -> string { + start := i^ + for { + switch next_char(src, i) { + case ' ', '\t', '\n', '\r', 0, ',': + i^ -= 1 + return src[start:i^] + case: + continue + } + } + } + + build_kinds: [dynamic]Build_Kind + defer shrink(&build_kinds) + + build_project_name_strings: [dynamic]string + defer shrink(&build_project_name_strings) + + build_project_names: [dynamic][]string + defer shrink(&build_project_names) + + parse_tag :: proc(text: string, tags: ^File_Tags, build_kinds: ^[dynamic]Build_Kind, + build_project_name_strings: ^[dynamic]string, + build_project_names: ^[dynamic][]string) { + i := 0 + + skip_whitespace(text, &i) + + if next_char(text, &i) == '+' { + switch scan_value(text, &i) { + case "ignore": + tags.ignore = true + case "lazy": + tags.lazy = true + case "no-instrumentation": + tags.no_instrumentation = true + case "private": + skip_whitespace(text, &i) + switch scan_value(text, &i) { + case "file": + tags.private = .File + case "package", "": + tags.private = .Package + } + case "build-project-name": + groups_loop: for { + index_start := len(build_project_name_strings) + + defer append(build_project_names, build_project_name_strings[index_start:]) + + for { + skip_whitespace(text, &i) + name_start := i + + switch next_char(text, &i) { + case 0, '\r', '\n': + i -= 1 + break groups_loop + case ',': + continue groups_loop + case '!': + // include ! in the name + case: + i -= 1 + } + + scan_value(text, &i) + append(build_project_name_strings, text[name_start:i]) + } + + append(build_project_names, build_project_name_strings[index_start:]) + } + case "build": + kinds_loop: for { + os_positive: runtime.Odin_OS_Types + os_negative: runtime.Odin_OS_Types + + arch_positive: runtime.Odin_Arch_Types + arch_negative: runtime.Odin_Arch_Types + + defer append(build_kinds, Build_Kind{ + os = (os_positive == {} ? runtime.ALL_ODIN_OS_TYPES : os_positive) -os_negative, + arch = (arch_positive == {} ? runtime.ALL_ODIN_ARCH_TYPES : arch_positive)-arch_negative, + }) + + for { + skip_whitespace(text, &i) + + is_notted: bool + switch next_char(text, &i) { + case 0, '\r', '\n': + i -= 1 + break kinds_loop + case ',': + continue kinds_loop + case '!': + is_notted = true + case: + i -= 1 + } + + value := scan_value(text, &i) + + if value == "ignore" { + tags.ignore = true + } else if os := get_build_os_from_string(value); os != .Unknown { + if is_notted { + os_negative += {os} + } else { + os_positive += {os} + } + } else if arch := get_build_arch_from_string(value); arch != .Unknown { + if is_notted { + arch_negative += {arch} + } else { + arch_positive += {arch} + } + } + } + } + } + } + } + + if file.docs != nil { + for comment in file.docs.list { + if len(comment.text) < 3 || comment.text[:2] != "//" { + continue + } + text := comment.text[2:] + + parse_tag(text, &tags, &build_kinds, &build_project_name_strings, &build_project_names) + } + } + + for tag in file.tags { + if len(tag.text) < 3 || tag.text[:2] != "#+" { + continue + } + // Only skip # because parse_tag skips the plus + text := tag.text[1:] + + parse_tag(text, &tags, &build_kinds, &build_project_name_strings, &build_project_names) + } + + tags.build = build_kinds[:] + tags.build_project_name = build_project_names[:] + + return +} + +Build_Target :: struct { + os: runtime.Odin_OS_Type, + arch: runtime.Odin_Arch_Type, + project_name: string, +} + +@require_results +match_build_tags :: proc(file_tags: File_Tags, target: Build_Target) -> bool { + + project_name_correct := len(target.project_name) == 0 || len(file_tags.build_project_name) == 0 + + for group in file_tags.build_project_name { + group_correct := true + for name in group { + if name[0] == '!' { + group_correct &&= target.project_name != name[1:] + } else { + group_correct &&= target.project_name == name + } + } + project_name_correct ||= group_correct + } + + os_and_arch_correct := len(file_tags.build) == 0 + + for kind in file_tags.build { + os_and_arch_correct ||= target.os in kind.os && target.arch in kind.arch + } + + return !file_tags.ignore && project_name_correct && os_and_arch_correct +} diff --git a/core/odin/parser/parse_files.odin b/core/odin/parser/parse_files.odin index 5f455c749..d4e532ec7 100644 --- a/core/odin/parser/parse_files.odin +++ b/core/odin/parser/parse_files.odin @@ -77,9 +77,7 @@ parse_package :: proc(pkg: ^ast.Package, p: ^Parser = nil) -> bool { if !parse_file(p, file) { ok = false } - if file.pkg_decl == nil { - error(p, p.curr_tok.pos, "Expected a package declaration at the start of the file") - } else if pkg.name == "" { + if pkg.name == "" { pkg.name = file.pkg_decl.name } else if pkg.name != file.pkg_decl.name { error(p, file.pkg_decl.pos, "different package name, expected '%s', got '%s'", pkg.name, file.pkg_decl.name) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 991b05398..d42766bde 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -161,11 +161,36 @@ parse_file :: proc(p: ^Parser, file: ^ast.File) -> bool { docs := p.lead_comment + invalid_pre_package_token: Maybe(tokenizer.Token) + + for p.curr_tok.kind != .Package && p.curr_tok.kind != .EOF { + if p.curr_tok.kind == .Comment { + consume_comment_groups(p, p.prev_tok) + } else if p.curr_tok.kind == .File_Tag { + append(&p.file.tags, p.curr_tok) + advance_token(p) + } else { + if invalid_pre_package_token == nil { + invalid_pre_package_token = p.curr_tok + } + + advance_token(p) + } + } + + if p.curr_tok.kind != .Package { + t := invalid_pre_package_token.? or_else p.curr_tok + error(p, t.pos, "Expected a package declaration at the start of the file") + return false + } + p.file.pkg_token = expect_token(p, .Package) - if p.file.pkg_token.kind != .Package { + + if ippt, ok := invalid_pre_package_token.?; ok { + error(p, ippt.pos, "Expected only comments or lines starting with '#+' before the package declaration") return false } - + pkg_name := expect_token_after(p, .Ident, "package") if pkg_name.kind == .Ident { switch name := pkg_name.text; { @@ -1438,6 +1463,15 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { stmt.state_flags += {.No_Bounds_Check} } return stmt + case "type_assert", "no_type_assert": + stmt := parse_stmt(p) + switch name { + case "type_assert": + stmt.state_flags += {.Type_Assert} + case "no_type_assert": + stmt.state_flags += {.No_Type_Assert} + } + return stmt case "partial": stmt := parse_stmt(p) #partial switch s in stmt.derived_stmt { @@ -2268,6 +2302,16 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { bd.name = name.text return bd + case "caller_expression": + bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) + bd.tok = tok + bd.name = name.text + + if peek_token_kind(p, .Open_Paren) { + return parse_call_expr(p, bd) + } + return bd + case "location", "exists", "load", "load_directory", "load_hash", "hash", "assert", "panic", "defined", "config": bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) bd.tok = tok diff --git a/core/odin/tokenizer/token.odin b/core/odin/tokenizer/token.odin index cd8953841..48d08f127 100644 --- a/core/odin/tokenizer/token.odin +++ b/core/odin/tokenizer/token.odin @@ -32,6 +32,7 @@ Token_Kind :: enum u32 { Invalid, EOF, Comment, + File_Tag, B_Literal_Begin, Ident, // main @@ -166,6 +167,7 @@ tokens := [Token_Kind.COUNT]string { "Invalid", "EOF", "Comment", + "FileTag", "", "identifier", diff --git a/core/odin/tokenizer/tokenizer.odin b/core/odin/tokenizer/tokenizer.odin index 62170aa10..c3a30581c 100644 --- a/core/odin/tokenizer/tokenizer.odin +++ b/core/odin/tokenizer/tokenizer.odin @@ -206,6 +206,23 @@ scan_comment :: proc(t: ^Tokenizer) -> string { return string(lit) } +scan_file_tag :: proc(t: ^Tokenizer) -> string { + offset := t.offset - 1 + + for t.ch != '\n' { + if t.ch == '/' { + next := peek_byte(t, 0) + + if next == '/' || next == '*' { + break + } + } + advance_rune(t) + } + + return string(t.src[offset : t.offset]) +} + scan_identifier :: proc(t: ^Tokenizer) -> string { offset := t.offset @@ -636,6 +653,9 @@ scan :: proc(t: ^Tokenizer) -> Token { if t.ch == '!' { kind = .Comment lit = scan_comment(t) + } else if t.ch == '+' { + kind = .File_Tag + lit = scan_file_tag(t) } case '/': kind = .Quo diff --git a/core/os/dir_unix.odin b/core/os/dir_unix.odin index b472e89b7..26e865204 100644 --- a/core/os/dir_unix.odin +++ b/core/os/dir_unix.odin @@ -1,4 +1,4 @@ -//+build darwin, linux, netbsd, freebsd, openbsd +#+build darwin, linux, netbsd, freebsd, openbsd package os import "core:strings" diff --git a/core/os/file_windows.odin b/core/os/file_windows.odin index 3f6f781aa..375da6aff 100644 --- a/core/os/file_windows.odin +++ b/core/os/file_windows.odin @@ -192,6 +192,8 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { case 0: w = win32.FILE_BEGIN case 1: w = win32.FILE_CURRENT case 2: w = win32.FILE_END + case: + return 0, .Invalid_Whence } hi := i32(offset>>32) lo := i32(offset) @@ -223,11 +225,13 @@ file_size :: proc(fd: Handle) -> (i64, Error) { MAX_RW :: 1<<30 @(private) -pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { +pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr_off := seek(fd, 0, 1) or_return + defer seek(fd, curr_off, 0) + buf := data if len(buf) > MAX_RW { buf = buf[:MAX_RW] - } o := win32.OVERLAPPED{ @@ -247,11 +251,13 @@ pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { return int(done), e } @(private) -pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { +pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr_off := seek(fd, 0, 1) or_return + defer seek(fd, curr_off, 0) + buf := data if len(buf) > MAX_RW { buf = buf[:MAX_RW] - } o := win32.OVERLAPPED{ @@ -271,13 +277,6 @@ pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { /* read_at returns n: 0, err: 0 on EOF -on Windows, read_at changes the position of the file cursor, on *nix, it does not. - - bytes: [8]u8{} - read_at(fd, bytes, 0) - read(fd, bytes) - -will read from the location twice on *nix, and from two different locations on Windows */ read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { if offset < 0 { @@ -302,15 +301,6 @@ read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { return } -/* -on Windows, write_at changes the position of the file cursor, on *nix, it does not. - - bytes: [8]u8{} - write_at(fd, bytes, 0) - write(fd, bytes) - -will write to the location twice on *nix, and to two different locations on Windows -*/ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { if offset < 0 { return 0, .Invalid_Offset diff --git a/core/os/os2/allocators.odin b/core/os/os2/allocators.odin index 0ab3adfb2..a205cae48 100644 --- a/core/os/os2/allocators.odin +++ b/core/os/os2/allocators.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" @@ -22,9 +22,14 @@ global_default_temp_allocator_index: uint @(require_results) temp_allocator :: proc() -> runtime.Allocator { + arena := &global_default_temp_allocator_arenas[global_default_temp_allocator_index] + if arena.backing_allocator.procedure == nil { + arena.backing_allocator = heap_allocator() + } + return runtime.Allocator{ procedure = temp_allocator_proc, - data = &global_default_temp_allocator_arenas[global_default_temp_allocator_index], + data = arena, } } @@ -61,3 +66,8 @@ TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime. global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT return tmp, loc } + +@(init, private) +init_thread_local_cleaner :: proc() { + runtime.add_thread_local_cleaner(temp_allocator_fini) +} diff --git a/core/os/os2/dir_linux.odin b/core/os/os2/dir_linux.odin index d4f62e213..6a097e192 100644 --- a/core/os/os2/dir_linux.odin +++ b/core/os/os2/dir_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 Read_Directory_Iterator_Impl :: struct { diff --git a/core/os/os2/dir_posix.odin b/core/os/os2/dir_posix.odin index 75f620d90..14fddde50 100644 --- a/core/os/os2/dir_posix.odin +++ b/core/os/os2/dir_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "core:sys/posix" diff --git a/core/os/os2/dir_windows.odin b/core/os/os2/dir_windows.odin index 1b9675064..09990aeec 100644 --- a/core/os/os2/dir_windows.odin +++ b/core/os/os2/dir_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/env_linux.odin b/core/os/os2/env_linux.odin index 99dd00d90..c248a323c 100644 --- a/core/os/os2/env_linux.odin +++ b/core/os/os2/env_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/env_posix.odin b/core/os/os2/env_posix.odin index 93524fb0c..e2080485d 100644 --- a/core/os/os2/env_posix.odin +++ b/core/os/os2/env_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "base:runtime" diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index ac30eb1d4..a1e8c969d 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import win32 "core:sys/windows" diff --git a/core/os/os2/errors_linux.odin b/core/os/os2/errors_linux.odin index 29815bf79..09492110d 100644 --- a/core/os/os2/errors_linux.odin +++ b/core/os/os2/errors_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "core:sys/linux" @@ -154,6 +154,14 @@ _get_platform_error :: proc(errno: linux.Errno) -> Error { return .Exist case .ENOENT: return .Not_Exist + case .ETIMEDOUT: + return .Timeout + case .EPIPE: + return .Broken_Pipe + case .EBADF: + return .Invalid_File + case .ENOMEM: + return .Out_Of_Memory } return Platform_Error(i32(errno)) diff --git a/core/os/os2/errors_posix.odin b/core/os/os2/errors_posix.odin index 9e3424a4a..59f0ba5f1 100644 --- a/core/os/os2/errors_posix.odin +++ b/core/os/os2/errors_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "core:sys/posix" diff --git a/core/os/os2/errors_windows.odin b/core/os/os2/errors_windows.odin index 6748c1167..56acd503f 100644 --- a/core/os/os2/errors_windows.odin +++ b/core/os/os2/errors_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" @@ -52,6 +52,9 @@ _get_platform_error :: proc() -> Error { case win32.ERROR_INVALID_HANDLE: return .Invalid_File + case win32.ERROR_NEGATIVE_SEEK: + return .Invalid_Offset + case win32.ERROR_BAD_ARGUMENTS, win32.ERROR_INVALID_PARAMETER, diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index f1c5b1fad..eedf8570c 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -132,6 +132,12 @@ name :: proc(f: ^File) -> string { return _name(f) } +/* + Close a file and its stream. + + Any further use of the file or its stream should be considered to be in the + same class of bugs as a use-after-free. +*/ close :: proc(f: ^File) -> Error { if f != nil { return io.close(f.stream) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 64683cf1e..e9ce13447 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" @@ -170,11 +170,23 @@ _name :: proc(f: ^File) -> string { } _seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { + // We have to handle this here, because Linux returns EINVAL for both + // invalid offsets and invalid whences. + switch whence { + case .Start, .Current, .End: + break + case: + return 0, .Invalid_Whence + } n, errno := linux.lseek(f.fd, offset, linux.Seek_Whence(whence)) - if errno != .NONE { + #partial switch errno { + case .EINVAL: + return 0, .Invalid_Offset + case .NONE: + return n, nil + case: return -1, _get_platform_error(errno) } - return n, nil } _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { @@ -189,6 +201,9 @@ _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { } _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { + if len(p) == 0 { + return 0, nil + } if offset < 0 { return 0, .Invalid_Offset } @@ -214,6 +229,9 @@ _write :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { } _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { + if len(p) == 0 { + return 0, nil + } if offset < 0 { return 0, .Invalid_Offset } diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin index 96b7ffe4e..b7dc43287 100644 --- a/core/os/os2/file_posix.odin +++ b/core/os/os2/file_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "base:runtime" @@ -419,9 +419,22 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, #assert(int(posix.Whence.CUR) == int(io.Seek_From.Current)) #assert(int(posix.Whence.END) == int(io.Seek_From.End)) + switch whence { + case .Start, .Current, .End: + break + case: + err = .Invalid_Whence + return + } + n = i64(posix.lseek(fd, posix.off_t(offset), posix.Whence(whence))) if n < 0 { - err = .Unknown + #partial switch posix.get_errno() { + case .EINVAL: + err = .Invalid_Offset + case: + err = .Unknown + } } return @@ -446,7 +459,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, return case .Query: - return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Query}) + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query}) case: return 0, .Empty diff --git a/core/os/os2/file_posix_darwin.odin b/core/os/os2/file_posix_darwin.odin index 056d775e6..920a63a71 100644 --- a/core/os/os2/file_posix_darwin.odin +++ b/core/os/os2/file_posix_darwin.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/file_posix_freebsd.odin b/core/os/os2/file_posix_freebsd.odin index e5007f8fe..05d031930 100644 --- a/core/os/os2/file_posix_freebsd.odin +++ b/core/os/os2/file_posix_freebsd.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/file_posix_netbsd.odin b/core/os/os2/file_posix_netbsd.odin index a9cc77a41..f96c227ba 100644 --- a/core/os/os2/file_posix_netbsd.odin +++ b/core/os/os2/file_posix_netbsd.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/file_posix_other.odin b/core/os/os2/file_posix_other.odin index 929622578..74b6374ec 100644 --- a/core/os/os2/file_posix_other.odin +++ b/core/os/os2/file_posix_other.odin @@ -1,5 +1,5 @@ -//+private -//+build openbsd +#+private +#+build openbsd package os2 import "base:runtime" diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin index e328f9a02..963544985 100644 --- a/core/os/os2/file_util.odin +++ b/core/os/os2/file_util.odin @@ -125,9 +125,6 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d has_size = true size = int(size64) } - } else if serr != .No_Size { - err = serr - return } if has_size && size > 0 { diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 74067464b..511935d74 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" @@ -248,6 +248,8 @@ _seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, er case .Start: w = win32.FILE_BEGIN case .Current: w = win32.FILE_CURRENT case .End: w = win32.FILE_END + case: + return 0, .Invalid_Whence } hi := i32(offset>>32) lo := i32(offset) @@ -264,6 +266,11 @@ _read :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { } _read_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { + length := len(p) + if length == 0 { + return + } + read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { if len(b) == 0 { return 0, nil @@ -318,7 +325,6 @@ _read_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { single_read_length: win32.DWORD total_read: int - length := len(p) sync.shared_guard(&f.rw_mutex) // multiple readers @@ -337,6 +343,10 @@ _read_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { if single_read_length > 0 && ok { total_read += int(single_read_length) + } else if single_read_length == 0 && ok { + // ok and 0 bytes means EOF: + // https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file + err = .EOF } else { err = _get_platform_error() } @@ -352,7 +362,7 @@ _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) buf = buf[:MAX_RW] } - curr_offset := _seek(f, offset, .Current) or_return + curr_offset := _seek(f, 0, .Current) or_return defer _seek(f, curr_offset, .Start) o := win32.OVERLAPPED{ @@ -421,7 +431,7 @@ _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) buf = buf[:MAX_RW] } - curr_offset := _seek(f, offset, .Current) or_return + curr_offset := _seek(f, 0, .Current) or_return defer _seek(f, curr_offset, .Start) o := win32.OVERLAPPED{ @@ -466,13 +476,13 @@ _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { _sync :: proc(f: ^File) -> Error { if f != nil && f.impl != nil { - return _flush((^File_Impl)(f.impl)) + return _flush_internal((^File_Impl)(f.impl)) } return nil } _flush :: proc(f: ^File_Impl) -> Error { - return _flush(f) + return _flush_internal(f) } _flush_internal :: proc(f: ^File_Impl) -> Error { handle := _handle(&f.file) @@ -813,7 +823,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, err = error_to_io_error(ferr) return case .Query: - return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Query}) + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query}) } return 0, .Empty } diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin index 11cf5ab41..ede5eb2ac 100644 --- a/core/os/os2/heap_linux.odin +++ b/core/os/os2/heap_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "core:sys/linux" @@ -280,7 +280,8 @@ heap_alloc :: proc(size: int) -> rawptr { _local_region, back_idx = _region_retrieve_with_space(blocks_needed, local_region_idx, back_idx) } user_ptr, used := _region_get_block(_local_region, idx, blocks_needed) - _local_region.hdr.free_blocks -= (used + 1) + + sync.atomic_sub_explicit(&_local_region.hdr.free_blocks, used + 1, .Release) // If this memory was ever used before, it now needs to be zero'd. if idx < _local_region.hdr.last_used { @@ -307,7 +308,7 @@ heap_resize :: proc(old_memory: rawptr, new_size: int) -> rawptr #no_bounds_chec heap_free :: proc(memory: rawptr) { alloc := _get_allocation_header(memory) - if alloc.requested & IS_DIRECT_MMAP == IS_DIRECT_MMAP { + if sync.atomic_load(&alloc.requested) & IS_DIRECT_MMAP == IS_DIRECT_MMAP { _direct_mmap_free(alloc) return } @@ -462,25 +463,31 @@ _region_local_free :: proc(alloc: ^Allocation_Header) #no_bounds_check { alloc := alloc add_to_free_list := true - _local_region.hdr.free_blocks += _get_block_count(alloc^) + 1 + idx := sync.atomic_load(&alloc.idx) + prev := sync.atomic_load(&alloc.prev) + next := sync.atomic_load(&alloc.next) + block_count := next - idx - 1 + free_blocks := sync.atomic_load(&_local_region.hdr.free_blocks) + block_count + 1 + sync.atomic_store_explicit(&_local_region.hdr.free_blocks, free_blocks, .Release) // try to merge with prev - if alloc.idx > 0 && _local_region.memory[alloc.prev].free_idx != NOT_FREE { - _local_region.memory[alloc.prev].next = alloc.next - _local_region.memory[alloc.next].prev = alloc.prev - alloc = &_local_region.memory[alloc.prev] + if idx > 0 && sync.atomic_load(&_local_region.memory[prev].free_idx) != NOT_FREE { + sync.atomic_store_explicit(&_local_region.memory[prev].next, next, .Release) + _local_region.memory[next].prev = prev + alloc = &_local_region.memory[prev] add_to_free_list = false } // try to merge with next - if alloc.next < BLOCKS_PER_REGION - 1 && _local_region.memory[alloc.next].free_idx != NOT_FREE { - old_next := alloc.next - alloc.next = _local_region.memory[old_next].next - _local_region.memory[alloc.next].prev = alloc.idx + if next < BLOCKS_PER_REGION - 1 && sync.atomic_load(&_local_region.memory[next].free_idx) != NOT_FREE { + old_next := next + sync.atomic_store_explicit(&alloc.next, sync.atomic_load(&_local_region.memory[old_next].next), .Release) + + sync.atomic_store_explicit(&_local_region.memory[next].prev, idx, .Release) if add_to_free_list { - _local_region.hdr.free_list[_local_region.memory[old_next].free_idx] = alloc.idx - alloc.free_idx = _local_region.memory[old_next].free_idx + sync.atomic_store_explicit(&_local_region.hdr.free_list[_local_region.memory[old_next].free_idx], idx, .Release) + sync.atomic_store_explicit(&alloc.free_idx, _local_region.memory[old_next].free_idx, .Release) } else { // NOTE: We have aleady merged with prev, and now merged with next. // Now, we are actually going to remove from the free_list. @@ -492,10 +499,11 @@ _region_local_free :: proc(alloc: ^Allocation_Header) #no_bounds_check { // This is the only place where anything is appended to the free list. if add_to_free_list { fl := _local_region.hdr.free_list - alloc.free_idx = _local_region.hdr.free_list_len - fl[alloc.free_idx] = alloc.idx - _local_region.hdr.free_list_len += 1 - if int(_local_region.hdr.free_list_len) == len(fl) { + fl_len := sync.atomic_load(&_local_region.hdr.free_list_len) + sync.atomic_store_explicit(&alloc.free_idx, fl_len, .Release) + fl[alloc.free_idx] = idx + sync.atomic_store_explicit(&_local_region.hdr.free_list_len, fl_len + 1, .Release) + if int(fl_len + 1) == len(fl) { free_alloc := _get_allocation_header(mem.raw_data(_local_region.hdr.free_list)) _region_resize(free_alloc, len(fl) * 2 * size_of(fl[0]), true) } @@ -512,8 +520,8 @@ _region_assign_free_list :: proc(region: ^Region, memory: rawptr, blocks: u16) { _region_retrieve_with_space :: proc(blocks: u16, local_idx: int = -1, back_idx: int = -1) -> (^Region, int) { r: ^Region idx: int - for r = global_regions; r != nil; r = r.hdr.next_region { - if idx == local_idx || idx < back_idx || r.hdr.free_blocks < blocks { + for r = sync.atomic_load(&global_regions); r != nil; r = r.hdr.next_region { + if idx == local_idx || idx < back_idx || sync.atomic_load(&r.hdr.free_blocks) < blocks { idx += 1 continue } @@ -581,7 +589,7 @@ _region_segment :: proc(region: ^Region, alloc: ^Allocation_Header, blocks, new_ _region_get_local_idx :: proc() -> int { idx: int - for r := global_regions; r != nil; r = r.hdr.next_region { + for r := sync.atomic_load(&global_regions); r != nil; r = r.hdr.next_region { if r == _local_region { return idx } @@ -597,9 +605,10 @@ _region_find_and_assign_local :: proc(alloc: ^Allocation_Header) { _local_region = _region_retrieve_from_addr(alloc) } - // At this point, _local_region is set correctly. Spin until acquired - res: ^^Region - for res != &_local_region { + // At this point, _local_region is set correctly. Spin until acquire + res := CURRENTLY_ACTIVE + + for res == CURRENTLY_ACTIVE { res = sync.atomic_compare_exchange_strong_explicit( &_local_region.hdr.local_addr, &_local_region, @@ -621,9 +630,9 @@ _region_contains_mem :: proc(r: ^Region, memory: rawptr) -> bool #no_bounds_chec _region_free_list_remove :: proc(region: ^Region, free_idx: u16) #no_bounds_check { // pop, swap and update allocation hdr if n := region.hdr.free_list_len - 1; free_idx != n { - region.hdr.free_list[free_idx] = region.hdr.free_list[n] + region.hdr.free_list[free_idx] = sync.atomic_load(®ion.hdr.free_list[n]) alloc_idx := region.hdr.free_list[free_idx] - region.memory[alloc_idx].free_idx = free_idx + sync.atomic_store_explicit(®ion.memory[alloc_idx].free_idx, free_idx, .Release) } region.hdr.free_list_len -= 1 } @@ -714,3 +723,4 @@ _get_allocation_header :: #force_inline proc(raw_mem: rawptr) -> ^Allocation_Hea _round_up_to_nearest :: #force_inline proc(size, round: int) -> int { return (size-1) + round - (size-1) % round } + diff --git a/core/os/os2/heap_posix.odin b/core/os/os2/heap_posix.odin index fcae267fa..1b52aed75 100644 --- a/core/os/os2/heap_posix.odin +++ b/core/os/os2/heap_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "base:runtime" diff --git a/core/os/os2/heap_windows.odin b/core/os/os2/heap_windows.odin index 4afc016a0..7fd4529a0 100644 --- a/core/os/os2/heap_windows.odin +++ b/core/os/os2/heap_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "core:mem" diff --git a/core/os/os2/internal_util.odin b/core/os/os2/internal_util.odin index 041cd531b..164e1e1be 100644 --- a/core/os/os2/internal_util.odin +++ b/core/os/os2/internal_util.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:intrinsics" diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index ba2f7235c..7be4121ae 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "core:strings" diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin index 39ada264e..6f358c58d 100644 --- a/core/os/os2/path_posix.odin +++ b/core/os/os2/path_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "base:runtime" @@ -39,12 +39,12 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { return internal_mkdir_all(clean_path, perm) internal_mkdir_all :: proc(path: string, perm: int) -> Error { - a, _ := filepath.split(path) - if a != path { - if len(a) > 1 && a[len(a)-1] == '/' { - a = a[:len(a)-1] + dir, file := filepath.split(path) + if file != path { + if len(dir) > 1 && dir[len(dir) - 1] == '/' { + dir = dir[:len(dir) - 1] } - internal_mkdir_all(a, perm) or_return + internal_mkdir_all(dir, perm) or_return } err := _mkdir(path, perm) diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index 4aa695ee2..3e92cb6f3 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import win32 "core:sys/windows" diff --git a/core/os/os2/pipe_linux.odin b/core/os/os2/pipe_linux.odin index 42315cf4e..ac3382bc3 100644 --- a/core/os/os2/pipe_linux.odin +++ b/core/os/os2/pipe_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "core:sys/linux" diff --git a/core/os/os2/pipe_posix.odin b/core/os/os2/pipe_posix.odin index 13c1f8aec..487e32aea 100644 --- a/core/os/os2/pipe_posix.odin +++ b/core/os/os2/pipe_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "core:sys/posix" diff --git a/core/os/os2/pipe_windows.odin b/core/os/os2/pipe_windows.odin index 59615e306..ee93fb683 100644 --- a/core/os/os2/pipe_windows.odin +++ b/core/os/os2/pipe_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import win32 "core:sys/windows" diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index f7a542276..ce65987b0 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -166,15 +166,15 @@ Process_Info :: struct { This procedure obtains an information, specified by `selection` parameter of a process given by `pid`. - - Use `free_process_info` to free the memory allocated by this procedure. In - case the function returns an error it may only have been an error for one part - of the information and you would still need to call it to free the other parts. + + Use `free_process_info` to free the memory allocated by this procedure. The + `free_process_info` procedure needs to be called, even if this procedure + returned an error, as some of the fields may have been allocated. **Note**: The resulting information may or may contain the fields specified by the `selection` parameter. Always check whether the returned `Process_Info` struct has the required fields before checking the error code - returned by this function. + returned by this procedure. */ @(require_results) process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { @@ -188,14 +188,14 @@ process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: about a process that has been opened by the application, specified in the `process` parameter. - Use `free_process_info` to free the memory allocated by this procedure. In - case the function returns an error it may only have been an error for one part - of the information and you would still need to call it to free the other parts. + Use `free_process_info` to free the memory allocated by this procedure. The + `free_process_info` procedure needs to be called, even if this procedure + returned an error, as some of the fields may have been allocated. **Note**: The resulting information may or may contain the fields specified by the `selection` parameter. Always check whether the returned `Process_Info` struct has the required fields before checking the error code - returned by this function. + returned by this procedure. */ @(require_results) process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { @@ -208,14 +208,14 @@ process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, This procedure obtains the information, specified by `selection` parameter about the currently running process. - Use `free_process_info` to free the memory allocated by this procedure. In - case the function returns an error it may only have been an error for one part - of the information and you would still need to call it to free the other parts. + Use `free_process_info` to free the memory allocated by this procedure. The + `free_process_info` procedure needs to be called, even if this procedure + returned an error, as some of the fields may have been allocated. **Note**: The resulting information may or may contain the fields specified by the `selection` parameter. Always check whether the returned `Process_Info` struct has the required fields before checking the error code - returned by this function. + returned by this procedure. */ @(require_results) current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { @@ -305,6 +305,7 @@ Process_Desc :: struct { // A slice of strings, each having the format `KEY=VALUE` representing the // full environment that the child process will receive. // In case this slice is `nil`, the current process' environment is used. + // NOTE(laytan): maybe should be `Maybe([]string)` so you can do `nil` == current env, empty == empty/no env. env: []string, // The `stderr` handle to give to the child process. It can be either a file // or a writeable end of a pipe. Passing `nil` will shut down the process' diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin index 40406bad5..ea5ee41b1 100644 --- a/core/os/os2/process_linux.odin +++ b/core/os/os2/process_linux.odin @@ -1,5 +1,5 @@ -//+build linux -//+private file +#+build linux +#+private file package os2 import "base:runtime" @@ -490,7 +490,6 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { if errno = linux.pipe2(&child_pipe_fds, {.CLOEXEC}); errno != .NONE { return process, _get_platform_error(errno) } - defer linux.close(child_pipe_fds[WRITE]) defer linux.close(child_pipe_fds[READ]) @@ -508,6 +507,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { // pid: linux.Pid if pid, errno = linux.fork(); errno != .NONE { + linux.close(child_pipe_fds[WRITE]) return process, _get_platform_error(errno) } @@ -573,25 +573,19 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) } - success_byte: [1]u8 - linux.write(child_pipe_fds[WRITE], success_byte[:]) - errno = linux.execveat(exe_fd, "", &cargs[0], env, {.AT_EMPTY_PATH}) - - // NOTE: we can't tell the parent about this failure because we already wrote the success byte. - // So if this happens the user will just see the process failed when they call process_wait. - assert(errno != nil) - intrinsics.trap() + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) } + linux.close(child_pipe_fds[WRITE]) + process.pid = int(pid) - n: int child_byte: [1]u8 errno = .EINTR for errno == .EINTR { - n, errno = linux.read(child_pipe_fds[READ], child_byte[:]) + _, errno = linux.read(child_pipe_fds[READ], child_byte[:]) } // If the read failed, something weird happened. Do not return the read diff --git a/core/os/os2/process_posix.odin b/core/os/os2/process_posix.odin index c9b67f199..5ac6babc1 100644 --- a/core/os/os2/process_posix.odin +++ b/core/os/os2/process_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "base:runtime" @@ -139,20 +139,22 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { err = _get_platform_error() return } - defer posix.close(pipe[WRITE]) defer posix.close(pipe[READ]) if posix.fcntl(pipe[READ], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + posix.close(pipe[WRITE]) err = _get_platform_error() return } if posix.fcntl(pipe[WRITE], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + posix.close(pipe[WRITE]) err = _get_platform_error() return } switch pid := posix.fork(); pid { case -1: + posix.close(pipe[WRITE]) err = _get_platform_error() return @@ -179,25 +181,20 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { if posix.chdir(cwd) != .OK { abort(pipe[WRITE]) } } - ok := u8(0) - posix.write(pipe[WRITE], &ok, 1) - res := posix.execve(strings.to_cstring(&exe_builder), raw_data(cmd), env) - - // NOTE: we can't tell the parent about this failure because we already wrote the success byte. - // So if this happens the user will just see the process failed when they call process_wait. - assert(res == -1) - runtime.trap() + abort(pipe[WRITE]) case: + posix.close(pipe[WRITE]) + errno: posix.Errno for { errno_byte: u8 switch posix.read(pipe[READ], &errno_byte, 1) { - case 1: + case 1: errno = posix.Errno(errno_byte) - case: + case -1: errno = posix.errno() if errno == .EINTR { continue diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin index 648c4d389..0ea1f643c 100644 --- a/core/os/os2/process_posix_darwin.odin +++ b/core/os/os2/process_posix_darwin.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/process_posix_other.odin b/core/os/os2/process_posix_other.odin index 02f78b9ac..77dbfa7eb 100644 --- a/core/os/os2/process_posix_other.odin +++ b/core/os/os2/process_posix_other.odin @@ -1,5 +1,5 @@ -//+private -//+build netbsd, openbsd, freebsd +#+private +#+build netbsd, openbsd, freebsd package os2 import "base:runtime" diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 47fd62401..0c32373f3 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -1,4 +1,4 @@ -//+private file +#+private file package os2 import "base:runtime" @@ -93,16 +93,33 @@ read_memory_as_slice :: proc(h: win32.HANDLE, addr: rawptr, dest: []$T) -> (byte @(private="package") _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { info.pid = pid - defer if err != nil { - free_process_info(info, allocator) + // Note(flysand): Open the process handle right away to prevent some race + // conditions. Once the handle is open, the process will be kept alive by + // the OS. + ph := win32.INVALID_HANDLE_VALUE + if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} { + ph = win32.OpenProcess( + win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ, + false, + u32(pid), + ) + if ph == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + return + } } - - // Data obtained from process snapshots - if selection >= {.PPid, .Priority} { + defer if ph != win32.INVALID_HANDLE_VALUE { + win32.CloseHandle(ph) + } + snapshot_process: if selection >= {.PPid, .Priority} { entry, entry_err := _process_entry_by_pid(info.pid) if entry_err != nil { - err = General_Error.Not_Exist - return + err = entry_err + if entry_err == General_Error.Not_Exist { + return + } else { + break snapshot_process + } } if .PPid in selection { info.fields += {.PPid} @@ -113,29 +130,18 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator info.priority = int(entry.pcPriClassBase) } } - if .Executable_Path in selection { // snap module - info.executable_path = _process_exe_by_pid(pid, allocator) or_return - info.fields += {.Executable_Path} - } - - ph := win32.INVALID_HANDLE_VALUE - - if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} { // need process handle - ph = win32.OpenProcess( - win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ, - false, - u32(pid), - ) - if ph == win32.INVALID_HANDLE_VALUE { - err = _get_platform_error() + snapshot_modules: if .Executable_Path in selection { + exe_path: string + exe_path, err = _process_exe_by_pid(pid, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { return + } else if err != nil { + break snapshot_modules } + info.executable_path = exe_path + info.fields += {.Executable_Path} } - defer if ph != win32.INVALID_HANDLE_VALUE { - win32.CloseHandle(ph) - } - - if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb + read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} { process_info_size: u32 process_info: win32.PROCESS_BASIC_INFORMATION status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) @@ -143,25 +149,26 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator // TODO(flysand): There's probably a mismatch between NTSTATUS and // windows userland error codes, I haven't checked. err = Platform_Error(status) - return - } - if process_info.PebBaseAddress == nil { - // Not sure what the error is - err = General_Error.Unsupported - return + break read_peb } + assert(process_info.PebBaseAddress != nil) process_peb: win32.PEB - - _ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return - + _, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) + if err != nil { + break read_peb + } process_params: win32.RTL_USER_PROCESS_PARAMETERS - _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return - + _, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) + if err != nil { + break read_peb + } if selection >= {.Command_Line, .Command_Args} { TEMP_ALLOCATOR_GUARD() cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return - _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return - + _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) + if err != nil { + break read_peb + } if .Command_Line in selection { info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return info.fields += {.Command_Line} @@ -175,23 +182,33 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator TEMP_ALLOCATOR_GUARD() env_len := process_params.EnvironmentSize / 2 envs_w := make([]u16, env_len, temp_allocator()) or_return - _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return - + _, err = read_memory_as_slice(ph, process_params.Environment, envs_w) + if err != nil { + break read_peb + } info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return info.fields += {.Environment} } if .Working_Dir in selection { TEMP_ALLOCATOR_GUARD() cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return - _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return - + _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) + if err != nil { + break read_peb + } info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return info.fields += {.Working_Dir} } } - - if .Username in selection { - info.username = _get_process_user(ph, allocator) or_return + read_username: if .Username in selection { + username: string + username, err = _get_process_user(ph, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break read_username + } + info.username = username info.fields += {.Username} } err = nil @@ -202,16 +219,16 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { pid := process.pid info.pid = pid - defer if err != nil { - free_process_info(info, allocator) - } - // Data obtained from process snapshots - if selection >= {.PPid, .Priority} { // snap process + snapshot_process: if selection >= {.PPid, .Priority} { entry, entry_err := _process_entry_by_pid(info.pid) if entry_err != nil { - err = General_Error.Not_Exist - return + err = entry_err + if entry_err == General_Error.Not_Exist { + return + } else { + break snapshot_process + } } if .PPid in selection { info.fields += {.PPid} @@ -222,12 +239,19 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields info.priority = int(entry.pcPriClassBase) } } - if .Executable_Path in selection { // snap module - info.executable_path = _process_exe_by_pid(pid, allocator) or_return + snapshot_module: if .Executable_Path in selection { + exe_path: string + exe_path, err = _process_exe_by_pid(pid, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break snapshot_module + } + info.executable_path = exe_path info.fields += {.Executable_Path} } ph := win32.HANDLE(process.handle) - if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb + read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} { process_info_size: u32 process_info: win32.PROCESS_BASIC_INFORMATION status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) @@ -237,23 +261,24 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields err = Platform_Error(status) return } - if process_info.PebBaseAddress == nil { - // Not sure what the error is - err = General_Error.Unsupported - return - } - + assert(process_info.PebBaseAddress != nil) process_peb: win32.PEB - _ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return - + _, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) + if err != nil { + break read_peb + } process_params: win32.RTL_USER_PROCESS_PARAMETERS - _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return - + _, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) + if err != nil { + break read_peb + } if selection >= {.Command_Line, .Command_Args} { TEMP_ALLOCATOR_GUARD() cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return - _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return - + _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) + if err != nil { + break read_peb + } if .Command_Line in selection { info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return info.fields += {.Command_Line} @@ -263,28 +288,37 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields info.fields += {.Command_Args} } } - if .Environment in selection { TEMP_ALLOCATOR_GUARD() env_len := process_params.EnvironmentSize / 2 envs_w := make([]u16, env_len, temp_allocator()) or_return - _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return - + _, err = read_memory_as_slice(ph, process_params.Environment, envs_w) + if err != nil { + break read_peb + } info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return info.fields += {.Environment} } - if .Working_Dir in selection { TEMP_ALLOCATOR_GUARD() cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return - _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return - + _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) + if err != nil { + break read_peb + } info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return info.fields += {.Working_Dir} } } - if .Username in selection { - info.username = _get_process_user(ph, allocator) or_return + read_username: if .Username in selection { + username: string + username, err = _get_process_user(ph, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break read_username + } + info.username = username info.fields += {.Username} } err = nil @@ -294,15 +328,15 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields @(private="package") _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { info.pid = get_pid() - defer if err != nil { - free_process_info(info, allocator) - } - - if selection >= {.PPid, .Priority} { // snap process + snapshot_process: if selection >= {.PPid, .Priority} { entry, entry_err := _process_entry_by_pid(info.pid) if entry_err != nil { - err = General_Error.Not_Exist - return + err = entry_err + if entry_err == General_Error.Not_Exist { + return + } else { + break snapshot_process + } } if .PPid in selection { info.fields += {.PPid} @@ -313,14 +347,16 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime info.priority = int(entry.pcPriClassBase) } } - if .Executable_Path in selection { + module_filename: if .Executable_Path in selection { exe_filename_w: [256]u16 path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w)) + assert(path_len > 0) info.executable_path = win32_utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return info.fields += {.Executable_Path} } - if selection >= {.Command_Line, .Command_Args} { + command_line: if selection >= {.Command_Line, .Command_Args} { command_line_w := win32.GetCommandLineW() + assert(command_line_w != nil) if .Command_Line in selection { info.command_line = win32_wstring_to_utf8(command_line_w, allocator) or_return info.fields += {.Command_Line} @@ -330,14 +366,22 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime info.fields += {.Command_Args} } } - if .Environment in selection { + read_environment: if .Environment in selection { env_block := win32.GetEnvironmentStringsW() + assert(env_block != nil) info.environment = _parse_environment_block(env_block, allocator) or_return info.fields += {.Environment} } - if .Username in selection { + read_username: if .Username in selection { process_handle := win32.GetCurrentProcess() - info.username = _get_process_user(process_handle, allocator) or_return + username: string + username, err = _get_process_user(process_handle, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break read_username + } + info.username = username info.fields += {.Username} } if .Working_Dir in selection { diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index 6ccac1be0..0433c1a61 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "core:time" diff --git a/core/os/os2/stat_posix.odin b/core/os/os2/stat_posix.odin index a817a862b..88029c1f5 100644 --- a/core/os/os2/stat_posix.odin +++ b/core/os/os2/stat_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "base:runtime" diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 566417c84..8ed2a6fed 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/temp_file_linux.odin b/core/os/os2/temp_file_linux.odin index d6f90fbaf..4eacbc54a 100644 --- a/core/os/os2/temp_file_linux.odin +++ b/core/os/os2/temp_file_linux.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/temp_file_posix.odin b/core/os/os2/temp_file_posix.odin index 67ec4d3e8..b44ea13a7 100644 --- a/core/os/os2/temp_file_posix.odin +++ b/core/os/os2/temp_file_posix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, netbsd, freebsd, openbsd +#+private +#+build darwin, netbsd, freebsd, openbsd package os2 import "base:runtime" diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index d888eda52..3e3e1285c 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "base:runtime" diff --git a/core/os/os2/user.odin b/core/os/os2/user.odin index af0bc5da4..a0a7a839d 100644 --- a/core/os/os2/user.odin +++ b/core/os/os2/user.odin @@ -4,21 +4,23 @@ import "base:runtime" @(require_results) user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + #partial switch ODIN_OS { case .Windows: - dir = get_env("LocalAppData", allocator) + dir = get_env("LocalAppData", temp_allocator()) if dir != "" { dir = clone_string(dir, allocator) or_return } case .Darwin: - dir = get_env("HOME", allocator) + dir = get_env("HOME", temp_allocator()) if dir != "" { dir = concatenate({dir, "/Library/Caches"}, allocator) or_return } case: // All other UNIX systems dir = get_env("XDG_CACHE_HOME", allocator) if dir == "" { - dir = get_env("HOME", allocator) + dir = get_env("HOME", temp_allocator()) if dir == "" { return } @@ -33,21 +35,23 @@ user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error @(require_results) user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + #partial switch ODIN_OS { case .Windows: - dir = get_env("AppData", allocator) + dir = get_env("AppData", temp_allocator()) if dir != "" { dir = clone_string(dir, allocator) or_return } case .Darwin: - dir = get_env("HOME", allocator) + dir = get_env("HOME", temp_allocator()) if dir != "" { dir = concatenate({dir, "/.config"}, allocator) or_return } case: // All other UNIX systems dir = get_env("XDG_CACHE_HOME", allocator) if dir == "" { - dir = get_env("HOME", allocator) + dir = get_env("HOME", temp_allocator()) if dir == "" { return } diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index f694dd588..371485a47 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -206,7 +206,7 @@ ENOPROTOOPT :: _Platform_Error.ENOPROTOOPT EPROTONOSUPPORT :: _Platform_Error.EPROTONOSUPPORT ESOCKTNOSUPPORT :: _Platform_Error.ESOCKTNOSUPPORT ENOTSUP :: _Platform_Error.ENOTSUP -EOPNOTSUPP :: _Platform_Error.EOPNOTSUPP +EOPNOTSUPP :: _Platform_Error.EOPNOTSUPP EPFNOSUPPORT :: _Platform_Error.EPFNOSUPPORT EAFNOSUPPORT :: _Platform_Error.EAFNOSUPPORT EADDRINUSE :: _Platform_Error.EADDRINUSE @@ -812,10 +812,21 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { assert(fd != -1) + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } final_offset := i64(_unix_lseek(fd, int(offset), c.int(whence))) if final_offset == -1 { - return 0, get_last_error() + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno } return final_offset, nil } @@ -1119,7 +1130,8 @@ unset_env :: proc(key: string) -> Error { } @(require_results) -get_current_directory :: proc() -> string { +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator page_size := get_page_size() // NOTE(tetra): See note in os_linux.odin/get_current_directory. buf := make([dynamic]u8, page_size) for { diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index 41c487b2b..f617cf973 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -6,6 +6,7 @@ foreign import libc "system:c" import "base:runtime" import "core:strings" import "core:c" +import "core:sys/freebsd" Handle :: distinct i32 File_Time :: distinct u64 @@ -446,8 +447,7 @@ close :: proc(fd: Handle) -> Error { } flush :: proc(fd: Handle) -> Error { - // do nothing - return nil + return cast(_Platform_Error)freebsd.fsync(cast(freebsd.Fd)fd) } // If you read or write more than `INT_MAX` bytes, FreeBSD returns `EINVAL`. @@ -481,29 +481,45 @@ write :: proc(fd: Handle, data: []byte) -> (int, Error) { } read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr := seek(fd, offset, SEEK_CUR) or_return - n, err = read(fd, data) - _, err1 := seek(fd, curr, SEEK_SET) - if err1 != nil && err == nil { - err = err1 + if len(data) == 0 { + return 0, nil } - return + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read, errno := freebsd.pread(cast(freebsd.Fd)fd, data[:to_read], cast(freebsd.off_t)offset) + + return bytes_read, cast(_Platform_Error)errno } write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr := seek(fd, offset, SEEK_CUR) or_return - n, err = write(fd, data) - _, err1 := seek(fd, curr, SEEK_SET) - if err1 != nil && err == nil { - err = err1 + if len(data) == 0 { + return 0, nil } - return + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written, errno := freebsd.pwrite(cast(freebsd.Fd)fd, data[:to_write], cast(freebsd.off_t)offset) + + return bytes_written, cast(_Platform_Error)errno } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, get_last_error() + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + case: + return 0, errno + } } return res, nil } @@ -824,7 +840,8 @@ get_env :: proc(key: string, allocator := context.allocator) -> (value: string) } @(require_results) -get_current_directory :: proc() -> string { +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator // NOTE(tetra): I would use PATH_MAX here, but I was not able to find // an authoritative value for it across all systems. // The largest value I could find was 4096, so might as well use the page size. @@ -904,7 +921,7 @@ get_page_size :: proc() -> int { _processor_core_count :: proc() -> int { count : int = 0 count_size := size_of(count) - if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == 0 { + if _sysctlbyname("hw.ncpu", &count, &count_size, nil, 0) == 0 { if count > 0 { return count } diff --git a/core/os/os_freestanding.odin b/core/os/os_freestanding.odin index c908e3738..c22a6d7d5 100644 --- a/core/os/os_freestanding.odin +++ b/core/os/os_freestanding.odin @@ -1,4 +1,4 @@ -//+build freestanding +#+build freestanding package os #panic("package os does not support a freestanding target") diff --git a/core/os/os_haiku.odin b/core/os/os_haiku.odin index 2d87c07a6..0d2c334be 100644 --- a/core/os/os_haiku.odin +++ b/core/os/os_haiku.odin @@ -119,49 +119,52 @@ S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK foreign libc { - @(link_name="_errorp") __error :: proc() -> ^c.int --- - - @(link_name="fork") _unix_fork :: proc() -> pid_t --- - @(link_name="getthrid") _unix_getthrid :: proc() -> int --- - - @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u16) -> Handle --- - @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- - @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- - @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- - @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- - @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- - @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- - @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- - @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- - @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- - @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- - @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- - - @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- - @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- - @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- - @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- - @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- - @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- - - @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- - @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- - @(link_name="free") _unix_free :: proc(ptr: rawptr) --- - @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- - - @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- - - @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- - - @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- - @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- - @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- - @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- + @(link_name="_errorp") __error :: proc() -> ^c.int --- + + @(link_name="fork") _unix_fork :: proc() -> pid_t --- + @(link_name="getthrid") _unix_getthrid :: proc() -> int --- + + @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u16) -> Handle --- + @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- + @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- + @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- + @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- + @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- + @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- + @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- + @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- + @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- + @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- + @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- + + @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- + @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- + @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- + @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- + @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- + @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + + @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- + @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- + @(link_name="free") _unix_free :: proc(ptr: rawptr) --- + @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- + + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- + + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- + @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- + @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- } MAXNAMLEN :: haiku.NAME_MAX @@ -216,7 +219,10 @@ close :: proc(fd: Handle) -> Error { } flush :: proc(fd: Handle) -> Error { - // do nothing + result := _unix_fsync(fd) + if result == -1 { + return get_last_error() + } return nil } @@ -250,29 +256,48 @@ write :: proc(fd: Handle, data: []byte) -> (int, Error) { } read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr := seek(fd, offset, SEEK_CUR) or_return - n, err = read(fd, data) - _, err1 := seek(fd, curr, SEEK_SET) - if err1 != nil && err == nil { - err = err1 + if len(data) == 0 { + return 0, nil } - return + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil } write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr := seek(fd, offset, SEEK_CUR) or_return - n, err = write(fd, data) - _, err1 := seek(fd, curr, SEEK_SET) - if err1 != nil && err == nil { - err = err1 + if len(data) == 0 { + return 0, nil } - return + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, get_last_error() + errno := get_last_error() + switch errno { + case .BAD_VALUE: + return 0, .Invalid_Offset + } + return 0, errno } return res, nil } diff --git a/core/os/os_js.odin b/core/os/os_js.odin index eb434c727..348554728 100644 --- a/core/os/os_js.odin +++ b/core/os/os_js.odin @@ -1,35 +1,38 @@ -//+build js +#+build js package os -import "base:runtime" +foreign import "odin_env" @(require_results) is_path_separator :: proc(c: byte) -> bool { return c == '/' || c == '\\' } +Handle :: distinct u32 + +stdout: Handle = 1 +stderr: Handle = 2 + @(require_results) open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) { unimplemented("core:os procedure not supported on JS target") } close :: proc(fd: Handle) -> Error { - unimplemented("core:os procedure not supported on JS target") + return nil } flush :: proc(fd: Handle) -> (err: Error) { - unimplemented("core:os procedure not supported on JS target") + return nil } - - write :: proc(fd: Handle, data: []byte) -> (int, Error) { - unimplemented("core:os procedure not supported on JS target") -} - -@(private="file") -read_console :: proc(handle: Handle, b: []byte) -> (n: int, err: Error) { - unimplemented("core:os procedure not supported on JS target") + foreign odin_env { + @(link_name="write") + _write :: proc "contextless" (fd: Handle, p: []byte) --- + } + _write(fd, data) + return len(data), nil } read :: proc(fd: Handle, data: []byte) -> (int, Error) { @@ -45,19 +48,6 @@ file_size :: proc(fd: Handle) -> (i64, Error) { unimplemented("core:os procedure not supported on JS target") } - -@(private) -MAX_RW :: 1<<30 - -@(private) -pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { - unimplemented("core:os procedure not supported on JS target") -} -@(private) -pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { - unimplemented("core:os procedure not supported on JS target") -} - read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { unimplemented("core:os procedure not supported on JS target") } @@ -65,16 +55,6 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) unimplemented("core:os procedure not supported on JS target") } -stdout: Handle = 1 -stderr: Handle = 2 - -@(require_results) -get_std_handle :: proc "contextless" (h: uint) -> Handle { - context = runtime.default_context() - unimplemented("core:os procedure not supported on JS target") -} - - @(require_results) exists :: proc(path: string) -> bool { unimplemented("core:os procedure not supported on JS target") @@ -90,9 +70,6 @@ is_dir :: proc(path: string) -> bool { unimplemented("core:os procedure not supported on JS target") } -// NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName -//@private cwd_lock := win32.SRWLOCK{} // zero is initialized - @(require_results) get_current_directory :: proc(allocator := context.allocator) -> string { unimplemented("core:os procedure not supported on JS target") @@ -118,18 +95,6 @@ remove_directory :: proc(path: string) -> (err: Error) { } - -@(private, require_results) -is_abs :: proc(path: string) -> bool { - unimplemented("core:os procedure not supported on JS target") -} - -@(private, require_results) -fix_long_path :: proc(path: string) -> string { - unimplemented("core:os procedure not supported on JS target") -} - - link :: proc(old_name, new_name: string) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } @@ -169,7 +134,6 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F unimplemented("core:os procedure not supported on JS target") } -Handle :: distinct uintptr File_Time :: distinct u64 _Platform_Error :: enum i32 { @@ -254,12 +218,7 @@ WSAECONNRESET :: Platform_Error.WSAECONNRESET ERROR_FILE_IS_PIPE :: General_Error.File_Is_Pipe ERROR_FILE_IS_NOT_DIR :: General_Error.Not_Dir -// "Argv" arguments converted to Odin strings -args := _alloc_command_line_arguments() - - - - +args: []string @(require_results) last_write_time :: proc(fd: Handle) -> (File_Time, Error) { @@ -279,26 +238,14 @@ get_page_size :: proc() -> int { @(private, require_results) _processor_core_count :: proc() -> int { - unimplemented("core:os procedure not supported on JS target") + return 1 } exit :: proc "contextless" (code: int) -> ! { - context = runtime.default_context() - unimplemented("core:os procedure not supported on JS target") + unimplemented_contextless("core:os procedure not supported on JS target") } - - @(require_results) current_thread_id :: proc "contextless" () -> int { - context = runtime.default_context() - unimplemented("core:os procedure not supported on JS target") + return 0 } - - - -@(require_results) -_alloc_command_line_arguments :: proc() -> []string { - return nil -} - diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 2f7a5ac43..8c8cd7f73 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -395,9 +395,9 @@ SIOCGIFFLAG :: enum c.int { PORTSEL = 13, /* Can set media type. */ AUTOMEDIA = 14, /* Auto media select active. */ DYNAMIC = 15, /* Dialup device with changing addresses. */ - LOWER_UP = 16, - DORMANT = 17, - ECHO = 18, + LOWER_UP = 16, + DORMANT = 17, + ECHO = 18, } SIOCGIFFLAGS :: bit_set[SIOCGIFFLAG; c.int] @@ -584,8 +584,7 @@ close :: proc(fd: Handle) -> Error { } flush :: proc(fd: Handle) -> Error { - // do nothing - return nil + return _get_errno(unix.sys_fsync(int(fd))) } // If you read or write more than `SSIZE_MAX` bytes, result is implementation defined (probably an error). @@ -654,9 +653,20 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := unix.sys_lseek(int(fd), offset, whence) if res < 0 { - return -1, _get_errno(int(res)) + errno := _get_errno(int(res)) + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno } return i64(res), nil } @@ -975,7 +985,8 @@ unset_env :: proc(key: string) -> Error { } @(require_results) -get_current_directory :: proc() -> string { +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator // NOTE(tetra): I would use PATH_MAX here, but I was not able to find // an authoritative value for it across all systems. // The largest value I could find was 4096, so might as well use the page size. diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin index 82a8dc1eb..493527803 100644 --- a/core/os/os_netbsd.odin +++ b/core/os/os_netbsd.odin @@ -426,7 +426,9 @@ foreign libc { @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u32) -> Handle --- @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 --- @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- @(link_name="stat") _unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> c.int --- @@ -441,6 +443,7 @@ foreign libc { @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, #c_vararg args: ..any) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- @@ -504,7 +507,10 @@ close :: proc(fd: Handle) -> Error { } flush :: proc(fd: Handle) -> Error { - // do nothing + result := _unix_fsync(fd) + if result == -1 { + return get_last_error() + } return nil } @@ -535,29 +541,48 @@ write :: proc(fd: Handle, data: []byte) -> (int, Error) { } read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr := seek(fd, offset, SEEK_CUR) or_return - n, err = read(fd, data) - _, err1 := seek(fd, curr, SEEK_SET) - if err1 != nil && err == nil { - err = err1 + if len(data) == 0 { + return 0, nil } - return + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil } write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr := seek(fd, offset, SEEK_CUR) or_return - n, err = write(fd, data) - _, err1 := seek(fd, curr, SEEK_SET) - if err1 != nil && err == nil { - err = err1 + if len(data) == 0 { + return 0, nil } - return + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, get_last_error() + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno } return res, nil } @@ -869,7 +894,8 @@ get_env :: proc(key: string, allocator := context.allocator) -> (value: string) } @(require_results) -get_current_directory :: proc() -> string { +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator // NOTE(tetra): I would use PATH_MAX here, but I was not able to find // an authoritative value for it across all systems. // The largest value I could find was 4096, so might as well use the page size. @@ -953,7 +979,7 @@ get_page_size :: proc() -> int { _processor_core_count :: proc() -> int { count : int = 0 count_size := size_of(count) - if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == 0 { + if _sysctlbyname("hw.ncpu", &count, &count_size, nil, 0) == 0 { if count > 0 { return count } diff --git a/core/os/os_openbsd.odin b/core/os/os_openbsd.odin index 44eac8564..62872d9dc 100644 --- a/core/os/os_openbsd.odin +++ b/core/os/os_openbsd.odin @@ -343,50 +343,53 @@ AT_REMOVEDIR :: 0x08 @(default_calling_convention="c") foreign libc { - @(link_name="__error") __error :: proc() -> ^c.int --- - - @(link_name="fork") _unix_fork :: proc() -> pid_t --- - @(link_name="getthrid") _unix_getthrid :: proc() -> int --- - - @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u32) -> Handle --- - @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- - @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- - @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- - @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- - @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- - @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- - @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- - @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- - @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- - @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- - @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- - @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- - - @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- - @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- - @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- - @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- - @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- - @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- - - @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- - @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- - @(link_name="free") _unix_free :: proc(ptr: rawptr) --- - @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- - - @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- - - @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- - - @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- - @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- - @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- - @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- + @(link_name="__error") __error :: proc() -> ^c.int --- + + @(link_name="fork") _unix_fork :: proc() -> pid_t --- + @(link_name="getthrid") _unix_getthrid :: proc() -> int --- + + @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u32) -> Handle --- + @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- + @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- + @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- + @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- + @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- + @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- + @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- + @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- + @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- + @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- + @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- + @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- + + @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- + @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- + @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- + @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- + @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- + @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + + @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- + @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- + @(link_name="free") _unix_free :: proc(ptr: rawptr) --- + @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- + + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- + + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- + + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- + @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- + @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- } @(require_results) @@ -428,7 +431,10 @@ close :: proc(fd: Handle) -> Error { } flush :: proc(fd: Handle) -> Error { - // do nothing + result := _unix_fsync(fd) + if result == -1 { + return get_last_error() + } return nil } @@ -463,29 +469,48 @@ write :: proc(fd: Handle, data: []byte) -> (int, Error) { } read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr := seek(fd, offset, SEEK_CUR) or_return - n, err = read(fd, data) - _, err1 := seek(fd, curr, SEEK_SET) - if err1 != nil && err == nil { - err = err1 + if len(data) == 0 { + return 0, nil } - return + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil } write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr := seek(fd, offset, SEEK_CUR) or_return - n, err = write(fd, data) - _, err1 := seek(fd, curr, SEEK_SET) - if err1 != nil && err == nil { - err = err1 + if len(data) == 0 { + return 0, nil } - return + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, get_last_error() + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno } return res, nil } @@ -781,7 +806,8 @@ get_env :: proc(key: string, allocator := context.allocator) -> (value: string) } @(require_results) -get_current_directory :: proc() -> string { +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator buf := make([dynamic]u8, MAX_PATH) for { cwd := _unix_getcwd(cstring(raw_data(buf)), c.size_t(len(buf))) diff --git a/core/os/os_windows.odin b/core/os/os_windows.odin index 273fe5af0..552508f3b 100644 --- a/core/os/os_windows.odin +++ b/core/os/os_windows.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package os import win32 "core:sys/windows" @@ -43,6 +43,7 @@ ERROR_BUFFER_OVERFLOW :: _Platform_Error(111) ERROR_INSUFFICIENT_BUFFER :: _Platform_Error(122) ERROR_MOD_NOT_FOUND :: _Platform_Error(126) ERROR_PROC_NOT_FOUND :: _Platform_Error(127) +ERROR_NEGATIVE_SEEK :: _Platform_Error(131) ERROR_DIR_NOT_EMPTY :: _Platform_Error(145) ERROR_ALREADY_EXISTS :: _Platform_Error(183) ERROR_ENVVAR_NOT_FOUND :: _Platform_Error(203) @@ -91,6 +92,9 @@ get_last_error :: proc "contextless" () -> Error { case win32.ERROR_INVALID_HANDLE: return .Invalid_File + case win32.ERROR_NEGATIVE_SEEK: + return .Invalid_Offset + case win32.ERROR_BAD_ARGUMENTS, win32.ERROR_INVALID_PARAMETER, diff --git a/core/os/stat_unix.odin b/core/os/stat_unix.odin index 8e89bee4f..7f7985e83 100644 --- a/core/os/stat_unix.odin +++ b/core/os/stat_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, netbsd, haiku +#+build linux, darwin, freebsd, openbsd, netbsd, haiku package os import "core:time" diff --git a/core/os/stream.odin b/core/os/stream.odin index 8acbee489..39edc9cd5 100644 --- a/core/os/stream.odin +++ b/core/os/stream.odin @@ -21,6 +21,9 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, case .Flush: os_err = flush(fd) case .Read: + if len(p) == 0 { + return 0, nil + } n_int, os_err = read(fd, p) n = i64(n_int) if n == 0 && os_err == nil { @@ -28,18 +31,27 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, } case .Read_At: + if len(p) == 0 { + return 0, nil + } n_int, os_err = read_at(fd, p, offset) n = i64(n_int) if n == 0 && os_err == nil { err = .EOF } case .Write: + if len(p) == 0 { + return 0, nil + } n_int, os_err = write(fd, p) n = i64(n_int) if n == 0 && os_err == nil { err = .EOF } case .Write_At: + if len(p) == 0 { + return 0, nil + } n_int, os_err = write_at(fd, p, offset) n = i64(n_int) if n == 0 && os_err == nil { @@ -58,5 +70,8 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, if err == nil && os_err != nil { err = error_to_io_error(os_err) } + if err != nil { + n = 0 + } return } diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin index 7137ad844..a18dc739e 100644 --- a/core/path/filepath/path_unix.odin +++ b/core/path/filepath/path_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, netbsd +#+build linux, darwin, freebsd, openbsd, netbsd package filepath when ODIN_OS == .Darwin { diff --git a/core/prof/spall/doc.odin b/core/prof/spall/doc.odin index d9259465b..c81bad05f 100644 --- a/core/prof/spall/doc.odin +++ b/core/prof/spall/doc.odin @@ -1,4 +1,7 @@ /* +Example: + package main + import "base:runtime" import "core:prof/spall" import "core:sync" diff --git a/core/prof/spall/spall_linux.odin b/core/prof/spall/spall_linux.odin index b25d2b336..8060af448 100644 --- a/core/prof/spall/spall_linux.odin +++ b/core/prof/spall/spall_linux.odin @@ -1,10 +1,10 @@ -//+private +#+private package spall // Only for types and constants. import "core:os" -// Package is `//+no-instrumentation`, safe to use. +// Package is `#+no-instrumentation`, safe to use. import "core:sys/linux" MAX_RW :: 0x7fffffff diff --git a/core/prof/spall/spall_unix.odin b/core/prof/spall/spall_unix.odin index fc05b8525..455245aad 100644 --- a/core/prof/spall/spall_unix.odin +++ b/core/prof/spall/spall_unix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, freebsd, openbsd, netbsd +#+private +#+build darwin, freebsd, openbsd, netbsd package spall // Only for types. diff --git a/core/prof/spall/spall_windows.odin b/core/prof/spall/spall_windows.odin index c8b044963..11e216b63 100644 --- a/core/prof/spall/spall_windows.odin +++ b/core/prof/spall/spall_windows.odin @@ -1,10 +1,10 @@ -//+private +#+private package spall // Only for types. import "core:os" -// Package is `//+no-instrumentation`, safe to use. +// Package is `#+no-instrumentation`, safe to use. import win32 "core:sys/windows" MAX_RW :: 1<<30 diff --git a/core/reflect/iterator.odin b/core/reflect/iterator.odin index 5b84f0133..090fe04cc 100644 --- a/core/reflect/iterator.odin +++ b/core/reflect/iterator.odin @@ -19,6 +19,7 @@ iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) { elem.data = rawptr(uintptr(val.data) + uintptr(it^ * info.elem_size)) elem.id = info.elem.id ok = true + index = it^ it^ += 1 } case Type_Info_Slice: @@ -27,6 +28,7 @@ iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) { elem.data = rawptr(uintptr(array.data) + uintptr(it^ * info.elem_size)) elem.id = info.elem.id ok = true + index = it^ it^ += 1 } case Type_Info_Dynamic_Array: @@ -35,6 +37,7 @@ iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) { elem.data = rawptr(uintptr(array.data) + uintptr(it^ * info.elem_size)) elem.id = info.elem.id ok = true + index = it^ it^ += 1 } } @@ -69,10 +72,12 @@ iterate_map :: proc(val: any, it: ^int) -> (key, value: any, ok: bool) { key.id = info.key.id value.id = info.value.id ok = true + it^ += 1 break } } } return -}
\ No newline at end of file +} + diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index decba00ee..c04afb380 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -1015,6 +1015,74 @@ bit_set_is_big_endian :: proc(value: any, loc := #caller_location) -> bool { } +Bit_Field :: struct { + name: string, + type: ^Type_Info, + size: uintptr, // Size in bits + offset: uintptr, // Offset in bits + tag: Struct_Tag, +} + +@(require_results) +bit_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Bit_Field) { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return soa_zip( + name = s.names[:s.field_count], + type = s.types[:s.field_count], + size = s.bit_sizes[:s.field_count], + offset = s.bit_offsets[:s.field_count], + tag = ([^]Struct_Tag)(s.tags)[:s.field_count], + ) + } + return nil +} + +@(require_results) +bit_field_names :: proc(T: typeid) -> []string { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return s.names[:s.field_count] + } + return nil +} + +@(require_results) +bit_field_types :: proc(T: typeid) -> []^Type_Info { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return s.types[:s.field_count] + } + return nil +} + +@(require_results) +bit_field_sizes :: proc(T: typeid) -> []uintptr { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return s.bit_sizes[:s.field_count] + } + return nil +} + +@(require_results) +bit_field_offsets :: proc(T: typeid) -> []uintptr { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return s.bit_offsets[:s.field_count] + } + return nil +} + +@(require_results) +bit_field_tags :: proc(T: typeid) -> []Struct_Tag { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return transmute([]Struct_Tag)s.tags[:s.field_count] + } + return nil +} + @(require_results) as_bool :: proc(a: any) -> (value: bool, valid: bool) { if a == nil { return } @@ -1698,4 +1766,4 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_ runtime.print_typeid(a.id) runtime.print_string("\n") return true -} +}
\ No newline at end of file diff --git a/core/simd/x86/abm.odin b/core/simd/x86/abm.odin index 9018a835a..4b07086ce 100644 --- a/core/simd/x86/abm.odin +++ b/core/simd/x86/abm.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "base:intrinsics" diff --git a/core/simd/x86/adx.odin b/core/simd/x86/adx.odin index 5750ae627..9c6ae063a 100644 --- a/core/simd/x86/adx.odin +++ b/core/simd/x86/adx.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 @(require_results) diff --git a/core/simd/x86/aes.odin b/core/simd/x86/aes.odin index a2cd2e4d3..338381422 100644 --- a/core/simd/x86/aes.odin +++ b/core/simd/x86/aes.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 @(require_results, enable_target_feature = "aes") diff --git a/core/simd/x86/cmpxchg16b.odin b/core/simd/x86/cmpxchg16b.odin index 1307a9cf2..78ebd182f 100644 --- a/core/simd/x86/cmpxchg16b.odin +++ b/core/simd/x86/cmpxchg16b.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package simd_x86 import "base:intrinsics" diff --git a/core/simd/x86/fxsr.odin b/core/simd/x86/fxsr.odin index a9213fed2..ab8cdca7d 100644 --- a/core/simd/x86/fxsr.odin +++ b/core/simd/x86/fxsr.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 @(enable_target_feature="fxsr") diff --git a/core/simd/x86/pclmulqdq.odin b/core/simd/x86/pclmulqdq.odin index e827bf6b9..14e633c06 100644 --- a/core/simd/x86/pclmulqdq.odin +++ b/core/simd/x86/pclmulqdq.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 @(require_results, enable_target_feature="pclmul") diff --git a/core/simd/x86/rdtsc.odin b/core/simd/x86/rdtsc.odin index 8a8b13c4b..84c762274 100644 --- a/core/simd/x86/rdtsc.odin +++ b/core/simd/x86/rdtsc.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 @(require_results) diff --git a/core/simd/x86/sha.odin b/core/simd/x86/sha.odin index bc58e8504..8caa3a268 100644 --- a/core/simd/x86/sha.odin +++ b/core/simd/x86/sha.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 @(require_results, enable_target_feature="sha") diff --git a/core/simd/x86/sse.odin b/core/simd/x86/sse.odin index 4dac50234..1b4a863b6 100644 --- a/core/simd/x86/sse.odin +++ b/core/simd/x86/sse.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "base:intrinsics" diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index 2e3eb8523..aaddbe6b4 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "base:intrinsics" diff --git a/core/simd/x86/sse3.odin b/core/simd/x86/sse3.odin index a905a7726..0e074c946 100644 --- a/core/simd/x86/sse3.odin +++ b/core/simd/x86/sse3.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "base:intrinsics" diff --git a/core/simd/x86/sse41.odin b/core/simd/x86/sse41.odin index c2c1abc2d..81089ed63 100644 --- a/core/simd/x86/sse41.odin +++ b/core/simd/x86/sse41.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "core:simd" diff --git a/core/simd/x86/sse42.odin b/core/simd/x86/sse42.odin index 7a674176b..1a5cb3f50 100644 --- a/core/simd/x86/sse42.odin +++ b/core/simd/x86/sse42.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "core:simd" diff --git a/core/simd/x86/ssse3.odin b/core/simd/x86/ssse3.odin index 2026c7f53..07c846e7b 100644 --- a/core/simd/x86/ssse3.odin +++ b/core/simd/x86/ssse3.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "base:intrinsics" diff --git a/core/simd/x86/types.odin b/core/simd/x86/types.odin index 06a2cd41e..ea0eff534 100644 --- a/core/simd/x86/types.odin +++ b/core/simd/x86/types.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "core:simd" diff --git a/core/slice/slice.odin b/core/slice/slice.odin index 989fcc696..99ad15547 100644 --- a/core/slice/slice.odin +++ b/core/slice/slice.odin @@ -200,6 +200,17 @@ equal :: proc(a, b: $T/[]$E) -> bool where intrinsics.type_is_comparable(E) #no_ return false } when intrinsics.type_is_simple_compare(E) { + if len(a) == 0 { + // Empty slices are always equivalent to each other. + // + // This check is here in the event that a slice with a `data` of + // nil is compared against a slice with a non-nil `data` but a + // length of zero. + // + // In that case, `memory_compare` would return -1 or +1 because one + // of the pointers is nil. + return true + } return runtime.memory_compare(raw_data(a), raw_data(b), len(a)*size_of(E)) == 0 } else { for i in 0..<len(a) { diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index 487b51907..36637c4cd 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -1,4 +1,4 @@ -//+private +#+private package slice import "base:intrinsics" diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index dce9f834a..b1155c22f 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -7,11 +7,11 @@ Parses a boolean value from the input string **Inputs** - s: The input string - - true: "1", "t", "T", "true", "TRUE", "True" - - false: "0", "f", "F", "false", "FALSE", "False" + - true: "1", "t", "T", "true", "TRUE", "True" + - false: "0", "f", "F", "false", "FALSE", "False" - n: An optional pointer to an int to store the length of the parsed substring (default: nil) -**Returns** +**Returns** - result: The parsed boolean value (default: false) - ok: A boolean indicating whether the parsing was successful */ @@ -29,7 +29,7 @@ parse_bool :: proc(s: string, n: ^int = nil) -> (result: bool = false, ok: bool) /* Finds the integer value of the given rune -**Inputs** +**Inputs** - r: The input rune to find the integer value of **Returns** The integer value of the given rune @@ -47,7 +47,7 @@ _digit_value :: proc(r: rune) -> int { /* Parses an integer value from the input string in the given base, without a prefix -**Inputs** +**Inputs** - str: The input string to parse the integer value from - base: The base of the integer value to be parsed (must be between 1 and 16) - n: An optional pointer to an int to store the length of the parsed substring (default: nil) @@ -65,7 +65,7 @@ Output: -1234 false -**Returns** +**Returns** - value: Parses an integer value from a string, in the given base, without a prefix. - ok: ok=false if no numeric value of the appropriate base could be found, or if the input string contained more than just the number. */ @@ -117,12 +117,12 @@ parse_i64_of_base :: proc(str: string, base: int, n: ^int = nil) -> (value: i64, /* Parses an integer value from the input string in base 10, unless there's a prefix -**Inputs** +**Inputs** - str: The input string to parse the integer value from - n: An optional pointer to an int to store the length of the parsed substring (default: nil) Example: - + import "core:fmt" import "core:strconv" parse_i64_maybe_prefixed_example :: proc() { @@ -132,13 +132,13 @@ Example: n, ok = strconv.parse_i64_maybe_prefixed("0xeeee") fmt.println(n,ok) } - + Output: 1234 true 61166 true -**Returns** +**Returns** - value: The parsed integer value - ok: ok=false if a valid integer could not be found, or if the input string contained more than just the number. */ @@ -200,14 +200,14 @@ parse_i64 :: proc{parse_i64_maybe_prefixed, parse_i64_of_base} /* Parses an unsigned 64-bit integer value from the input string without a prefix, using the specified base -**Inputs** +**Inputs** - str: The input string to parse - base: The base of the number system to use for parsing - - Must be between 1 and 16 (inclusive) + - Must be between 1 and 16 (inclusive) - n: An optional pointer to an int to store the length of the parsed substring (default: nil) Example: - + import "core:fmt" import "core:strconv" parse_u64_of_base_example :: proc() { @@ -217,13 +217,13 @@ Example: n, ok = strconv.parse_u64_of_base("5678eee",16) fmt.println(n,ok) } - + Output: 1234 false 90672878 true -**Returns** +**Returns** - value: The parsed uint64 value - ok: A boolean indicating whether the parsing was successful */ @@ -261,15 +261,15 @@ parse_u64_of_base :: proc(str: string, base: int, n: ^int = nil) -> (value: u64, /* Parses an unsigned 64-bit integer value from the input string, using the specified base or inferring the base from a prefix -**Inputs** +**Inputs** - str: The input string to parse - base: The base of the number system to use for parsing (default: 0) - - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) - - If base is not 0, it will be used for parsing regardless of any prefix in the input string + - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) + - If base is not 0, it will be used for parsing regardless of any prefix in the input string - n: An optional pointer to an int to store the length of the parsed substring (default: nil) Example: - + import "core:fmt" import "core:strconv" parse_u64_maybe_prefixed_example :: proc() { @@ -279,13 +279,13 @@ Example: n, ok = strconv.parse_u64_maybe_prefixed("0xee") fmt.println(n,ok) } - + Output: 1234 true 238 true -**Returns** +**Returns** - value: The parsed uint64 value - ok: ok=false if a valid integer could not be found, if the value was negative, or if the input string contained more than just the number. */ @@ -336,14 +336,14 @@ parse_u64 :: proc{parse_u64_maybe_prefixed, parse_u64_of_base} /* Parses a signed integer value from the input string, using the specified base or inferring the base from a prefix -**Inputs** +**Inputs** - s: The input string to parse - base: The base of the number system to use for parsing (default: 0) - - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) - - If base is not 0, it will be used for parsing regardless of any prefix in the input string + - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) + - If base is not 0, it will be used for parsing regardless of any prefix in the input string Example: - + import "core:fmt" import "core:strconv" parse_int_example :: proc() { @@ -356,14 +356,14 @@ Example: n, ok = strconv.parse_int("0xffff") // with prefix and inferred base fmt.println(n,ok) } - + Output: 1234 true 65535 true 65535 true -**Returns** +**Returns** - value: The parsed int value - ok: `false` if no appropriate value could be found, or if the input string contained more than just the number. */ @@ -379,11 +379,11 @@ parse_int :: proc(s: string, base := 0, n: ^int = nil) -> (value: int, ok: bool) /* Parses an unsigned integer value from the input string, using the specified base or inferring the base from a prefix -**Inputs** +**Inputs** - s: The input string to parse - base: The base of the number system to use for parsing (default: 0, inferred) - - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) - - If base is not 0, it will be used for parsing regardless of any prefix in the input string + - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) + - If base is not 0, it will be used for parsing regardless of any prefix in the input string Example: @@ -1729,7 +1729,7 @@ quote_rune :: proc(buf: []byte, r: rune) -> string { } } - if buf == nil { + if buf == nil || r < 0 { return "" } diff --git a/core/strings/strings.odin b/core/strings/strings.odin index 216d7ad79..dbc84f8b7 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -93,7 +93,7 @@ Inputs: Returns: - res: A string created from the null-terminated byte pointer and length */ -string_from_null_terminated_ptr :: proc(ptr: [^]byte, len: int) -> (res: string) { +string_from_null_terminated_ptr :: proc "contextless" (ptr: [^]byte, len: int) -> (res: string) { s := string(ptr[:len]) s = truncate_to_byte(s, 0) return s @@ -139,7 +139,7 @@ NOTE: Failure to find the byte results in returning the entire string. Returns: - res: The truncated string */ -truncate_to_byte :: proc(str: string, b: byte) -> (res: string) { +truncate_to_byte :: proc "contextless" (str: string, b: byte) -> (res: string) { n := index_byte(str, b) if n < 0 { n = len(str) @@ -261,7 +261,7 @@ Inputs: Returns: - result: `-1` if `lhs` comes first, `1` if `rhs` comes first, or `0` if they are equal */ -compare :: proc(lhs, rhs: string) -> (result: int) { +compare :: proc "contextless" (lhs, rhs: string) -> (result: int) { return mem.compare(transmute([]byte)lhs, transmute([]byte)rhs) } /* @@ -710,20 +710,17 @@ The concatenated string, and an error if allocation fails concatenate_safe :: proc(a: []string, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) { return concatenate(a, allocator) } + /* Returns a substring of the input string `s` with the specified rune offset and length -*Allocates Using Provided Allocator* - Inputs: - s: The input string to cut - rune_offset: The starting rune index (default is 0). In runes, not bytes. - rune_length: The number of runes to include in the substring (default is 0, which returns the remainder of the string). In runes, not bytes. -- allocator: (default is context.allocator) Returns: - res: The substring -- err: An optional allocator error if one occured, `nil` otherwise Example: @@ -743,57 +740,71 @@ Output: example */ -cut :: proc(s: string, rune_offset := int(0), rune_length := int(0), allocator := context.allocator, loc := #caller_location) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error { +cut :: proc(s: string, rune_offset := int(0), rune_length := int(0)) -> (res: string) { s := s; rune_length := rune_length - context.allocator = allocator - // If we signal that we want the entire remainder (length <= 0) *and* - // the offset is zero, then we can early out by cloning the input - if rune_offset == 0 && rune_length <= 0 { - return clone(s) + count := 0 + for _, offset in s { + if count == rune_offset { + s = s[offset:] + break + } + count += 1 } - // We need to know if we have enough runes to cover offset + length. - rune_count := utf8.rune_count_in_string(s) - - // We're asking for a substring starting after the end of the input string. - // That's just an empty string. - if rune_offset >= rune_count { - return "", nil + if rune_length <= 1 { + return s } - // If we don't specify the length of the substring, use the remainder. - if rune_length <= 0 { - rune_length = rune_count - rune_offset + count = 0 + for _, offset in s { + if count == rune_length { + s = s[:offset] + break + } + count += 1 } + return s +} - // We don't yet know how many bytes we need exactly. - // But we do know it's bounded by the number of runes * 4 bytes, - // and can be no more than the size of the input string. - bytes_needed := min(rune_length * 4, len(s)) - buf := make([]u8, bytes_needed, allocator, loc) or_return +/* +Returns a substring of the input string `s` with the specified rune offset and length - byte_offset := 0 - for i := 0; i < rune_count; i += 1 { - _, w := utf8.decode_rune_in_string(s) +*Allocates Using Provided Allocator* - // If the rune is part of the substring, copy it to the output buffer. - if i >= rune_offset { - for j := 0; j < w; j += 1 { - buf[byte_offset+j] = s[j] - } - byte_offset += w - } +Inputs: +- s: The input string to cut +- rune_offset: The starting rune index (default is 0). In runes, not bytes. +- rune_length: The number of runes to include in the substring (default is 0, which returns the remainder of the string). In runes, not bytes. +- allocator: (default is context.allocator) - // We're done if we reach the end of the input string, *or* - // if we've reached a specified length in runes. - if rune_length > 0 { - if i == rune_offset + rune_length - 1 { break } - } - s = s[w:] +Returns: +- res: The substring +- err: An optional allocator error if one occured, `nil` otherwise + +Example: + + import "core:fmt" + import "core:strings" + + cut_example :: proc() { + fmt.println(strings.cut_clone("some example text", 0, 4)) // -> "some" + fmt.println(strings.cut_clone("some example text", 2, 2)) // -> "me" + fmt.println(strings.cut_clone("some example text", 5, 7)) // -> "example" } - return string(buf[:byte_offset]), nil + +Output: + + some + me + example + +*/ +cut_clone :: proc(s: string, rune_offset := int(0), rune_length := int(0), allocator := context.allocator, loc := #caller_location) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error { + res = cut(s, rune_offset, rune_length) + return clone(res, allocator, loc) } + /* Splits the input string `s` into a slice of substrings separated by the specified `sep` string @@ -1436,7 +1447,7 @@ Output: -1 */ -index_byte :: proc(s: string, c: byte) -> (res: int) { +index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) { return #force_inline bytes.index_byte(transmute([]u8)s, c) } /* @@ -1471,7 +1482,7 @@ Output: -1 */ -last_index_byte :: proc(s: string, c: byte) -> (res: int) { +last_index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) { return #force_inline bytes.last_index_byte(transmute([]u8)s, c) } /* @@ -1565,8 +1576,8 @@ Output: -1 */ -index :: proc(s, substr: string) -> (res: int) { - hash_str_rabin_karp :: proc(s: string) -> (hash: u32 = 0, pow: u32 = 1) { +index :: proc "contextless" (s, substr: string) -> (res: int) { + hash_str_rabin_karp :: proc "contextless" (s: string) -> (hash: u32 = 0, pow: u32 = 1) { for i := 0; i < len(s); i += 1 { hash = hash*PRIME_RABIN_KARP + u32(s[i]) } @@ -3316,3 +3327,106 @@ levenshtein_distance :: proc(a, b: string, allocator := context.allocator, loc : return costs[n], nil } + +@(private) +internal_substring :: proc(s: string, rune_start: int, rune_end: int) -> (sub: string, ok: bool) { + sub = s + ok = true + + rune_i: int + + if rune_start > 0 { + ok = false + for _, i in sub { + if rune_start == rune_i { + ok = true + sub = sub[i:] + break + } + rune_i += 1 + } + if !ok { return } + } + + if rune_end >= rune_start { + ok = false + for _, i in sub { + if rune_end == rune_i { + ok = true + sub = sub[:i] + break + } + rune_i += 1 + } + + if rune_end == rune_i { + ok = true + } + } + + return +} + +/* +Returns a substring of `s` that starts at rune index `rune_start` and goes up to `rune_end`. + +Think of it as slicing `s[rune_start:rune_end]` but rune-wise. + +Inputs: +- s: the string to substring +- rune_start: the start (inclusive) rune +- rune_end: the end (exclusive) rune + +Returns: +- sub: the substring +- ok: whether the rune indexes where in bounds of the original string +*/ +substring :: proc(s: string, rune_start: int, rune_end: int) -> (sub: string, ok: bool) { + if rune_start < 0 || rune_end < 0 || rune_end < rune_start { + return + } + + return internal_substring(s, rune_start, rune_end) +} + +/* +Returns a substring of `s` that starts at rune index `rune_start` and goes up to the end of the string. + +Think of it as slicing `s[rune_start:]` but rune-wise. + +Inputs: +- s: the string to substring +- rune_start: the start (inclusive) rune + +Returns: +- sub: the substring +- ok: whether the rune indexes where in bounds of the original string +*/ +substring_from :: proc(s: string, rune_start: int) -> (sub: string, ok: bool) { + if rune_start < 0 { + return + } + + return internal_substring(s, rune_start, -1) +} + +/* +Returns a substring of `s` that goes up to rune index `rune_end`. + +Think of it as slicing `s[:rune_end]` but rune-wise. + +Inputs: +- s: the string to substring +- rune_end: the end (exclusive) rune + +Returns: +- sub: the substring +- ok: whether the rune indexes where in bounds of the original string +*/ +substring_to :: proc(s: string, rune_end: int) -> (sub: string, ok: bool) { + if rune_end < 0 { + return + } + + return internal_substring(s, -1, rune_end) +} diff --git a/core/sync/chan/chan.odin b/core/sync/chan/chan.odin index 0c98124de..c470d15f3 100644 --- a/core/sync/chan/chan.odin +++ b/core/sync/chan/chan.odin @@ -22,19 +22,17 @@ Raw_Chan :: struct { allocator: runtime.Allocator, allocation_size: int, msg_size: u16, - closed: b16, // atomic + closed: b16, // guarded by `mutex` mutex: sync.Mutex, r_cond: sync.Cond, w_cond: sync.Cond, - r_waiting: int, // atomic - w_waiting: int, // atomic + r_waiting: int, // guarded by `mutex` + w_waiting: int, // guarded by `mutex` // Buffered queue: ^Raw_Queue, // Unbuffered - r_mutex: sync.Mutex, - w_mutex: sync.Mutex, unbuffered_data: rawptr, } @@ -164,27 +162,30 @@ send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) { } if c.queue != nil { // buffered sync.guard(&c.mutex) - for c.queue.len == c.queue.cap { - sync.atomic_add(&c.w_waiting, 1) + for !c.closed && c.queue.len == c.queue.cap { + c.w_waiting += 1 sync.wait(&c.w_cond, &c.mutex) - sync.atomic_sub(&c.w_waiting, 1) + c.w_waiting -= 1 + } + + if c.closed { + return false } ok = raw_queue_push(c.queue, msg_in) - if sync.atomic_load(&c.r_waiting) > 0 { + if c.r_waiting > 0 { sync.signal(&c.r_cond) } } else if c.unbuffered_data != nil { // unbuffered - sync.guard(&c.w_mutex) sync.guard(&c.mutex) - if sync.atomic_load(&c.closed) { + if c.closed { return false } mem.copy(c.unbuffered_data, msg_in, int(c.msg_size)) - sync.atomic_add(&c.w_waiting, 1) - if sync.atomic_load(&c.r_waiting) > 0 { + c.w_waiting += 1 + if c.r_waiting > 0 { sync.signal(&c.r_cond) } sync.wait(&c.w_cond, &c.mutex) @@ -201,13 +202,13 @@ recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) { if c.queue != nil { // buffered sync.guard(&c.mutex) for c.queue.len == 0 { - if sync.atomic_load(&c.closed) { + if c.closed { return } - sync.atomic_add(&c.r_waiting, 1) + c.r_waiting += 1 sync.wait(&c.r_cond, &c.mutex) - sync.atomic_sub(&c.r_waiting, 1) + c.r_waiting -= 1 } msg := raw_queue_pop(c.queue) @@ -215,27 +216,26 @@ recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) { mem.copy(msg_out, msg, int(c.msg_size)) } - if sync.atomic_load(&c.w_waiting) > 0 { + if c.w_waiting > 0 { sync.signal(&c.w_cond) } ok = true } else if c.unbuffered_data != nil { // unbuffered - sync.guard(&c.r_mutex) sync.guard(&c.mutex) - for !sync.atomic_load(&c.closed) && - sync.atomic_load(&c.w_waiting) == 0 { - sync.atomic_add(&c.r_waiting, 1) + for !c.closed && + c.w_waiting == 0 { + c.r_waiting += 1 sync.wait(&c.r_cond, &c.mutex) - sync.atomic_sub(&c.r_waiting, 1) + c.r_waiting -= 1 } - if sync.atomic_load(&c.closed) { + if c.closed { return } mem.copy(msg_out, c.unbuffered_data, int(c.msg_size)) - sync.atomic_sub(&c.w_waiting, 1) + c.w_waiting -= 1 sync.signal(&c.w_cond) ok = true @@ -255,21 +255,24 @@ try_send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) return false } + if c.closed { + return false + } + ok = raw_queue_push(c.queue, msg_in) - if sync.atomic_load(&c.r_waiting) > 0 { + if c.r_waiting > 0 { sync.signal(&c.r_cond) } } else if c.unbuffered_data != nil { // unbuffered - sync.guard(&c.w_mutex) sync.guard(&c.mutex) - if sync.atomic_load(&c.closed) { + if c.closed { return false } mem.copy(c.unbuffered_data, msg_in, int(c.msg_size)) - sync.atomic_add(&c.w_waiting, 1) - if sync.atomic_load(&c.r_waiting) > 0 { + c.w_waiting += 1 + if c.r_waiting > 0 { sync.signal(&c.r_cond) } sync.wait(&c.w_cond, &c.mutex) @@ -294,21 +297,19 @@ try_recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> bool { mem.copy(msg_out, msg, int(c.msg_size)) } - if sync.atomic_load(&c.w_waiting) > 0 { + if c.w_waiting > 0 { sync.signal(&c.w_cond) } return true } else if c.unbuffered_data != nil { // unbuffered - sync.guard(&c.r_mutex) sync.guard(&c.mutex) - if sync.atomic_load(&c.closed) || - sync.atomic_load(&c.w_waiting) == 0 { + if c.closed || c.w_waiting == 0 { return false } mem.copy(msg_out, c.unbuffered_data, int(c.msg_size)) - sync.atomic_sub(&c.w_waiting, 1) + c.w_waiting -= 1 sync.signal(&c.w_cond) return true @@ -351,10 +352,10 @@ close :: proc "contextless" (c: ^Raw_Chan) -> bool { return false } sync.guard(&c.mutex) - if sync.atomic_load(&c.closed) { + if c.closed { return false } - sync.atomic_store(&c.closed, true) + c.closed = true sync.broadcast(&c.r_cond) sync.broadcast(&c.w_cond) return true @@ -366,7 +367,7 @@ is_closed :: proc "contextless" (c: ^Raw_Chan) -> bool { return true } sync.guard(&c.mutex) - return bool(sync.atomic_load(&c.closed)) + return bool(c.closed) } @@ -421,22 +422,21 @@ raw_queue_pop :: proc "contextless" (q: ^Raw_Queue) -> (data: rawptr) { @(require_results) can_recv :: proc "contextless" (c: ^Raw_Chan) -> bool { + sync.guard(&c.mutex) if is_buffered(c) { - return len(c) > 0 + return c.queue.len > 0 } - sync.guard(&c.mutex) - return sync.atomic_load(&c.w_waiting) > 0 + return c.w_waiting > 0 } @(require_results) can_send :: proc "contextless" (c: ^Raw_Chan) -> bool { + sync.guard(&c.mutex) if is_buffered(c) { - sync.guard(&c.mutex) - return len(c) < cap(c) + return c.queue.len < c.queue.cap } - sync.guard(&c.mutex) - return sync.atomic_load(&c.r_waiting) > 0 + return c.w_waiting == 0 } @@ -485,4 +485,4 @@ select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: [] ok = send_raw(sends[sel.idx], send_msgs[sel.idx]) } return -}
\ No newline at end of file +} diff --git a/core/sync/doc.odin b/core/sync/doc.odin index 9876c46fb..320732ea7 100644 --- a/core/sync/doc.odin +++ b/core/sync/doc.odin @@ -7,8 +7,8 @@ synchronize threads' access to shared memory. To limit or control the threads' access to shared memory typically the following approaches are used: -* Locks -* Lock-free +- Locks +- Lock-free When using locks, sections of the code that access shared memory (also known as **critical sections**) are guarded by locks, allowing limited access to threads @@ -18,4 +18,4 @@ In lock-free programming the data itself is organized in such a way that threads don't intervene much. It can be done via segmenting the data between threads, and/or by using atomic operations. */ -package sync
\ No newline at end of file +package sync diff --git a/core/sync/extended.odin b/core/sync/extended.odin index b446fefa0..30b1b2770 100644 --- a/core/sync/extended.odin +++ b/core/sync/extended.odin @@ -8,7 +8,7 @@ _ :: vg Wait group. Wait group is a synchronization primitive used by the waiting thread to wait, -until a all working threads finish work. +until all working threads finish work. The waiting thread first sets the number of working threads it will expect to wait for using `wait_group_add` call, and start waiting using `wait_group_wait` @@ -35,7 +35,7 @@ Wait_Group :: struct #no_copy { /* Increment an internal counter of a wait group. -This procedure atomicaly increments a number to the specified wait group's +This procedure atomically increments a number to the specified wait group's internal counter by a specified amount. This operation can be done on any thread. */ @@ -48,12 +48,12 @@ wait_group_add :: proc "contextless" (wg: ^Wait_Group, delta: int) { atomic_add(&wg.counter, delta) if wg.counter < 0 { - _panic("sync.Wait_Group negative counter") + panic_contextless("sync.Wait_Group negative counter") } if wg.counter == 0 { cond_broadcast(&wg.cond) if wg.counter != 0 { - _panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") + panic_contextless("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") } } } @@ -81,7 +81,7 @@ wait_group_wait :: proc "contextless" (wg: ^Wait_Group) { if wg.counter != 0 { cond_wait(&wg.cond, &wg.mutex) if wg.counter != 0 { - _panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") + panic_contextless("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") } } } @@ -105,7 +105,7 @@ wait_group_wait_with_timeout :: proc "contextless" (wg: ^Wait_Group, duration: t return false } if wg.counter != 0 { - _panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") + panic_contextless("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") } } return true @@ -121,7 +121,7 @@ When `barrier_wait` procedure is called by any thread, that thread will block the execution, until all threads associated with the barrier reach the same point of execution and also call `barrier_wait`. -when barrier is initialized, a `thread_count` parameter is passed, signifying +When a barrier is initialized, a `thread_count` parameter is passed, signifying the amount of participant threads of the barrier. The barrier also keeps track of an internal atomic counter. When a thread calls `barrier_wait`, the internal counter is incremented. When the internal counter reaches `thread_count`, it is @@ -208,7 +208,7 @@ Represents a thread synchronization primitive that, when signalled, releases one single waiting thread and then resets automatically to a state where it can be signalled again. -When a thread calls `auto_reset_event_wait`, it's execution will be blocked, +When a thread calls `auto_reset_event_wait`, its execution will be blocked, until the event is signalled by another thread. The call to `auto_reset_event_signal` wakes up exactly one thread waiting for the event. */ @@ -228,15 +228,15 @@ thread. */ auto_reset_event_signal :: proc "contextless" (e: ^Auto_Reset_Event) { old_status := atomic_load_explicit(&e.status, .Relaxed) + new_status := old_status + 1 if old_status < 1 else 1 for { - new_status := old_status + 1 if old_status < 1 else 1 if _, ok := atomic_compare_exchange_weak_explicit(&e.status, old_status, new_status, .Release, .Relaxed); ok { break } - - if old_status < 0 { - sema_post(&e.sema) - } + cpu_relax() + } + if old_status < 0 { + sema_post(&e.sema) } } @@ -297,7 +297,7 @@ waiting to acquire the lock, exactly one of those threads is unblocked and allowed into the critical section. */ ticket_mutex_unlock :: #force_inline proc "contextless" (m: ^Ticket_Mutex) { - atomic_add_explicit(&m.serving, 1, .Relaxed) + atomic_add_explicit(&m.serving, 1, .Release) } /* @@ -331,8 +331,8 @@ Benaphore. A benaphore is a combination of an atomic variable and a semaphore that can improve locking efficiency in a no-contention system. Acquiring a benaphore -lock doesn't call into an internal semaphore, if no other thread in a middle of -a critical section. +lock doesn't call into an internal semaphore, if no other thread is in the +middle of a critical section. Once a lock on a benaphore is acquired by a thread, no other thread is allowed into any critical sections, associted with the same benaphore, until the lock @@ -355,7 +355,7 @@ from entering any critical sections associated with the same benaphore, until until the lock is released. */ benaphore_lock :: proc "contextless" (b: ^Benaphore) { - if atomic_add_explicit(&b.counter, 1, .Acquire) > 1 { + if atomic_add_explicit(&b.counter, 1, .Acquire) > 0 { sema_wait(&b.sema) } } @@ -381,10 +381,10 @@ Release a lock on a benaphore. This procedure releases a lock on the specified benaphore. If any of the threads are waiting on the lock, exactly one thread is allowed into a critical section -associated with the same banaphore. +associated with the same benaphore. */ benaphore_unlock :: proc "contextless" (b: ^Benaphore) { - if atomic_sub_explicit(&b.counter, 1, .Release) > 0 { + if atomic_sub_explicit(&b.counter, 1, .Release) > 1 { sema_post(&b.sema) } } @@ -418,8 +418,8 @@ benaphore_guard :: proc "contextless" (m: ^Benaphore) -> bool { /* Recursive benaphore. -Recurisve benaphore is just like a plain benaphore, except it allows reentrancy -into the critical section. +A recursive benaphore is just like a plain benaphore, except it allows +reentrancy into the critical section. When a lock is acquired on a benaphore, all other threads attempting to acquire a lock on the same benaphore will be blocked from any critical sections, @@ -449,13 +449,15 @@ recursive benaphore, until the lock is released. */ recursive_benaphore_lock :: proc "contextless" (b: ^Recursive_Benaphore) { tid := current_thread_id() - if atomic_add_explicit(&b.counter, 1, .Acquire) > 1 { - if tid != b.owner { - sema_wait(&b.sema) + check_owner: if tid != atomic_load_explicit(&b.owner, .Acquire) { + atomic_add_explicit(&b.counter, 1, .Relaxed) + if _, ok := atomic_compare_exchange_strong_explicit(&b.owner, 0, tid, .Release, .Relaxed); ok { + break check_owner } + sema_wait(&b.sema) + atomic_store_explicit(&b.owner, tid, .Release) } // inside the lock - b.owner = tid b.recursion += 1 } @@ -472,15 +474,14 @@ benaphore, until the lock is released. */ recursive_benaphore_try_lock :: proc "contextless" (b: ^Recursive_Benaphore) -> bool { tid := current_thread_id() - if b.owner == tid { - atomic_add_explicit(&b.counter, 1, .Acquire) - } - - if v, _ := atomic_compare_exchange_strong_explicit(&b.counter, 0, 1, .Acquire, .Acquire); v != 0 { + check_owner: if tid != atomic_load_explicit(&b.owner, .Acquire) { + if _, ok := atomic_compare_exchange_strong_explicit(&b.owner, 0, tid, .Release, .Relaxed); ok { + atomic_add_explicit(&b.counter, 1, .Relaxed) + break check_owner + } return false } // inside the lock - b.owner = tid b.recursion += 1 return true } @@ -494,14 +495,14 @@ for other threads for entering. */ recursive_benaphore_unlock :: proc "contextless" (b: ^Recursive_Benaphore) { tid := current_thread_id() - _assert(tid == b.owner, "tid != b.owner") + assert_contextless(tid == atomic_load_explicit(&b.owner, .Relaxed), "tid != b.owner") b.recursion -= 1 recursion := b.recursion + if recursion == 0 { - b.owner = 0 - } - if atomic_sub_explicit(&b.counter, 1, .Release) > 0 { - if recursion == 0 { + if atomic_sub_explicit(&b.counter, 1, .Relaxed) == 1 { + atomic_store_explicit(&b.owner, 0, .Release) + } else { sema_post(&b.sema) } } @@ -740,4 +741,4 @@ Make event available. one_shot_event_signal :: proc "contextless" (e: ^One_Shot_Event) { atomic_store_explicit(&e.state, 1, .Release) futex_broadcast(&e.state) -}
\ No newline at end of file +} diff --git a/core/sync/futex_darwin.odin b/core/sync/futex_darwin.odin index fca9aadfe..10ff7bfbb 100644 --- a/core/sync/futex_darwin.odin +++ b/core/sync/futex_darwin.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin +#+private +#+build darwin package sync import "core:c" @@ -12,6 +12,8 @@ foreign System { // __ulock_wait is not available on 10.15 // See https://github.com/odin-lang/Odin/issues/1959 __ulock_wait :: proc "c" (operation: u32, addr: rawptr, value: u64, timeout_us: u32) -> c.int --- + // >= MacOS 11. + __ulock_wait2 :: proc "c" (operation: u32, addr: rawptr, value: u64, timeout_ns: u64, value2: u64) -> c.int --- __ulock_wake :: proc "c" (operation: u32, addr: rawptr, wake_value: u64) -> c.int --- } @@ -48,22 +50,29 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati case -ETIMEDOUT: return false case: - _panic("darwin.os_sync_wait_on_address_with_timeout failure") + panic_contextless("darwin.os_sync_wait_on_address_with_timeout failure") } } else { - timeout_ns := u32(duration) - s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns) + when darwin.ULOCK_WAIT_2_AVAILABLE { + timeout_ns := u64(duration) + s := __ulock_wait2(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns, 0) + } else { + timeout_us := u32(duration / time.Microsecond) + s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_us) + } + if s >= 0 { return true } + switch s { case EINTR, EFAULT: return true case ETIMEDOUT: return false case: - _panic("futex_wait failure") + panic_contextless("futex_wait failure") } return true @@ -83,7 +92,7 @@ _futex_signal :: proc "contextless" (f: ^Futex) { case -ENOENT: return case: - _panic("darwin.os_sync_wake_by_address_any failure") + panic_contextless("darwin.os_sync_wake_by_address_any failure") } } } else { @@ -99,7 +108,7 @@ _futex_signal :: proc "contextless" (f: ^Futex) { case ENOENT: return case: - _panic("futex_wake_single failure") + panic_contextless("futex_wake_single failure") } } @@ -119,7 +128,7 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) { case -ENOENT: return case: - _panic("darwin.os_sync_wake_by_address_all failure") + panic_contextless("darwin.os_sync_wake_by_address_all failure") } } } else { @@ -135,7 +144,7 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) { case ENOENT: return case: - _panic("futex_wake_all failure") + panic_contextless("futex_wake_all failure") } } diff --git a/core/sync/futex_freebsd.odin b/core/sync/futex_freebsd.odin index ac6e2400a..e3f95b146 100644 --- a/core/sync/futex_freebsd.odin +++ b/core/sync/futex_freebsd.odin @@ -1,5 +1,5 @@ -//+private -//+build freebsd +#+private +#+build freebsd package sync import "core:c" @@ -21,7 +21,7 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool { continue } - _panic("_futex_wait failure") + panic_contextless("_futex_wait failure") } unreachable() @@ -44,14 +44,14 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati return false } - _panic("_futex_wait_with_timeout failure") + panic_contextless("_futex_wait_with_timeout failure") } _futex_signal :: proc "contextless" (f: ^Futex) { errno := freebsd._umtx_op(f, .WAKE, 1, nil, nil) if errno != nil { - _panic("_futex_signal failure") + panic_contextless("_futex_signal failure") } } @@ -59,6 +59,6 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) { errno := freebsd._umtx_op(f, .WAKE, cast(c.ulong)max(i32), nil, nil) if errno != nil { - _panic("_futex_broadcast failure") + panic_contextless("_futex_broadcast failure") } } diff --git a/core/sync/futex_haiku.odin b/core/sync/futex_haiku.odin index 6fe5894a0..21d07b801 100644 --- a/core/sync/futex_haiku.odin +++ b/core/sync/futex_haiku.odin @@ -1,4 +1,4 @@ -//+private +#+private package sync import "core:c" diff --git a/core/sync/futex_linux.odin b/core/sync/futex_linux.odin index fe57c12ed..52143880b 100644 --- a/core/sync/futex_linux.odin +++ b/core/sync/futex_linux.odin @@ -1,5 +1,5 @@ -//+private -//+build linux +#+private +#+build linux package sync import "core:time" @@ -15,7 +15,7 @@ _futex_wait :: proc "contextless" (futex: ^Futex, expected: u32) -> bool { return true case: // TODO(flysand): More descriptive panic messages based on the vlaue of `errno` - _panic("futex_wait failure") + panic_contextless("futex_wait failure") } } @@ -34,7 +34,7 @@ _futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, du case .NONE, .EINTR, .EAGAIN: return true case: - _panic("futex_wait_with_timeout failure") + panic_contextless("futex_wait_with_timeout failure") } } @@ -44,7 +44,7 @@ _futex_signal :: proc "contextless" (futex: ^Futex) { case .NONE: return case: - _panic("futex_wake_single failure") + panic_contextless("futex_wake_single failure") } } @@ -57,6 +57,6 @@ _futex_broadcast :: proc "contextless" (futex: ^Futex) { case .NONE: return case: - _panic("_futex_wake_all failure") + panic_contextless("_futex_wake_all failure") } } diff --git a/core/sync/futex_netbsd.odin b/core/sync/futex_netbsd.odin index d12409f32..e49b25b02 100644 --- a/core/sync/futex_netbsd.odin +++ b/core/sync/futex_netbsd.odin @@ -1,4 +1,4 @@ -//+private +#+private package sync import "base:intrinsics" @@ -35,7 +35,7 @@ _futex_wait :: proc "contextless" (futex: ^Futex, expected: u32) -> bool { case EINTR, EAGAIN: return true case: - _panic("futex_wait failure") + panic_contextless("futex_wait failure") } } return true @@ -55,7 +55,7 @@ _futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, du case ETIMEDOUT: return false case: - _panic("futex_wait_with_timeout failure") + panic_contextless("futex_wait_with_timeout failure") } } return true @@ -63,12 +63,12 @@ _futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, du _futex_signal :: proc "contextless" (futex: ^Futex) { if _, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, 1, 0, 0, 0); !ok { - _panic("futex_wake_single failure") + panic_contextless("futex_wake_single failure") } } _futex_broadcast :: proc "contextless" (futex: ^Futex) { if _, ok := intrinsics.syscall_bsd(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, uintptr(max(i32)), 0, 0, 0); !ok { - _panic("_futex_wake_all failure") + panic_contextless("_futex_wake_all failure") } } diff --git a/core/sync/futex_openbsd.odin b/core/sync/futex_openbsd.odin index 4883a0841..7d3cc8578 100644 --- a/core/sync/futex_openbsd.odin +++ b/core/sync/futex_openbsd.odin @@ -1,5 +1,5 @@ -//+private -//+build openbsd +#+private +#+build openbsd package sync import "core:c" @@ -36,7 +36,7 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool { return false } - _panic("futex_wait failure") + panic_contextless("futex_wait failure") } _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool { @@ -62,14 +62,14 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati return false } - _panic("futex_wait_with_timeout failure") + panic_contextless("futex_wait_with_timeout failure") } _futex_signal :: proc "contextless" (f: ^Futex) { res := _unix_futex(f, FUTEX_WAKE_PRIVATE, 1, nil) if res == -1 { - _panic("futex_wake_single failure") + panic_contextless("futex_wake_single failure") } } @@ -77,6 +77,6 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) { res := _unix_futex(f, FUTEX_WAKE_PRIVATE, u32(max(i32)), nil) if res == -1 { - _panic("_futex_wake_all failure") + panic_contextless("_futex_wake_all failure") } } diff --git a/core/sync/futex_wasm.odin b/core/sync/futex_wasm.odin index de88e8198..0f9659a02 100644 --- a/core/sync/futex_wasm.odin +++ b/core/sync/futex_wasm.odin @@ -1,5 +1,5 @@ -//+private -//+build wasm32, wasm64p32 +#+private +#+build wasm32, wasm64p32 package sync import "base:intrinsics" @@ -10,7 +10,7 @@ import "core:time" _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool { when !intrinsics.has_target_feature("atomics") { - _panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") + panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") } else { s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1) return s != 0 @@ -19,7 +19,7 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool { _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool { when !intrinsics.has_target_feature("atomics") { - _panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") + panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") } else { s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, i64(duration)) return s != 0 @@ -28,7 +28,7 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati _futex_signal :: proc "contextless" (f: ^Futex) { when !intrinsics.has_target_feature("atomics") { - _panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") + panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") } else { loop: for { s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1) @@ -41,7 +41,7 @@ _futex_signal :: proc "contextless" (f: ^Futex) { _futex_broadcast :: proc "contextless" (f: ^Futex) { when !intrinsics.has_target_feature("atomics") { - _panic("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") + panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") } else { loop: for { s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), ~u32(0)) diff --git a/core/sync/futex_windows.odin b/core/sync/futex_windows.odin index 6a26baf5b..bb9686a1a 100644 --- a/core/sync/futex_windows.odin +++ b/core/sync/futex_windows.odin @@ -1,5 +1,5 @@ -//+private -//+build windows +#+private +#+build windows package sync import "core:time" diff --git a/core/sync/primitives.odin b/core/sync/primitives.odin index a22824481..f091de045 100644 --- a/core/sync/primitives.odin +++ b/core/sync/primitives.odin @@ -1,6 +1,5 @@ package sync -import "base:runtime" import "core:time" /* @@ -390,7 +389,7 @@ recursive_mutex_guard :: proc "contextless" (m: ^Recursive_Mutex) -> bool { A condition variable. `Cond` implements a condition variable, a rendezvous point for threads waiting -for signalling the occurence of an event. Condition variables are used on +for signalling the occurence of an event. Condition variables are used in conjuction with mutexes to provide a shared access to one or more shared variable. @@ -560,7 +559,7 @@ futex_wait :: proc "contextless" (f: ^Futex, expected: u32) { return } ok := _futex_wait(f, expected) - _assert(ok, "futex_wait failure") + assert_contextless(ok, "futex_wait failure") } /* @@ -597,18 +596,3 @@ Wake up multiple threads waiting on a futex. futex_broadcast :: proc "contextless" (f: ^Futex) { _futex_broadcast(f) } - - -@(private) -_assert :: proc "contextless" (cond: bool, msg: string) { - if !cond { - _panic(msg) - } -} - -@(private) -_panic :: proc "contextless" (msg: string) -> ! { - runtime.print_string(msg) - runtime.print_byte('\n') - runtime.trap() -} diff --git a/core/sync/primitives_atomic.odin b/core/sync/primitives_atomic.odin index 2cf25ac11..3c4324eb7 100644 --- a/core/sync/primitives_atomic.odin +++ b/core/sync/primitives_atomic.odin @@ -240,7 +240,7 @@ atomic_recursive_mutex_lock :: proc "contextless" (m: ^Atomic_Recursive_Mutex) { atomic_recursive_mutex_unlock :: proc "contextless" (m: ^Atomic_Recursive_Mutex) { tid := current_thread_id() - _assert(tid == m.owner, "tid != m.owner") + assert_contextless(tid == m.owner, "tid != m.owner") m.recursion -= 1 recursion := m.recursion if recursion == 0 { @@ -338,7 +338,7 @@ atomic_sema_wait :: proc "contextless" (s: ^Atomic_Sema) { original_count := atomic_load_explicit(&s.count, .Relaxed) for original_count == 0 { futex_wait(&s.count, u32(original_count)) - original_count = s.count + original_count = atomic_load_explicit(&s.count, .Relaxed) } if original_count == atomic_compare_exchange_strong_explicit(&s.count, original_count, original_count-1, .Acquire, .Acquire) { return @@ -361,7 +361,7 @@ atomic_sema_wait_with_timeout :: proc "contextless" (s: ^Atomic_Sema, duration: if !futex_wait_with_timeout(&s.count, u32(original_count), remaining) { return false } - original_count = s.count + original_count = atomic_load_explicit(&s.count, .Relaxed) } if original_count == atomic_compare_exchange_strong_explicit(&s.count, original_count, original_count-1, .Acquire, .Acquire) { return true diff --git a/core/sync/primitives_darwin.odin b/core/sync/primitives_darwin.odin index 146f69e86..141cea744 100644 --- a/core/sync/primitives_darwin.odin +++ b/core/sync/primitives_darwin.odin @@ -1,5 +1,5 @@ -//+build darwin -//+private +#+build darwin +#+private package sync import "core:c" diff --git a/core/sync/primitives_freebsd.odin b/core/sync/primitives_freebsd.odin index 2d7cbf18d..fe6b11e72 100644 --- a/core/sync/primitives_freebsd.odin +++ b/core/sync/primitives_freebsd.odin @@ -1,5 +1,5 @@ -//+build freebsd -//+private +#+build freebsd +#+private package sync import "core:c" diff --git a/core/sync/primitives_haiku.odin b/core/sync/primitives_haiku.odin index 4b8f6b02d..69d005206 100644 --- a/core/sync/primitives_haiku.odin +++ b/core/sync/primitives_haiku.odin @@ -1,4 +1,4 @@ -//+private +#+private package sync import "core:sys/haiku" diff --git a/core/sync/primitives_internal.odin b/core/sync/primitives_internal.odin index 23483aef5..4478a77d2 100644 --- a/core/sync/primitives_internal.odin +++ b/core/sync/primitives_internal.odin @@ -1,4 +1,4 @@ -//+private +#+private package sync import "core:time" diff --git a/core/sync/primitives_linux.odin b/core/sync/primitives_linux.odin index aa7a8b4b2..bf04f8d99 100644 --- a/core/sync/primitives_linux.odin +++ b/core/sync/primitives_linux.odin @@ -1,5 +1,5 @@ -//+build linux -//+private +#+build linux +#+private package sync import "core:sys/linux" diff --git a/core/sync/primitives_netbsd.odin b/core/sync/primitives_netbsd.odin index 594f2ff5c..66da0745a 100644 --- a/core/sync/primitives_netbsd.odin +++ b/core/sync/primitives_netbsd.odin @@ -1,4 +1,4 @@ -//+private +#+private package sync foreign import libc "system:c" diff --git a/core/sync/primitives_openbsd.odin b/core/sync/primitives_openbsd.odin index ff3ff837f..1f6efd8f7 100644 --- a/core/sync/primitives_openbsd.odin +++ b/core/sync/primitives_openbsd.odin @@ -1,5 +1,5 @@ -//+build openbsd -//+private +#+build openbsd +#+private package sync foreign import libc "system:c" diff --git a/core/sync/primitives_wasm.odin b/core/sync/primitives_wasm.odin index f8d9ab657..8906d96be 100644 --- a/core/sync/primitives_wasm.odin +++ b/core/sync/primitives_wasm.odin @@ -1,5 +1,5 @@ -//+private -//+build wasm32, wasm64p32 +#+private +#+build wasm32, wasm64p32 package sync _current_thread_id :: proc "contextless" () -> int { diff --git a/core/sync/primitives_windows.odin b/core/sync/primitives_windows.odin index 9f5bfc280..744bc248b 100644 --- a/core/sync/primitives_windows.odin +++ b/core/sync/primitives_windows.odin @@ -1,5 +1,5 @@ -//+build windows -//+private +#+build windows +#+private package sync import "core:time" diff --git a/core/sys/darwin/CoreFoundation/CFString.odin b/core/sys/darwin/CoreFoundation/CFString.odin index 6ad3c5bfc..24485a494 100644 --- a/core/sys/darwin/CoreFoundation/CFString.odin +++ b/core/sys/darwin/CoreFoundation/CFString.odin @@ -1,7 +1,5 @@ package CoreFoundation -import "base:runtime" - foreign import CoreFoundation "system:CoreFoundation.framework" String :: distinct TypeRef // same as CFStringRef @@ -9,157 +7,157 @@ String :: distinct TypeRef // same as CFStringRef StringEncoding :: distinct u32 StringBuiltInEncodings :: enum StringEncoding { - MacRoman = 0, + MacRoman = 0, WindowsLatin1 = 0x0500, - ISOLatin1 = 0x0201, + ISOLatin1 = 0x0201, NextStepLatin = 0x0B01, - ASCII = 0x0600, - Unicode = 0x0100, - UTF8 = 0x08000100, + ASCII = 0x0600, + Unicode = 0x0100, + UTF8 = 0x08000100, NonLossyASCII = 0x0BFF, - UTF16 = 0x0100, + UTF16 = 0x0100, UTF16BE = 0x10000100, UTF16LE = 0x14000100, - UTF32 = 0x0c000100, - UTF32BE = 0x18000100, - UTF32LE = 0x1c000100, + UTF32 = 0x0c000100, + UTF32BE = 0x18000100, + UTF32LE = 0x1c000100, } StringEncodings :: enum Index { - MacJapanese = 1, - MacChineseTrad = 2, - MacKorean = 3, - MacArabic = 4, - MacHebrew = 5, - MacGreek = 6, - MacCyrillic = 7, - MacDevanagari = 9, - MacGurmukhi = 10, - MacGujarati = 11, - MacOriya = 12, - MacBengali = 13, - MacTamil = 14, - MacTelugu = 15, - MacKannada = 16, - MacMalayalam = 17, - MacSinhalese = 18, - MacBurmese = 19, - MacKhmer = 20, - MacThai = 21, - MacLaotian = 22, - MacGeorgian = 23, - MacArmenian = 24, - MacChineseSimp = 25, - MacTibetan = 26, - MacMongolian = 27, - MacEthiopic = 28, - MacCentralEurRoman = 29, - MacVietnamese = 30, - MacExtArabic = 31, - MacSymbol = 33, - MacDingbats = 34, - MacTurkish = 35, - MacCroatian = 36, - MacIcelandic = 37, - MacRomanian = 38, - MacCeltic = 39, - MacGaelic = 40, - MacFarsi = 0x8C, - MacUkrainian = 0x98, - MacInuit = 0xEC, - MacVT100 = 0xFC, - MacHFS = 0xFF, - ISOLatin2 = 0x0202, - ISOLatin3 = 0x0203, - ISOLatin4 = 0x0204, - ISOLatinCyrillic = 0x0205, - ISOLatinArabic = 0x0206, - ISOLatinGreek = 0x0207, - ISOLatinHebrew = 0x0208, - ISOLatin5 = 0x0209, - ISOLatin6 = 0x020A, - ISOLatinThai = 0x020B, - ISOLatin7 = 0x020D, - ISOLatin8 = 0x020E, - ISOLatin9 = 0x020F, - ISOLatin10 = 0x0210, - DOSLatinUS = 0x0400, - DOSGreek = 0x0405, - DOSBalticRim = 0x0406, - DOSLatin1 = 0x0410, - DOSGreek1 = 0x0411, - DOSLatin2 = 0x0412, - DOSCyrillic = 0x0413, - DOSTurkish = 0x0414, - DOSPortuguese = 0x0415, - DOSIcelandic = 0x0416, - DOSHebrew = 0x0417, - DOSCanadianFrench = 0x0418, - DOSArabic = 0x0419, - DOSNordic = 0x041A, - DOSRussian = 0x041B, - DOSGreek2 = 0x041C, - DOSThai = 0x041D, - DOSJapanese = 0x0420, - DOSChineseSimplif = 0x0421, - DOSKorean = 0x0422, - DOSChineseTrad = 0x0423, - WindowsLatin2 = 0x0501, - WindowsCyrillic = 0x0502, - WindowsGreek = 0x0503, - WindowsLatin5 = 0x0504, - WindowsHebrew = 0x0505, - WindowsArabic = 0x0506, - WindowsBalticRim = 0x0507, - WindowsVietnamese = 0x0508, - WindowsKoreanJohab = 0x0510, - ANSEL = 0x0601, - JIS_X0201_76 = 0x0620, - JIS_X0208_83 = 0x0621, - JIS_X0208_90 = 0x0622, - JIS_X0212_90 = 0x0623, - JIS_C6226_78 = 0x0624, - ShiftJIS_X0213 = 0x0628, - ShiftJIS_X0213_MenKuTen = 0x0629, - GB_2312_80 = 0x0630, - GBK_95 = 0x0631, - GB_18030_2000 = 0x0632, - KSC_5601_87 = 0x0640, - KSC_5601_92_Johab = 0x0641, - CNS_11643_92_P1 = 0x0651, - CNS_11643_92_P2 = 0x0652, - CNS_11643_92_P3 = 0x0653, - ISO_2022_JP = 0x0820, - ISO_2022_JP_2 = 0x0821, - ISO_2022_JP_1 = 0x0822, - ISO_2022_JP_3 = 0x0823, - ISO_2022_CN = 0x0830, - ISO_2022_CN_EXT = 0x0831, - ISO_2022_KR = 0x0840, - EUC_JP = 0x0920, - EUC_CN = 0x0930, - EUC_TW = 0x0931, - EUC_KR = 0x0940, - ShiftJIS = 0x0A01, - KOI8_R = 0x0A02, - Big5 = 0x0A03, - MacRomanLatin1 = 0x0A04, - HZ_GB_2312 = 0x0A05, - Big5_HKSCS_1999 = 0x0A06, - VISCII = 0x0A07, - KOI8_U = 0x0A08, - Big5_E = 0x0A09, - NextStepJapanese = 0x0B02, - EBCDIC_US = 0x0C01, - EBCDIC_CP037 = 0x0C02, - UTF7 = 0x04000100, - UTF7_IMAP = 0x0A10, - ShiftJIS_X0213_00 = 0x0628, // Deprecated. Use `ShiftJIS_X0213` instead. + MacJapanese = 1, + MacChineseTrad = 2, + MacKorean = 3, + MacArabic = 4, + MacHebrew = 5, + MacGreek = 6, + MacCyrillic = 7, + MacDevanagari = 9, + MacGurmukhi = 10, + MacGujarati = 11, + MacOriya = 12, + MacBengali = 13, + MacTamil = 14, + MacTelugu = 15, + MacKannada = 16, + MacMalayalam = 17, + MacSinhalese = 18, + MacBurmese = 19, + MacKhmer = 20, + MacThai = 21, + MacLaotian = 22, + MacGeorgian = 23, + MacArmenian = 24, + MacChineseSimp = 25, + MacTibetan = 26, + MacMongolian = 27, + MacEthiopic = 28, + MacCentralEurRoman = 29, + MacVietnamese = 30, + MacExtArabic = 31, + MacSymbol = 33, + MacDingbats = 34, + MacTurkish = 35, + MacCroatian = 36, + MacIcelandic = 37, + MacRomanian = 38, + MacCeltic = 39, + MacGaelic = 40, + MacFarsi = 0x8C, + MacUkrainian = 0x98, + MacInuit = 0xEC, + MacVT100 = 0xFC, + MacHFS = 0xFF, + ISOLatin2 = 0x0202, + ISOLatin3 = 0x0203, + ISOLatin4 = 0x0204, + ISOLatinCyrillic = 0x0205, + ISOLatinArabic = 0x0206, + ISOLatinGreek = 0x0207, + ISOLatinHebrew = 0x0208, + ISOLatin5 = 0x0209, + ISOLatin6 = 0x020A, + ISOLatinThai = 0x020B, + ISOLatin7 = 0x020D, + ISOLatin8 = 0x020E, + ISOLatin9 = 0x020F, + ISOLatin10 = 0x0210, + DOSLatinUS = 0x0400, + DOSGreek = 0x0405, + DOSBalticRim = 0x0406, + DOSLatin1 = 0x0410, + DOSGreek1 = 0x0411, + DOSLatin2 = 0x0412, + DOSCyrillic = 0x0413, + DOSTurkish = 0x0414, + DOSPortuguese = 0x0415, + DOSIcelandic = 0x0416, + DOSHebrew = 0x0417, + DOSCanadianFrench = 0x0418, + DOSArabic = 0x0419, + DOSNordic = 0x041A, + DOSRussian = 0x041B, + DOSGreek2 = 0x041C, + DOSThai = 0x041D, + DOSJapanese = 0x0420, + DOSChineseSimplif = 0x0421, + DOSKorean = 0x0422, + DOSChineseTrad = 0x0423, + WindowsLatin2 = 0x0501, + WindowsCyrillic = 0x0502, + WindowsGreek = 0x0503, + WindowsLatin5 = 0x0504, + WindowsHebrew = 0x0505, + WindowsArabic = 0x0506, + WindowsBalticRim = 0x0507, + WindowsVietnamese = 0x0508, + WindowsKoreanJohab = 0x0510, + ANSEL = 0x0601, + JIS_X0201_76 = 0x0620, + JIS_X0208_83 = 0x0621, + JIS_X0208_90 = 0x0622, + JIS_X0212_90 = 0x0623, + JIS_C6226_78 = 0x0624, + ShiftJIS_X0213 = 0x0628, + ShiftJIS_X0213_MenKuTen = 0x0629, + GB_2312_80 = 0x0630, + GBK_95 = 0x0631, + GB_18030_2000 = 0x0632, + KSC_5601_87 = 0x0640, + KSC_5601_92_Johab = 0x0641, + CNS_11643_92_P1 = 0x0651, + CNS_11643_92_P2 = 0x0652, + CNS_11643_92_P3 = 0x0653, + ISO_2022_JP = 0x0820, + ISO_2022_JP_2 = 0x0821, + ISO_2022_JP_1 = 0x0822, + ISO_2022_JP_3 = 0x0823, + ISO_2022_CN = 0x0830, + ISO_2022_CN_EXT = 0x0831, + ISO_2022_KR = 0x0840, + EUC_JP = 0x0920, + EUC_CN = 0x0930, + EUC_TW = 0x0931, + EUC_KR = 0x0940, + ShiftJIS = 0x0A01, + KOI8_R = 0x0A02, + Big5 = 0x0A03, + MacRomanLatin1 = 0x0A04, + HZ_GB_2312 = 0x0A05, + Big5_HKSCS_1999 = 0x0A06, + VISCII = 0x0A07, + KOI8_U = 0x0A08, + Big5_E = 0x0A09, + NextStepJapanese = 0x0B02, + EBCDIC_US = 0x0C01, + EBCDIC_CP037 = 0x0C02, + UTF7 = 0x04000100, + UTF7_IMAP = 0x0A10, + ShiftJIS_X0213_00 = 0x0628, // Deprecated. Use `ShiftJIS_X0213` instead. } -@(link_prefix = "CF", default_calling_convention = "c") +@(link_prefix="CF", default_calling_convention="c") foreign CoreFoundation { // Copies the character contents of a string to a local C string buffer after converting the characters to a given encoding. StringGetCString :: proc(theString: String, buffer: [^]byte, bufferSize: Index, encoding: StringEncoding) -> b8 --- @@ -181,23 +179,16 @@ foreign CoreFoundation { STR :: StringMakeConstantString -StringCopyToOdinString :: proc( - theString: String, - allocator := context.allocator, -) -> ( - str: string, - ok: bool, -) #optional_ok { +StringCopyToOdinString :: proc(theString: String, allocator := context.allocator) -> (str: string, ok: bool) #optional_ok { length := StringGetLength(theString) max := StringGetMaximumSizeForEncoding(length, StringEncoding(StringBuiltInEncodings.UTF8)) buf, err := make([]byte, max, allocator) - if err != nil { return } - - raw_str := runtime.Raw_String { - data = raw_data(buf), + if err != nil { + return } - StringGetBytes(theString, {0, length}, StringEncoding(StringBuiltInEncodings.UTF8), 0, false, raw_data(buf), max, (^Index)(&raw_str.len)) - return transmute(string)raw_str, true + n: Index + StringGetBytes(theString, {0, length}, StringEncoding(StringBuiltInEncodings.UTF8), 0, false, raw_data(buf), Index(len(buf)), &n) + return string(buf[:n]), true } diff --git a/core/sys/darwin/Foundation/NSApplication.odin b/core/sys/darwin/Foundation/NSApplication.odin index 482221cdf..7191f6d07 100644 --- a/core/sys/darwin/Foundation/NSApplication.odin +++ b/core/sys/darwin/Foundation/NSApplication.odin @@ -79,7 +79,10 @@ Application_setActivationPolicy :: proc "c" (self: ^Application, activationPolic return msgSend(BOOL, self, "setActivationPolicy:", activationPolicy) } -@(deprecated="Use NSApplication method activate instead.") +// NOTE: this is technically deprecated but still actively used (Sokol, glfw, SDL, etc.) +// and has no clear alternative although `activate` is what Apple tells you to use, +// that does not work the same way. +// @(deprecated="Use NSApplication method activate instead.") @(objc_type=Application, objc_name="activateIgnoringOtherApps") Application_activateIgnoringOtherApps :: proc "c" (self: ^Application, ignoreOtherApps: BOOL) { msgSend(nil, self, "activateIgnoringOtherApps:", ignoreOtherApps) diff --git a/core/sys/darwin/Foundation/NSEvent.odin b/core/sys/darwin/Foundation/NSEvent.odin index f20afd3ab..548c5c172 100644 --- a/core/sys/darwin/Foundation/NSEvent.odin +++ b/core/sys/darwin/Foundation/NSEvent.odin @@ -5,8 +5,8 @@ Event :: struct {using _: Object} -EventMask :: distinct bit_set[EventType; UInteger] -EventMaskAny :: ~EventMask{} +EventMask :: distinct bit_set[EventType; UInteger] +EventMaskAny :: transmute(EventMask)(max(UInteger)) when size_of(UInteger) == 4 { // We don't support a 32-bit darwin system but this is mostly to shut up the type checker for the time being diff --git a/core/sys/darwin/Foundation/NSString.odin b/core/sys/darwin/Foundation/NSString.odin index b4918b3fb..a10b33fc0 100644 --- a/core/sys/darwin/Foundation/NSString.odin +++ b/core/sys/darwin/Foundation/NSString.odin @@ -58,7 +58,10 @@ MakeConstantString :: proc "c" (#const c: cstring) -> ^String { @(link_prefix="NS", default_calling_convention="c") foreign Foundation { - StringFromClass :: proc(cls: Class) -> ^String --- + StringFromClass :: proc(cls: Class) -> ^String --- + ClassFromString :: proc(str: ^String) -> Class --- + StringFromSelector :: proc(selector: SEL) -> ^String --- + SelectorFromString :: proc(str: ^String) -> SEL --- } @(objc_type=String, objc_name="alloc", objc_is_class_method=true) diff --git a/core/sys/darwin/Foundation/objc.odin b/core/sys/darwin/Foundation/objc.odin index 51cfee444..82d6199ce 100644 --- a/core/sys/darwin/Foundation/objc.odin +++ b/core/sys/darwin/Foundation/objc.odin @@ -9,24 +9,85 @@ import "core:c" IMP :: proc "c" (object: id, sel: SEL, #c_vararg args: ..any) -> id +@(default_calling_convention="c") foreign Foundation { - objc_getMetaClass :: proc "c" (name: cstring) -> id --- - objc_lookUpClass :: proc "c" (name: cstring) -> Class --- - objc_allocateClassPair :: proc "c" (superclass : Class, name : cstring, extraBytes : c.size_t) -> Class --- - objc_registerClassPair :: proc "c" (cls : Class) --- - - sel_registerName :: proc "c" (name: cstring) -> SEL --- - - class_addMethod :: proc "c" (cls: Class, name: SEL, imp: IMP, types: cstring) -> BOOL --- - class_getInstanceMethod :: proc "c" (cls: Class, name: SEL) -> Method --- - class_createInstance :: proc "c" (cls: Class, extraBytes: c.size_t) -> id --- - - method_setImplementation :: proc "c" (method: Method, imp: IMP) --- - - object_getClass :: proc "c" (obj: id) -> Class --- - object_setClass :: proc "c" (obj: id, cls: Class) -> Class --- - object_getClassName :: proc "c" (obj: id) -> cstring --- - object_getIndexedIvars :: proc "c" (obj: id) -> rawptr --- + objc_getMetaClass :: proc(name: cstring) -> id --- + objc_lookUpClass :: proc(name: cstring) -> Class --- + objc_allocateClassPair :: proc(superclass: Class, name: cstring, extraBytes: c.size_t) -> Class --- + objc_registerClassPair :: proc(cls: Class) --- + objc_disposeClassPair :: proc(cls: Class) --- + objc_duplicateClass :: proc(original: Class, name: cstring, extraBytes: c.size_t) -> Class --- + objc_getProtocol :: proc(name: cstring) -> ^Protocol --- + objc_copyProtocolList :: proc(outCount: ^uint) -> [^]^Protocol --- + objc_constructInstance :: proc(cls: Class, bytes: rawptr) -> id --- + objc_destructInstance :: proc(obj: id) -> rawptr --- + objc_getClassList :: proc(buffer: [^]Class, bufferCount: int) -> int --- + objc_copyClassList :: proc(outCount: ^uint) -> [^]Class --- + objc_getRequiredClass :: proc(name: cstring) -> Class --- + objc_setAssociatedObject :: proc(object: id, key: rawptr, value: id, policy: objc_AssociationPolicy) --- + objc_getAssociatedObject :: proc(object: id, key: rawptr) -> id --- + objc_removeAssociatedObjects :: proc(object: id) --- + + sel_registerName :: proc(name: cstring) -> SEL --- + sel_getName :: proc(sel: SEL) -> cstring --- + sel_isEqual :: proc(lhs, rhs: SEL) -> BOOL --- + + class_addMethod :: proc(cls: Class, name: SEL, imp: IMP, types: cstring) -> BOOL --- + class_getInstanceMethod :: proc(cls: Class, name: SEL) -> Method --- + class_getClassMethod :: proc(cls: Class, name: SEL) -> Method --- + class_copyMethodList :: proc(cls: Class, outCount: ^uint) -> [^]Method --- + class_createInstance :: proc(cls: Class, extraBytes: c.size_t) -> id --- + class_replaceMethod :: proc(cls: Class, name: SEL, imp: IMP, types: cstring) -> IMP --- + class_getMethodImplementation :: proc(cls: Class, name: SEL) -> IMP --- + class_getSuperclass :: proc(cls: Class) -> Class --- + class_getName :: proc(cls: Class) -> cstring --- + class_isMetaClass :: proc(cls: Class) -> BOOL --- + class_addProtocol :: proc(cls: Class, protocol: ^Protocol) -> BOOL --- + class_getVersion :: proc(cls: Class) -> c.int --- + class_setVersion :: proc(cls: Class, version: c.int) --- + class_getProperty :: proc(cls: Class, name: cstring) -> objc_property_t --- + class_addProperty :: proc(cls: Class, name: cstring, attributes: [^]objc_property_attribute_t, attributeCount: uint) -> BOOL --- + class_replaceProperty :: proc(cls: Class, name: cstring, attributes: [^]objc_property_attribute_t, attributeCount: uint) --- + class_copyPropertyList :: proc(cls: Class, outCount: ^uint) -> [^]objc_property_t --- + class_conformsToProtocol :: proc(cls: Class, protocol: ^Protocol) -> BOOL --- + class_copyProtocolList :: proc(cls: Class, outCount: ^uint) -> [^]^Protocol --- + class_respondsToSelector :: proc(cls: Class, sel: SEL) -> BOOL --- + class_getClassVariable :: proc(cls: Class, name: cstring) -> Ivar --- + class_getInstanceVariable :: proc(cls: Class, name: cstring) -> Ivar --- + class_addIvar :: proc(cls: Class, name: cstring, size: c.size_t, alignment: u8, types: cstring) -> BOOL --- + class_copyIvarList :: proc(cls: Class, outCount: ^uint) -> [^]Ivar --- + class_getInstanceSize :: proc(cls: Class) -> c.size_t --- + + property_getName :: proc(property: objc_property_t) -> cstring --- + property_getAttributes :: proc(property: objc_property_t) -> cstring --- + property_copyAttributeList :: proc(property: objc_property_t, outCount: ^uint) -> [^]objc_property_attribute_t --- + property_copyAttributeValue :: proc(property: objc_property_t, attributeName: cstring) -> cstring --- + + protocol_conformsToProtocol :: proc(proto: ^Protocol, other: ^Protocol) -> BOOL --- + protocol_isEqual :: proc(proto: ^Protocol, other: ^Protocol) -> BOOL --- + protocol_getName :: proc(proto: ^Protocol) -> cstring --- + + method_getImplementation :: proc(m: Method) -> IMP --- + method_setImplementation :: proc(m: Method, imp: IMP) --- + method_copyArgumentType :: proc(m: Method, index: uint) -> cstring --- + method_getReturnType :: proc(m: Method, dst: cstring, dst_len: c.size_t) --- + method_getNumberOfArguments :: proc(m: Method) -> uint --- + method_getArgumentType :: proc(m: Method, index: uint, dst: cstring, dst_len: c.size_t) --- + + object_getClass :: proc(obj: id) -> Class --- + object_setClass :: proc(obj: id, cls: Class) -> Class --- + object_copy :: proc(obj: id, size: c.size_t) -> id --- + object_dispose :: proc(obj: id) -> id --- + object_getClassName :: proc(obj: id) -> cstring --- + object_getIndexedIvars :: proc(obj: id) -> rawptr --- + object_getInstanceVariable :: proc(obj: id, name: cstring, outValue: rawptr) -> Ivar --- + object_setInstanceVariable :: proc(obj: id, name: cstring, value: rawptr) -> Ivar --- + object_getIvar :: proc(obj: id, ivar: Ivar) -> id --- + object_setIvar :: proc(obj: id, ivar: Ivar, value: id) --- + + ivar_getName :: proc(v: Ivar) -> cstring --- + ivar_getTypeEncoding :: proc(v: Ivar) -> cstring --- + ivar_getOffset :: proc(v: Ivar) -> c.ptrdiff_t --- } @@ -47,7 +108,17 @@ objc_method :: struct { } objc_method_list :: struct {} +objc_property :: struct{} +objc_property_t :: ^objc_property + +objc_property_attribute_t :: struct { + name: cstring, + value: cstring, +} + objc_ivar :: struct {} +Ivar :: ^objc_ivar + objc_ivar_list :: struct {} objc_cache :: struct { @@ -85,3 +156,11 @@ objc_class_internals :: struct { protocols: rawptr, } + +objc_AssociationPolicy :: enum c.uintptr_t { + Assign = 0, + Retain_Nonatomic = 1, + Copy_Nonatomic = 3, + Retain = 01401, + Copy = 01403, +} diff --git a/core/sys/darwin/darwin.odin b/core/sys/darwin/darwin.odin index ddd25a76c..d109f5544 100644 --- a/core/sys/darwin/darwin.odin +++ b/core/sys/darwin/darwin.odin @@ -1,4 +1,4 @@ -//+build darwin +#+build darwin package darwin import "core:c" diff --git a/core/sys/darwin/sync.odin b/core/sys/darwin/sync.odin index 121d3edef..58fc7c9e4 100644 --- a/core/sys/darwin/sync.odin +++ b/core/sys/darwin/sync.odin @@ -5,6 +5,7 @@ foreign import system "system:System.framework" // #define OS_WAIT_ON_ADDR_AVAILABILITY \ // __API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4)) when ODIN_OS == .Darwin { + when ODIN_PLATFORM_SUBTARGET == .iOS && ODIN_MINIMUM_OS_VERSION >= 17_04_00 { WAIT_ON_ADDRESS_AVAILABLE :: true } else when ODIN_MINIMUM_OS_VERSION >= 14_04_00 { @@ -12,8 +13,18 @@ when ODIN_OS == .Darwin { } else { WAIT_ON_ADDRESS_AVAILABLE :: false } + + when ODIN_PLATFORM_SUBTARGET == .iOS && ODIN_MINIMUM_OS_VERSION >= 14_00_00 { + ULOCK_WAIT_2_AVAILABLE :: true + } else when ODIN_MINIMUM_OS_VERSION >= 11_00_00 { + ULOCK_WAIT_2_AVAILABLE :: true + } else { + ULOCK_WAIT_2_AVAILABLE :: false + } + } else { WAIT_ON_ADDRESS_AVAILABLE :: false + ULOCK_WAIT_2_AVAILABLE :: false } os_sync_wait_on_address_flag :: enum u32 { diff --git a/core/sys/darwin/xnu_system_call_helpers.odin b/core/sys/darwin/xnu_system_call_helpers.odin index a641a4be9..ae8373f99 100644 --- a/core/sys/darwin/xnu_system_call_helpers.odin +++ b/core/sys/darwin/xnu_system_call_helpers.odin @@ -15,9 +15,9 @@ sys_write_string :: proc (fd: c.int, message: string) -> bool { Offset_From :: enum c.int { SEEK_SET = 0, // the offset is set to offset bytes. SEEK_CUR = 1, // the offset is set to its current location plus offset bytes. - SEEK_END = 2, // the offset is set to the size of the file plus offset bytes. - SEEK_HOLE = 3, // the offset is set to the start of the next hole greater than or equal to the supplied offset. - SEEK_DATA = 4, // the offset is set to the start of the next non-hole file region greater than or equal to the supplied offset. + SEEK_END = 2, // the offset is set to the size of the file plus offset bytes. + SEEK_HOLE = 3, // the offset is set to the start of the next hole greater than or equal to the supplied offset. + SEEK_DATA = 4, // the offset is set to the start of the next non-hole file region greater than or equal to the supplied offset. } Open_Flags_Enum :: enum u8 { diff --git a/core/sys/darwin/xnu_system_call_wrappers.odin b/core/sys/darwin/xnu_system_call_wrappers.odin index 30faf86be..1188091a9 100644 --- a/core/sys/darwin/xnu_system_call_wrappers.odin +++ b/core/sys/darwin/xnu_system_call_wrappers.odin @@ -192,43 +192,43 @@ _STRUCT_TIMEVAL :: struct { /* pwd.h */ _Password_Entry :: struct { - pw_name: cstring, /* username */ - pw_passwd: cstring, /* user password */ - pw_uid: i32, /* user ID */ - pw_gid: i32, /* group ID */ + pw_name: cstring, /* username */ + pw_passwd: cstring, /* user password */ + pw_uid: i32, /* user ID */ + pw_gid: i32, /* group ID */ pw_change: u64, /* password change time */ pw_class: cstring, /* user access class */ - pw_gecos: cstring, /* full user name */ - pw_dir: cstring, /* home directory */ - pw_shell: cstring, /* shell program */ + pw_gecos: cstring, /* full user name */ + pw_dir: cstring, /* home directory */ + pw_shell: cstring, /* shell program */ pw_expire: u64, /* account expiration */ pw_fields: i32, /* filled fields */ } /* processinfo.h */ _Proc_Bsdinfo :: struct { - pbi_flags: u32, /* if is 64bit; emulated etc */ - pbi_status: u32, - pbi_xstatus: u32, - pbi_pid: u32, - pbi_ppid: u32, - pbi_uid: u32, - pbi_gid: u32, - pbi_ruid: u32, - pbi_rgid: u32, - pbi_svuid: u32, - pbi_svgid: u32, - res: u32, - pbi_comm: [DARWIN_MAXCOMLEN]u8, - pbi_name: [2 * DARWIN_MAXCOMLEN]u8, /* empty if no name is registered */ - pbi_nfiles: u32, - pbi_pgid: u32, - pbi_pjobc: u32, - e_tdev: u32, /* controlling tty dev */ - e_tpgid: u32, /* tty process group id */ - pbi_nice: i32, - pbi_start_tvsec: u64, - pbi_start_tvusec: u64, + pbi_flags: u32, /* if is 64bit; emulated etc */ + pbi_status: u32, + pbi_xstatus: u32, + pbi_pid: u32, + pbi_ppid: u32, + pbi_uid: u32, + pbi_gid: u32, + pbi_ruid: u32, + pbi_rgid: u32, + pbi_svuid: u32, + pbi_svgid: u32, + res: u32, + pbi_comm: [DARWIN_MAXCOMLEN]u8, + pbi_name: [2 * DARWIN_MAXCOMLEN]u8, /* empty if no name is registered */ + pbi_nfiles: u32, + pbi_pgid: u32, + pbi_pjobc: u32, + e_tdev: u32, /* controlling tty dev */ + e_tpgid: u32, /* tty process group id */ + pbi_nice: i32, + pbi_start_tvsec: u64, + pbi_start_tvusec: u64, } /*--==========================================================================--*/ diff --git a/core/sys/freebsd/syscalls.odin b/core/sys/freebsd/syscalls.odin index 4a79bd56c..83b51138a 100644 --- a/core/sys/freebsd/syscalls.odin +++ b/core/sys/freebsd/syscalls.odin @@ -14,12 +14,16 @@ import "core:c" // FreeBSD 15 syscall numbers // See: https://alfonsosiciliano.gitlab.io/posts/2023-08-28-freebsd-15-system-calls.html +SYS_read : uintptr : 3 +SYS_write : uintptr : 4 SYS_open : uintptr : 5 SYS_close : uintptr : 6 SYS_getpid : uintptr : 20 SYS_recvfrom : uintptr : 29 SYS_accept : uintptr : 30 +SYS_getsockname: uintptr : 32 SYS_fcntl : uintptr : 92 +SYS_fsync : uintptr : 95 SYS_socket : uintptr : 97 SYS_connect : uintptr : 98 SYS_bind : uintptr : 104 @@ -29,12 +33,46 @@ SYS_shutdown : uintptr : 134 SYS_setsockopt : uintptr : 105 SYS_sysctl : uintptr : 202 SYS__umtx_op : uintptr : 454 +SYS_pread : uintptr : 475 +SYS_pwrite : uintptr : 476 SYS_accept4 : uintptr : 541 // // Odin syscall wrappers // +// Read input. +// +// The read() function appeared in Version 1 AT&T UNIX. +read :: proc "contextless" (fd: Fd, buf: []u8) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_read, + cast(uintptr)fd, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf)) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Write output. +// +// The write() function appeared in Version 1 AT&T UNIX. +write :: proc "contextless" (fd: Fd, buf: []u8) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_pwrite, + cast(uintptr)fd, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf)) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + // Open or create a file for reading, writing or executing. // // The open() function appeared in Version 1 AT&T UNIX. @@ -164,6 +202,36 @@ accept_nil :: proc "contextless" (s: Fd) -> (Fd, Errno) { accept :: proc { accept_T, accept_nil } +// Get socket name. +// +// The getsockname() system call appeared in 4.2BSD. +getsockname :: proc "contextless" (s: Fd, sockaddr: ^$T) -> Errno { + // sockaddr must contain a valid pointer, or this will segfault because + // we're telling the syscall that there's memory available to write to. + addrlen: socklen_t = size_of(T) + + result, ok := intrinsics.syscall_bsd(SYS_getsockname, + cast(uintptr)s, + cast(uintptr)sockaddr, + cast(uintptr)&addrlen) + + if !ok { + return cast(Errno)result + } + + return nil +} + +// Synchronize changes to a file. +// +// The fsync() system call appeared in 4.2BSD. +fsync :: proc "contextless" (fd: Fd) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_fsync, + cast(uintptr)fd) + + return cast(Errno)result +} + // File control. // // The fcntl() system call appeared in 4.2BSD. @@ -469,6 +537,46 @@ _umtx_op :: proc "contextless" (obj: rawptr, op: Userland_Mutex_Operation, val: return cast(Errno)result } +// Read input without modifying the file pointer. +// +// The pread() function appeared in AT&T System V Release 4 UNIX. +pread :: proc "contextless" (fd: Fd, buf: []u8, offset: off_t) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_pread, + cast(uintptr)fd, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf), + cast(uintptr)offset) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Write output without modifying the file pointer. +// +// The pwrite() function appeared in AT&T System V Release 4 UNIX. +// +// BUGS +// +// The pwrite() system call appends the file without changing the file +// offset if O_APPEND is set, contrary to IEEE Std 1003.1-2008 (“POSIX.1”) +// where pwrite() writes into offset regardless of whether O_APPEND is set. +pwrite :: proc "contextless" (fd: Fd, buf: []u8, offset: off_t) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_pwrite, + cast(uintptr)fd, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf), + cast(uintptr)offset) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + // Accept a connection on a socket. // // The accept4() system call appeared in FreeBSD 10.0. diff --git a/core/sys/freebsd/types.odin b/core/sys/freebsd/types.odin index a13961a47..37e8abf68 100644 --- a/core/sys/freebsd/types.odin +++ b/core/sys/freebsd/types.odin @@ -695,12 +695,12 @@ Record_Lock_Flag :: enum c.int { // struct flock File_Lock :: struct { - start: off_t, /* starting offset */ - len: off_t, /* len = 0 means until end of file */ - pid: pid_t, /* lock owner */ - type: Record_Lock_Flag, /* lock type: read/write, etc. */ - whence: c.short, /* type of l_start */ - sysid: c.int, /* remote system id or zero for local */ + start: off_t, /* starting offset */ + len: off_t, /* len = 0 means until end of file */ + pid: pid_t, /* lock owner */ + type: Record_Lock_Flag, /* lock type: read/write, etc. */ + whence: c.short, /* type of l_start */ + sysid: c.int, /* remote system id or zero for local */ } /* diff --git a/core/sys/haiku/errors.odin b/core/sys/haiku/errors.odin index 023045001..febe647ea 100644 --- a/core/sys/haiku/errors.odin +++ b/core/sys/haiku/errors.odin @@ -1,4 +1,4 @@ -//+build haiku +#+build haiku package sys_haiku import "core:c" diff --git a/core/sys/haiku/find_directory.odin b/core/sys/haiku/find_directory.odin index 103e677d7..758c4dff4 100644 --- a/core/sys/haiku/find_directory.odin +++ b/core/sys/haiku/find_directory.odin @@ -1,4 +1,4 @@ -//+build haiku +#+build haiku package sys_haiku import "core:c" diff --git a/core/sys/haiku/os.odin b/core/sys/haiku/os.odin index 883072c2d..6ab3ef573 100644 --- a/core/sys/haiku/os.odin +++ b/core/sys/haiku/os.odin @@ -1,4 +1,4 @@ -//+build haiku +#+build haiku package sys_haiku import "core:c" diff --git a/core/sys/haiku/types.odin b/core/sys/haiku/types.odin index 0440d5a98..47755b0b7 100644 --- a/core/sys/haiku/types.odin +++ b/core/sys/haiku/types.odin @@ -1,4 +1,4 @@ -//+build haiku +#+build haiku package sys_haiku import "core:c" diff --git a/core/sys/info/cpu_arm.odin b/core/sys/info/cpu_arm.odin index aa4bb368a..960e55a56 100644 --- a/core/sys/info/cpu_arm.odin +++ b/core/sys/info/cpu_arm.odin @@ -1,4 +1,4 @@ -//+build arm32, arm64 +#+build arm32, arm64 package sysinfo import "core:sys/unix" diff --git a/core/sys/info/cpu_intel.odin b/core/sys/info/cpu_intel.odin index 73d4c15e7..d6fa98507 100644 --- a/core/sys/info/cpu_intel.odin +++ b/core/sys/info/cpu_intel.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package sysinfo import "base:intrinsics" diff --git a/core/sys/info/cpu_linux_arm.odin b/core/sys/info/cpu_linux_arm.odin index dcc252971..6408decb7 100644 --- a/core/sys/info/cpu_linux_arm.odin +++ b/core/sys/info/cpu_linux_arm.odin @@ -1,5 +1,5 @@ -//+build arm32, arm64 -//+build linux +#+build arm32, arm64 +#+build linux package sysinfo import "core:sys/linux" diff --git a/core/sys/info/cpu_linux_riscv64.odin b/core/sys/info/cpu_linux_riscv64.odin index 0f109e7ba..84f6134d4 100644 --- a/core/sys/info/cpu_linux_riscv64.odin +++ b/core/sys/info/cpu_linux_riscv64.odin @@ -1,5 +1,5 @@ -//+build riscv64 -//+build linux +#+build riscv64 +#+build linux package sysinfo import "base:intrinsics" @@ -8,35 +8,102 @@ import "core:sys/linux" @(init, private) init_cpu_features :: proc() { - fd, err := linux.open("/proc/self/auxv", {}) - if err != .NONE { return } - defer linux.close(fd) - - // This is probably enough right? - buf: [4096]byte - n, rerr := linux.read(fd, buf[:]) - if rerr != .NONE || n == 0 { return } - - ulong :: u64 - AT_HWCAP :: 16 - - // TODO: using these we could get more information than just the basics. - // AT_HWCAP2 :: 26 - // AT_HWCAP3 :: 29 - // AT_HWCAP4 :: 30 - - auxv := buf[:n] - for len(auxv) >= size_of(ulong)*2 { - key := intrinsics.unaligned_load((^ulong)(&auxv[0])) - val := intrinsics.unaligned_load((^ulong)(&auxv[size_of(ulong)])) - auxv = auxv[2*size_of(ulong):] - - if key != AT_HWCAP { - continue + _features: CPU_Features + defer cpu_features = _features + + HWCAP_Bits :: enum u64 { + I = 'I' - 'A', + M = 'M' - 'A', + A = 'A' - 'A', + F = 'F' - 'A', + D = 'D' - 'A', + C = 'C' - 'A', + V = 'V' - 'A', + } + HWCAP :: bit_set[HWCAP_Bits; u64] + + // Read HWCAP for base extensions, we can get this info through hwprobe too but that is Linux 6.4+ only. + { + fd, err := linux.open("/proc/self/auxv", {}) + if err != .NONE { return } + defer linux.close(fd) + + // This is probably enough right? + buf: [4096]byte + n, rerr := linux.read(fd, buf[:]) + if rerr != .NONE || n == 0 { return } + + ulong :: u64 + AT_HWCAP :: 16 + + auxv := buf[:n] + for len(auxv) >= size_of(ulong)*2 { + key := intrinsics.unaligned_load((^ulong)(&auxv[0])) + val := intrinsics.unaligned_load((^ulong)(&auxv[size_of(ulong)])) + auxv = auxv[2*size_of(ulong):] + + if key != AT_HWCAP { + continue + } + + cap := transmute(HWCAP)(val) + if .I in cap { + _features += { .I } + } + if .M in cap { + _features += { .M } + } + if .A in cap { + _features += { .A } + } + if .F in cap { + _features += { .F } + } + if .D in cap { + _features += { .D } + } + if .C in cap { + _features += { .C } + } + if .V in cap { + _features += { .V } + } + break } + } - cpu_features = transmute(CPU_Features)(val) - break + // hwprobe for other features. + { + pairs := []linux.RISCV_HWProbe{ + { key = .IMA_EXT_0 }, + { key = .CPUPERF_0 }, + { key = .MISALIGNED_SCALAR_PERF }, + } + err := linux.riscv_hwprobe(raw_data(pairs), len(pairs), 0, nil, {}) + if err != nil { + assert(err == .ENOSYS, "unexpected error from riscv_hwprobe()") + return + } + + assert(pairs[0].key == .IMA_EXT_0) + exts := pairs[0].value.ima_ext_0 + exts -= { .FD, .C, .V } + _features += transmute(CPU_Features)exts + + if pairs[2].key == .MISALIGNED_SCALAR_PERF { + if pairs[2].value.misaligned_scalar_perf == .FAST { + _features += { .Misaligned_Supported, .Misaligned_Fast } + } else if pairs[2].value.misaligned_scalar_perf != .UNSUPPORTED { + _features += { .Misaligned_Supported } + } + } else { + assert(pairs[1].key == .CPUPERF_0) + if .FAST in pairs[1].value.cpu_perf_0 { + _features += { .Misaligned_Supported, .Misaligned_Fast } + } else if .UNSUPPORTED not_in pairs[1].value.cpu_perf_0 { + _features += { .Misaligned_Supported } + } + } } } diff --git a/core/sys/info/cpu_riscv64.odin b/core/sys/info/cpu_riscv64.odin index 754110911..c3319c48c 100644 --- a/core/sys/info/cpu_riscv64.odin +++ b/core/sys/info/cpu_riscv64.odin @@ -1,13 +1,97 @@ package sysinfo CPU_Feature :: enum u64 { - I = 'I' - 'A', // Base features, don't think this is ever not here. - M = 'M' - 'A', // Integer multiplication and division, currently required by Odin. - A = 'A' - 'A', // Atomics. - F = 'F' - 'A', // Single precision floating point, currently required by Odin. - D = 'D' - 'A', // Double precision floating point, currently required by Odin. - C = 'C' - 'A', // Compressed instructions. - V = 'V' - 'A', // Vector operations. + // Bit-Manipulation ISA Extensions v1. + Zba = 3, + Zbb, + Zbs, + + // CMOs (ratified). + Zicboz, + + // Bit-Manipulation ISA Extensions v1. + Zbc, + + // Scalar Crypto ISA extensions v1. + Zbkb, + Zbkc, + Zbkx, + Zknd, + Zkne, + Zknh, + Zksed, + Zksh, + Zkt, + + // Cryptography Extensions Volume II v1. + Zvbb, + Zvbc, + Zvkb, + Zvkg, + Zvkned, + Zvknha, + Zvknhb, + Zvksed, + Zvksh, + Zvkt, + + // ISA Manual v1. + Zfh, + Zfhmin, + Zihintntl, + + // ISA manual (ratified). + Zvfh, + Zvfhmin, + Zfa, + Ztso, + + // Atomic Compare-and-Swap Instructions Manual (ratified). + Zacas, + Zicond, + + // ISA manual (ratified). + Zihintpause, + + // Vector Extensions Manual v1. + Zve32x, + Zve32f, + Zve64x, + Zve64f, + Zve64d, + + // ISA manual (ratified). + Zimop, + + // Code Size Reduction (ratified). + Zca, + Zcb, + Zcd, + Zcf, + + // ISA manual (ratified). + Zcmop, + Zawrs, + + // Base features, don't think this is ever not here. + I, + // Integer multiplication and division, currently required by Odin. + M, + // Atomics. + A, + // Single precision floating point, currently required by Odin. + F, + // Double precision floating point, currently required by Odin. + D, + // Compressed instructions. + C, + // Vector operations. + V, + + // Indicates Misaligned Scalar Loads will not trap the program. + Misaligned_Supported, + // Indicates Hardware Support for Misaligned Scalar Loads. + Misaligned_Fast, } CPU_Features :: distinct bit_set[CPU_Feature; u64] diff --git a/core/sys/info/doc.odin b/core/sys/info/doc.odin index 802cd9c60..b5cd62d81 100644 --- a/core/sys/info/doc.odin +++ b/core/sys/info/doc.odin @@ -2,6 +2,12 @@ Copyright 2022 Jeroen van Rijn <nom@duclavier.com>. Made available under Odin's BSD-3 license. +List of contributors: + Jeroen van Rijn: Initial implementation. + Laytan: ARM and RISC-V CPU feature detection. +*/ + +/* Package `core:sys/info` gathers system information on: Windows, Linux, macOS, FreeBSD & OpenBSD. @@ -11,9 +17,10 @@ and CPU information. On Windows, GPUs will also be enumerated using the registry. CPU feature flags can be tested against `cpu_features`, where applicable, e.g. -`if .aes in si.aes { ... }` +`if .aes in info.cpu_features.? { ... }` Example: + package main import "core:fmt" import si "core:sys/info" diff --git a/core/sys/info/platform_bsd.odin b/core/sys/info/platform_bsd.odin index e2273d253..6bb32cd3d 100644 --- a/core/sys/info/platform_bsd.odin +++ b/core/sys/info/platform_bsd.odin @@ -1,4 +1,4 @@ -//+build openbsd, netbsd +#+build openbsd, netbsd package sysinfo import sys "core:sys/unix" diff --git a/core/sys/info/platform_darwin.odin b/core/sys/info/platform_darwin.odin index 3fd857bfe..493f038f0 100644 --- a/core/sys/info/platform_darwin.odin +++ b/core/sys/info/platform_darwin.odin @@ -530,6 +530,10 @@ macos_release_map: map[string]Darwin_To_Release = { "23F79" = {{23, 5, 0}, "macOS", {"Sonoma", {14, 5, 0}}}, "23G80" = {{23, 6, 0}, "macOS", {"Sonoma", {14, 6, 0}}}, "23G93" = {{23, 6, 0}, "macOS", {"Sonoma", {14, 6, 1}}}, + "23H124" = {{23, 6, 0}, "macOS", {"Sonoma", {14, 7, 0}}}, + + // MacOS Sequoia + "24A335" = {{24, 0, 0}, "macOS", {"Sequoia", {15, 0, 0}}}, } @(private) diff --git a/core/sys/kqueue/kqueue.odin b/core/sys/kqueue/kqueue.odin index 27d1ecaae..56be1cf7a 100644 --- a/core/sys/kqueue/kqueue.odin +++ b/core/sys/kqueue/kqueue.odin @@ -1,4 +1,4 @@ -//+build darwin, netbsd, openbsd, freebsd +#+build darwin, netbsd, openbsd, freebsd package kqueue when ODIN_OS == .Darwin { diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin index f78891bc8..8a4a6dd7a 100644 --- a/core/sys/linux/bits.odin +++ b/core/sys/linux/bits.odin @@ -1839,3 +1839,87 @@ Execveat_Flags_Bits :: enum { AT_SYMLINK_NOFOLLOW = 8, AT_EMPTY_PATH = 12, } + +RISCV_HWProbe_Key :: enum i64 { + UNSUPPORTED = -1, + MVENDORID = 0, + MARCHID = 1, + MIMPID = 2, + BASE_BEHAVIOR = 3, + IMA_EXT_0 = 4, + // Deprecated, try `.MISALIGNED_SCALAR_PERF` first, if that is `.UNSUPPORTED`, use this. + CPUPERF_0 = 5, + ZICBOZ_BLOCK_SIZE = 6, + HIGHEST_VIRT_ADDRESS = 7, + TIME_CSR_FREQ = 8, + MISALIGNED_SCALAR_PERF = 9, +} + +RISCV_HWProbe_Flags_Bits :: enum { + WHICH_CPUS, +} + +RISCV_HWProbe_Base_Behavior_Bits :: enum { + IMA, +} + +RISCV_HWProbe_IMA_Ext_0_Bits :: enum { + FD, + C, + V, + EXT_ZBA, + EXT_ZBB, + EXT_ZBS, + EXT_ZICBOZ, + EXT_ZBC, + EXT_ZBKB, + EXT_ZBKC, + EXT_ZBKX, + EXT_ZKND, + EXT_ZKNE, + EXT_ZKNH, + EXT_ZKSED, + EXT_ZKSH, + EXT_ZKT, + EXT_ZVBB, + EXT_ZVBC, + EXT_ZVKB, + EXT_ZVKG, + EXT_ZVKNED, + EXT_ZVKNHA, + EXT_ZVKNHB, + EXT_ZVKSED, + EXT_ZVKSH, + EXT_ZVKT, + EXT_ZFH, + EXT_ZFHMIN, + EXT_ZIHINTNTL, + EXT_ZVFH, + EXT_ZVFHMIN, + EXT_ZFA, + EXT_ZTSO, + EXT_ZACAS, + EXT_ZICOND, + EXT_ZIHINTPAUSE, + EXT_ZVE32X, + EXT_ZVE32F, + EXT_ZVE64X, + EXT_ZVE64F, + EXT_ZVE64D, + EXT_ZIMOP, + EXT_ZCA, + EXT_ZCB, + EXT_ZCD, + EXT_ZCF, + EXT_ZCMOP, + EXT_ZAWRS, +} + +RISCV_HWProbe_Misaligned_Scalar_Perf :: enum { + UNKNOWN, + EMULATED, + SLOW, + FAST, + UNSUPPORTED, +} + diff --git a/core/sys/linux/helpers.odin b/core/sys/linux/helpers.odin index f1abbbf61..aefc1179e 100644 --- a/core/sys/linux/helpers.odin +++ b/core/sys/linux/helpers.odin @@ -1,5 +1,5 @@ -//+build linux -//+no-instrumentation +#+build linux +#+no-instrumentation package linux import "base:intrinsics" diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 6e4194be7..c5894d78b 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -1,4 +1,4 @@ -//+no-instrumentation +#+no-instrumentation package linux import "base:intrinsics" @@ -1443,8 +1443,9 @@ ptrace_traceme :: proc "contextless" (rq: PTrace_Traceme_Type) -> (Errno) { } ptrace_peek :: proc "contextless" (rq: PTrace_Peek_Type, pid: Pid, addr: uintptr) -> (uint, Errno) { - ret := syscall(SYS_ptrace, rq, pid, addr) - return errno_unwrap(ret, uint) + res: uint = --- + ret := syscall(SYS_ptrace, rq, pid, addr, &res) + return res, Errno(-ret) } ptrace_poke :: proc "contextless" (rq: PTrace_Poke_Type, pid: Pid, addr: uintptr, data: uint) -> (Errno) { @@ -1493,12 +1494,12 @@ ptrace_setregset :: proc "contextless" (rq: PTrace_Setregset_Type, pid: Pid, not } ptrace_getsiginfo :: proc "contextless" (rq: PTrace_Getsiginfo_Type, pid: Pid, si: ^Sig_Info) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, si) + ret := syscall(SYS_ptrace, rq, pid, si, rawptr(nil)) return Errno(-ret) } ptrace_peeksiginfo :: proc "contextless" (rq: PTrace_Peeksiginfo_Type, pid: Pid, si: ^Sig_Info) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, si) + ret := syscall(SYS_ptrace, rq, pid, si, rawptr(nil)) return Errno(-ret) } @@ -1518,52 +1519,52 @@ ptrace_setoptions :: proc "contextless" (rq: PTrace_Setoptions_Type, pid: Pid, o } ptrace_geteventmsg :: proc "contextless" (rq: PTrace_Geteventmsg_Type, pid: Pid, msg: ^uint) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, msg) + ret := syscall(SYS_ptrace, rq, pid, msg, rawptr(nil)) return Errno(-ret) } ptrace_cont :: proc "contextless" (rq: PTrace_Cont_Type, pid: Pid, sig: Signal) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, sig) + ret := syscall(SYS_ptrace, rq, pid, rawptr(nil), sig) return Errno(-ret) } ptrace_singlestep :: proc "contextless" (rq: PTrace_Singlestep_Type, pid: Pid, sig: Signal) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, sig) + ret := syscall(SYS_ptrace, rq, pid, rawptr(nil), sig) return Errno(-ret) } ptrace_syscall :: proc "contextless" (rq: PTrace_Syscall_Type, pid: Pid, sig: Signal) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, sig) + ret := syscall(SYS_ptrace, rq, pid, rawptr(nil), sig) return Errno(-ret) } ptrace_sysemu :: proc "contextless" (rq: PTrace_Sysemu_Type, pid: Pid, sig: Signal) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, sig) + ret := syscall(SYS_ptrace, rq, pid, rawptr(nil), sig) return Errno(-ret) } ptrace_sysemu_singlestep :: proc "contextless" (rq: PTrace_Sysemu_Singlestep_Type, pid: Pid, sig: Signal) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, sig) + ret := syscall(SYS_ptrace, rq, pid, rawptr(nil), sig) return Errno(-ret) } ptrace_listen :: proc "contextless" (rq: PTrace_Listen_Type, pid: Pid) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid) + ret := syscall(SYS_ptrace, rq, pid, 0, rawptr(nil)) return Errno(-ret) } ptrace_interrupt :: proc "contextless" (rq: PTrace_Interrupt_Type, pid: Pid) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid) + ret := syscall(SYS_ptrace, rq, pid, 0, rawptr(nil)) return Errno(-ret) } ptrace_attach :: proc "contextless" (rq: PTrace_Attach_Type, pid: Pid) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid) + ret := syscall(SYS_ptrace, rq, pid, 0, rawptr(nil)) return Errno(-ret) } ptrace_seize :: proc "contextless" (rq: PTrace_Seize_Type, pid: Pid, opt: PTrace_Options) -> (Errno) { - ret := syscall(SYS_ptrace, rq, pid, 0, transmute(u32) opt) + ret := syscall(SYS_ptrace, rq, pid, 0, transmute(u32) opt, rawptr(nil)) return Errno(-ret) } @@ -2993,3 +2994,18 @@ epoll_pwait2 :: proc(epfd: Fd, events: [^]EPoll_Event, count: i32, timeout: ^Tim // TODO(flysand): fchmodat2 // TODO(flysand): map_shadow_stack + +when ODIN_ARCH == .riscv64 { + /* + Probe for RISC-V Hardware Support. + Available since Linux 6.4. + + TODO: cpu_set_t + + See: https://docs.kernel.org/arch/riscv/hwprobe.html + */ + riscv_hwprobe :: proc "contextless" (pairs: [^]RISCV_HWProbe, pair_count: uint, cpu_count: uint, cpus: rawptr /* cpu_set_t */, flags: RISCV_HWProbe_Flags) -> Errno { + ret := syscall(SYS_riscv_hwprobe, pairs, pair_count, cpu_count, cpus, transmute(u32)flags) + return Errno(-ret) + } +} diff --git a/core/sys/linux/syscall_amd64.odin b/core/sys/linux/syscall_amd64.odin index ee4e16280..31c8ed61c 100644 --- a/core/sys/linux/syscall_amd64.odin +++ b/core/sys/linux/syscall_amd64.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package linux // AMD64 uses the new way to define syscalls, i.e. one that diff --git a/core/sys/linux/syscall_arm32.odin b/core/sys/linux/syscall_arm32.odin index 74640a1a3..731ce36a5 100644 --- a/core/sys/linux/syscall_arm32.odin +++ b/core/sys/linux/syscall_arm32.odin @@ -1,4 +1,4 @@ -//+build arm32 +#+build arm32 package linux // This file was taken and transformed from diff --git a/core/sys/linux/syscall_arm64.odin b/core/sys/linux/syscall_arm64.odin index 61b5a31b7..da8eb45da 100644 --- a/core/sys/linux/syscall_arm64.odin +++ b/core/sys/linux/syscall_arm64.odin @@ -1,4 +1,4 @@ -//+build arm64 +#+build arm64 package linux // Syscalls for arm64 are defined using the new way, i.e. differently from diff --git a/core/sys/linux/syscall_i386.odin b/core/sys/linux/syscall_i386.odin index 4609fc99c..affdff02c 100644 --- a/core/sys/linux/syscall_i386.odin +++ b/core/sys/linux/syscall_i386.odin @@ -1,4 +1,4 @@ -//+build i386 +#+build i386 package linux // The numbers are taken from diff --git a/core/sys/linux/syscall_riscv64.odin b/core/sys/linux/syscall_riscv64.odin index ce374312e..17845c5ed 100644 --- a/core/sys/linux/syscall_riscv64.odin +++ b/core/sys/linux/syscall_riscv64.odin @@ -1,4 +1,4 @@ -//+build riscv64 +#+build riscv64 package linux // https://github.com/riscv-collab/riscv-gnu-toolchain/blob/master/linux-headers/include/asm-generic/unistd.h @@ -248,6 +248,7 @@ SYS_rt_tgsigqueueinfo :: uintptr(240) SYS_perf_event_open :: uintptr(241) SYS_accept4 :: uintptr(242) SYS_recvmmsg :: uintptr(243) +SYS_riscv_hwprobe :: uintptr(258) SYS_wait4 :: uintptr(260) SYS_prlimit64 :: uintptr(261) SYS_fanotify_init :: uintptr(262) diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin index 3f873f96c..0e5b8218b 100644 --- a/core/sys/linux/types.odin +++ b/core/sys/linux/types.odin @@ -1335,3 +1335,20 @@ EPoll_Event :: struct #packed { Flags for execveat(2) syscall. */ Execveat_Flags :: bit_set[Execveat_Flags_Bits; i32] + +RISCV_HWProbe_Flags :: bit_set[RISCV_HWProbe_Flags_Bits; u32] +RISCV_HWProbe_CPU_Perf_0 :: bit_set[RISCV_HWProbe_Misaligned_Scalar_Perf; u64] +RISCV_HWProbe_Base_Behavior :: bit_set[RISCV_HWProbe_Base_Behavior_Bits; u64] +RISCV_HWProbe_IMA_Ext_0 :: bit_set[RISCV_HWProbe_IMA_Ext_0_Bits; u64] + +RISCV_HWProbe :: struct { + // set to `.UNSUPPORTED` by the kernel if that is the case. + key: RISCV_HWProbe_Key, + value: struct #raw_union { + base_behavior: RISCV_HWProbe_Base_Behavior, + ima_ext_0: RISCV_HWProbe_IMA_Ext_0, + cpu_perf_0: RISCV_HWProbe_CPU_Perf_0, + misaligned_scalar_perf: RISCV_HWProbe_Misaligned_Scalar_Perf, + raw: u64, + }, +} diff --git a/core/sys/linux/wrappers.odin b/core/sys/linux/wrappers.odin index 7a30c3bde..4f6118c80 100644 --- a/core/sys/linux/wrappers.odin +++ b/core/sys/linux/wrappers.odin @@ -1,4 +1,4 @@ -//+build linux +#+build linux package linux /// Low 8 bits of the exit code diff --git a/core/sys/llvm/bit_manipulation.odin b/core/sys/llvm/bit_manipulation.odin index 39464773d..2e237dd32 100644 --- a/core/sys/llvm/bit_manipulation.odin +++ b/core/sys/llvm/bit_manipulation.odin @@ -1,4 +1,5 @@ // Bit Manipulation Intrinsics + package sys_llvm /* diff --git a/core/sys/llvm/code_generator.odin b/core/sys/llvm/code_generator.odin index 7d41ed67b..6422976c5 100644 --- a/core/sys/llvm/code_generator.odin +++ b/core/sys/llvm/code_generator.odin @@ -1,4 +1,5 @@ // Code Generator Intrinsics + package sys_llvm @(default_calling_convention="none") diff --git a/core/sys/llvm/standard_c_library.odin b/core/sys/llvm/standard_c_library.odin index 108d1268e..1818e8462 100644 --- a/core/sys/llvm/standard_c_library.odin +++ b/core/sys/llvm/standard_c_library.odin @@ -1,4 +1,5 @@ // Standard C Library Intrinsics + package sys_llvm @(default_calling_convention="none") diff --git a/core/sys/posix/time.odin b/core/sys/posix/time.odin index 9b91c9558..5c6ebcf2f 100644 --- a/core/sys/posix/time.odin +++ b/core/sys/posix/time.odin @@ -13,7 +13,7 @@ when ODIN_OS == .Darwin { foreign lib { /* - Convert the broken down time in the structure to a string form: Sun Sep 16 01:03:52 1973\n\0 + Convert the broken down time in the structure to a string form: Sun Sep 16 01:03:52 1973. [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/asctime_r.html ]] */ diff --git a/core/sys/posix/unistd.odin b/core/sys/posix/unistd.odin index 6ed9e5d11..15dbb576f 100644 --- a/core/sys/posix/unistd.odin +++ b/core/sys/posix/unistd.odin @@ -112,7 +112,7 @@ foreign lib { Return configuration-defined string values. Its use and purpose are similar to sysconf(), but it is used where string values rather than numeric values are returned. - Returns: 0 (setting errno) if `name` is invalid, need `buf` `len` if buf is `nil`, amount of bytes added to buf otherwise + Returns: 0 (setting errno) if `name` is invalid, need `buf` of `len` bytes if `buf` is `nil`, amount of bytes added to buf otherwise [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/confstr.html ]] */ diff --git a/core/sys/unix/pthread_darwin.odin b/core/sys/unix/pthread_darwin.odin index 378fa9309..eb2cc4c9f 100644 --- a/core/sys/unix/pthread_darwin.odin +++ b/core/sys/unix/pthread_darwin.odin @@ -1,4 +1,4 @@ -//+build darwin +#+build darwin package unix import "core:c" diff --git a/core/sys/unix/pthread_freebsd.odin b/core/sys/unix/pthread_freebsd.odin index 5f4dac289..38fe7db55 100644 --- a/core/sys/unix/pthread_freebsd.odin +++ b/core/sys/unix/pthread_freebsd.odin @@ -1,4 +1,4 @@ -//+build freebsd +#+build freebsd package unix import "core:c" diff --git a/core/sys/unix/pthread_linux.odin b/core/sys/unix/pthread_linux.odin index f4ded7464..d67add24b 100644 --- a/core/sys/unix/pthread_linux.odin +++ b/core/sys/unix/pthread_linux.odin @@ -1,4 +1,4 @@ -//+build linux +#+build linux package unix import "core:c" diff --git a/core/sys/unix/pthread_openbsd.odin b/core/sys/unix/pthread_openbsd.odin index 855e7d99c..2c6d9e598 100644 --- a/core/sys/unix/pthread_openbsd.odin +++ b/core/sys/unix/pthread_openbsd.odin @@ -1,4 +1,4 @@ -//+build openbsd +#+build openbsd package unix import "core:c" diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin index 68a0859b4..43c4866ed 100644 --- a/core/sys/unix/pthread_unix.odin +++ b/core/sys/unix/pthread_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, netbsd, haiku +#+build linux, darwin, freebsd, openbsd, netbsd, haiku package unix foreign import "system:pthread" @@ -27,6 +27,8 @@ foreign pthread { pthread_equal :: proc(a, b: pthread_t) -> b32 --- + pthread_detach :: proc(t: pthread_t) -> c.int --- + sched_get_priority_min :: proc(policy: c.int) -> c.int --- sched_get_priority_max :: proc(policy: c.int) -> c.int --- diff --git a/core/sys/unix/sysctl_darwin.odin b/core/sys/unix/sysctl_darwin.odin index 92222bdfe..32dd720b0 100644 --- a/core/sys/unix/sysctl_darwin.odin +++ b/core/sys/unix/sysctl_darwin.odin @@ -1,4 +1,4 @@ -//+build darwin +#+build darwin package unix import "base:intrinsics" diff --git a/core/sys/unix/sysctl_freebsd.odin b/core/sys/unix/sysctl_freebsd.odin index 8ca40ef1b..f5fee6c6c 100644 --- a/core/sys/unix/sysctl_freebsd.odin +++ b/core/sys/unix/sysctl_freebsd.odin @@ -1,4 +1,4 @@ -//+build freebsd +#+build freebsd package unix import "base:intrinsics" diff --git a/core/sys/unix/sysctl_openbsd.odin b/core/sys/unix/sysctl_openbsd.odin index b93e8f9bd..49c9b6336 100644 --- a/core/sys/unix/sysctl_openbsd.odin +++ b/core/sys/unix/sysctl_openbsd.odin @@ -1,4 +1,4 @@ -//+build openbsd +#+build openbsd package unix import "core:c" diff --git a/core/sys/valgrind/callgrind.odin b/core/sys/valgrind/callgrind.odin index b1ba8c6e9..5cd58753a 100644 --- a/core/sys/valgrind/callgrind.odin +++ b/core/sys/valgrind/callgrind.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package sys_valgrind import "base:intrinsics" diff --git a/core/sys/valgrind/helgrind.odin b/core/sys/valgrind/helgrind.odin index 2f0114522..3f5e7a531 100644 --- a/core/sys/valgrind/helgrind.odin +++ b/core/sys/valgrind/helgrind.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package sys_valgrind import "base:intrinsics" diff --git a/core/sys/valgrind/memcheck.odin b/core/sys/valgrind/memcheck.odin index dfbe4c3be..bc77444be 100644 --- a/core/sys/valgrind/memcheck.odin +++ b/core/sys/valgrind/memcheck.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package sys_valgrind import "base:intrinsics" diff --git a/core/sys/valgrind/valgrind.odin b/core/sys/valgrind/valgrind.odin index d0c46af53..b5c71664f 100644 --- a/core/sys/valgrind/valgrind.odin +++ b/core/sys/valgrind/valgrind.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package sys_valgrind import "base:intrinsics" diff --git a/core/sys/wasm/README.md b/core/sys/wasm/README.md new file mode 100644 index 000000000..1aaeaa429 --- /dev/null +++ b/core/sys/wasm/README.md @@ -0,0 +1,15 @@ +# WASM on the Web + +This directory is for use when targeting the `js_wasm32` target and the packages that rely on it. + +The `js_wasm32` target assumes that the WASM output will be ran within a web browser rather than a standalone VM. In the VM cases, either `wasi_wasm32` or `freestanding_wasm32` should be used accordingly. + +## Example for `js_wasm32` + +```html +<!-- Copy `core:sys/wasm/js/odin.js` into your web server --> +<script type="text/javascript" src="odin.js"></script> +<script type="text/javascript"> + odin.runWasm(pathToWasm, consolePreElement); +</script> +``` diff --git a/core/sys/wasm/js/dom.odin b/core/sys/wasm/js/dom.odin new file mode 100644 index 000000000..ffc58a9a3 --- /dev/null +++ b/core/sys/wasm/js/dom.odin @@ -0,0 +1,93 @@ +#+build js wasm32, js wasm64p32 +package wasm_js_interface + +foreign import dom_lib "odin_dom" + +@(default_calling_convention="contextless") +foreign dom_lib { + get_element_value_f64 :: proc(id: string) -> f64 --- + set_element_value_f64 :: proc(id: string, value: f64) --- + + get_element_key_f64 :: proc(id: string, key: string) -> f64 --- + set_element_key_f64 :: proc(id: string, key: string, value: f64) --- + + set_element_value_string :: proc(id: string, value: string) --- + get_element_value_string_length :: proc(id: string) -> int --- + + set_element_key_string :: proc(id: string, key: string, value: string) --- + get_element_key_string_length :: proc(id: string, key: string, ) -> int --- + + device_pixel_ratio :: proc() -> f64 --- + + window_set_scroll :: proc(x, y: f64) --- +} + +get_element_value_string :: proc "contextless" (id: string, buf: []byte) -> string { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="get_element_value_string") + _get_element_value_string :: proc(id: string, buf: []byte) -> int --- + } + n := _get_element_value_string(id, buf) + return string(buf[:n]) +} + +get_element_key_string :: proc "contextless" (id: string, key: string, buf: []byte) -> string { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="get_element_key_string") + _get_element_key_string :: proc(id: string, key: string, buf: []byte) -> int --- + } + n := _get_element_key_string(id, key, buf) + return string(buf[:n]) + +} + + + +get_element_min_max :: proc "contextless" (id: string) -> (min, max: f64) { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="get_element_min_max") + _get_element_min_max :: proc(min_max: ^[2]f64, id: string) --- + } + min_max: [2]f64 + _get_element_min_max(&min_max, id) + return min_max[0], min_max[1] +} + + +Rect :: struct { + x, y, width, height: f64, +} + +get_bounding_client_rect :: proc "contextless" (id: string) -> (rect: Rect) { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="get_bounding_client_rect") + _get_bounding_client_rect :: proc(rect: ^Rect, id: string) --- + } + _get_bounding_client_rect(&rect, id) + return +} + +window_get_rect :: proc "contextless" () -> (rect: Rect) { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="window_get_rect") + _window_get_rect :: proc(rect: ^Rect) --- + } + _window_get_rect(&rect) + return +} + +window_get_scroll :: proc "contextless" () -> (x, y: f64) { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="window_get_scroll") + _window_get_scroll :: proc(scroll: ^[2]f64) --- + } + scroll: [2]f64 + _window_get_scroll(&scroll) + return scroll.x, scroll.y +}
\ No newline at end of file diff --git a/core/sys/wasm/js/dom_all_targets.odin b/core/sys/wasm/js/dom_all_targets.odin new file mode 100644 index 000000000..171deed2f --- /dev/null +++ b/core/sys/wasm/js/dom_all_targets.odin @@ -0,0 +1,36 @@ +#+build !js +package wasm_js_interface + +import "base:runtime" + + +get_element_value_string :: proc "contextless" (id: string, buf: []byte) -> string { + context = runtime.default_context() + panic("vendor:wasm/js not supported on non JS targets") +} + + +get_element_min_max :: proc "contextless" (id: string) -> (min, max: f64) { + context = runtime.default_context() + panic("vendor:wasm/js not supported on non JS targets") +} + + +Rect :: struct { + x, y, width, height: f64, +} + +get_bounding_client_rect :: proc "contextless" (id: string) -> (rect: Rect) { + context = runtime.default_context() + panic("vendor:wasm/js not supported on non JS targets") +} + +window_get_rect :: proc "contextless" () -> (rect: Rect) { + context = runtime.default_context() + panic("vendor:wasm/js not supported on non JS targets") +} + +window_get_scroll :: proc "contextless" () -> (x, y: f64) { + context = runtime.default_context() + panic("vendor:wasm/js not supported on non JS targets") +} diff --git a/core/sys/wasm/js/events.odin b/core/sys/wasm/js/events.odin new file mode 100644 index 000000000..905b3eba9 --- /dev/null +++ b/core/sys/wasm/js/events.odin @@ -0,0 +1,418 @@ +#+build js wasm32, js wasm64p32 +package wasm_js_interface + +foreign import dom_lib "odin_dom" + +Event_Kind :: enum u32 { + Invalid, + + Load, + Unload, + Error, + Resize, + Visibility_Change, + Fullscreen_Change, + Fullscreen_Error, + + Click, + Double_Click, + Mouse_Move, + Mouse_Over, + Mouse_Out, + Mouse_Up, + Mouse_Down, + + Key_Up, + Key_Down, + Key_Press, + + Scroll, + Wheel, + + Focus, + Focus_In, + Focus_Out, + Submit, + Blur, + Change, + Hash_Change, + Select, + + Animation_Start, + Animation_End, + Animation_Iteration, + Animation_Cancel, + + Copy, + Cut, + Paste, + + // Drag, + // Drag_Start, + // Drag_End, + // Drag_Enter, + // Drag_Leave, + // Drag_Over, + // Drop, + + Pointer_Cancel, + Pointer_Down, + Pointer_Enter, + Pointer_Leave, + Pointer_Move, + Pointer_Over, + Pointer_Up, + Got_Pointer_Capture, + Lost_Pointer_Capture, + Pointer_Lock_Change, + Pointer_Lock_Error, + + Selection_Change, + Selection_Start, + + Touch_Cancel, + Touch_End, + Touch_Move, + Touch_Start, + + Transition_Start, + Transition_End, + Transition_Run, + Transition_Cancel, + + Context_Menu, + + Gamepad_Connected, + Gamepad_Disconnected, + + Custom, + +} +event_kind_string := [Event_Kind]string{ + .Invalid = "", + + .Load = "load", + .Unload = "unload", + .Error = "error", + .Resize = "resize", + .Visibility_Change = "visibilitychange", + .Fullscreen_Change = "fullscreenchange", + .Fullscreen_Error = "fullscreenerror", + + .Click = "click", + .Double_Click = "dblclick", + .Mouse_Move = "mousemove", + .Mouse_Over = "mouseover", + .Mouse_Out = "mouseout", + .Mouse_Up = "mouseup", + .Mouse_Down = "mousedown", + + .Key_Up = "keyup", + .Key_Down = "keydown", + .Key_Press = "keypress", + + .Scroll = "scroll", + .Wheel = "wheel", + + .Focus = "focus", + .Focus_In = "focusin", + .Focus_Out = "focusout", + .Submit = "submit", + .Blur = "blur", + .Change = "change", + .Hash_Change = "hashchange", + .Select = "select", + + .Animation_Start = "animationstart", + .Animation_End = "animationend", + .Animation_Iteration = "animationiteration", + .Animation_Cancel = "animationcancel", + + .Copy = "copy", + .Cut = "cut", + .Paste = "paste", + + // .Drag, = "drag", + // .Drag_Start, = "dragstart", + // .Drag_End, = "dragend", + // .Drag_Enter, = "dragenter", + // .Drag_Leave, = "dragleave", + // .Drag_Over, = "dragover", + // .Drop, = "drop", + + .Pointer_Cancel = "pointercancel", + .Pointer_Down = "pointerdown", + .Pointer_Enter = "pointerenter", + .Pointer_Leave = "pointerleave", + .Pointer_Move = "pointermove", + .Pointer_Over = "pointerover", + .Pointer_Up = "pointerup", + .Got_Pointer_Capture = "gotpointercapture", + .Lost_Pointer_Capture = "lostpointercapture", + .Pointer_Lock_Change = "pointerlockchange", + .Pointer_Lock_Error = "pointerlockerror", + + .Selection_Change = "selectionchange", + .Selection_Start = "selectionstart", + + .Transition_Start = "transitionstart", + .Transition_End = "transitionend", + .Transition_Run = "transitionrun", + .Transition_Cancel = "transitioncancel", + + .Touch_Cancel = "touchcancel", + .Touch_End = "touchend", + .Touch_Move = "touchmove", + .Touch_Start = "touchstart", + + .Context_Menu = "contextmenu", + + .Gamepad_Connected = "gamepadconnected", + .Gamepad_Disconnected = "gamepaddisconnected", + + .Custom = "?custom?", +} + +Delta_Mode :: enum u32 { + Pixel = 0, + Line = 1, + Page = 2, +} + +Key_Location :: enum u8 { + Standard = 0, + Left = 1, + Right = 2, + Numpad = 3, +} + +KEYBOARD_MAX_KEY_SIZE :: 16 +KEYBOARD_MAX_CODE_SIZE :: 16 + +GAMEPAD_MAX_ID_SIZE :: 64 +GAMEPAD_MAX_MAPPING_SIZE :: 64 + +GAMEPAD_MAX_BUTTONS :: 64 +GAMEPAD_MAX_AXES :: 16 + +Event_Target_Kind :: enum u32 { + Element = 0, + Document = 1, + Window = 2, +} + +Event_Phase :: enum u8 { + None = 0, + Capturing_Phase = 1, + At_Target = 2, + Bubbling_Phase = 3, +} + +Event_Option :: enum u8 { + Bubbles = 0, + Cancelable = 1, + Composed = 2, +} +Event_Options :: distinct bit_set[Event_Option; u8] + +Gamepad_Button :: struct { + value: f64, + pressed: bool, + touched: bool, +} + +Gamepad_State :: struct { + id: string, + mapping: string, + index: int, + connected: bool, + timestamp: f64, + + button_count: int, + axis_count: int, + buttons: [GAMEPAD_MAX_BUTTONS]Gamepad_Button `fmt:"v,button_count"`, + axes: [GAMEPAD_MAX_AXES]f64 `fmt:"v,axes_count"`, + + _id_len: int `fmt:"-"`, + _mapping_len: int `fmt:"-"`, + _id_buf: [GAMEPAD_MAX_ID_SIZE]byte `fmt:"-"`, + _mapping_buf: [GAMEPAD_MAX_MAPPING_SIZE]byte `fmt:"-"`, +} + +Event :: struct { + kind: Event_Kind, + target_kind: Event_Target_Kind, + current_target_kind: Event_Target_Kind, + id: string, + timestamp: f64, + + phase: Event_Phase, + options: Event_Options, + is_composing: bool, + is_trusted: bool, + + using data: struct #raw_union #align(8) { + scroll: struct { + delta: [2]f64, + }, + visibility_change: struct { + is_visible: bool, + }, + wheel: struct { + delta: [3]f64, + delta_mode: Delta_Mode, + }, + + key: struct { + key: string, + code: string, + location: Key_Location, + + ctrl: bool, + shift: bool, + alt: bool, + meta: bool, + + repeat: bool, + + _key_len: int `fmt:"-"`, + _code_len: int `fmt:"-"`, + _key_buf: [KEYBOARD_MAX_KEY_SIZE]byte `fmt:"-"`, + _code_buf: [KEYBOARD_MAX_KEY_SIZE]byte `fmt:"-"`, + }, + + mouse: struct { + screen: [2]i64, + client: [2]i64, + offset: [2]i64, + page: [2]i64, + movement: [2]i64, + + ctrl: bool, + shift: bool, + alt: bool, + meta: bool, + + button: i16, + buttons: bit_set[0..<16; u16], + }, + + gamepad: Gamepad_State, + }, + + + user_data: rawptr, + callback: proc(e: Event), +} + +@(default_calling_convention="contextless") +foreign dom_lib { + event_stop_propagation :: proc() --- + event_stop_immediate_propagation :: proc() --- + event_prevent_default :: proc() --- + dispatch_custom_event :: proc(id: string, name: string, options := Event_Options{}) -> bool --- +} + +add_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="add_event_listener") + _add_event_listener :: proc(id: string, name: string, name_code: Event_Kind, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool --- + } + // TODO: Pointer_Lock_Change etc related stuff for all different browsers + return _add_event_listener(id, event_kind_string[kind], kind, user_data, callback, use_capture) +} + +remove_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, callback: proc(e: Event)) -> bool { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="remove_event_listener") + _remove_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc "odin" (Event)) -> bool --- + } + return _remove_event_listener(id, event_kind_string[kind], user_data, callback) +} + +add_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="add_window_event_listener") + _add_window_event_listener :: proc(name: string, name_code: Event_Kind, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool --- + } + return _add_window_event_listener(event_kind_string[kind], kind, user_data, callback, use_capture) +} + +remove_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event)) -> bool { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="remove_window_event_listener") + _remove_window_event_listener :: proc(name: string, user_data: rawptr, callback: proc "odin" (Event)) -> bool --- + } + return _remove_window_event_listener(event_kind_string[kind], user_data, callback) +} + +remove_event_listener_from_event :: proc(e: Event) -> bool { + if e.id == "" { + return remove_window_event_listener(e.kind, e.user_data, e.callback) + } + return remove_event_listener(e.id, e.kind, e.user_data, e.callback) +} + +add_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="add_event_listener") + _add_event_listener :: proc(id: string, name: string, name_code: Event_Kind, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool --- + } + return _add_event_listener(id, name, .Custom, user_data, callback, use_capture) +} +remove_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event)) -> bool { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="remove_event_listener") + _remove_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc "odin" (Event)) -> bool --- + } + return _remove_event_listener(id, name, user_data, callback) +} + +get_gamepad_state :: proc "contextless" (index: int, s: ^Gamepad_State) -> bool { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="get_gamepad_state") + _get_gamepad_state :: proc(index: int, s: ^Gamepad_State) -> bool --- + } + + if s == nil { + return false + } + return _get_gamepad_state(index, s) +} + + +@(export, link_name="odin_dom_do_event_callback") +do_event_callback :: proc(user_data: rawptr, callback: proc(e: Event)) { + @(default_calling_convention="contextless") + foreign dom_lib { + init_event_raw :: proc(e: ^Event) --- + } + + if callback != nil { + event := Event{ + user_data = user_data, + callback = callback, + } + + + init_event_raw(&event) + + #partial switch event.kind { + case .Key_Up, .Key_Down, .Key_Press: + event.key.key = string(event.key._key_buf[:event.key._key_len]) + event.key.code = string(event.key._code_buf[:event.key._code_len]) + case .Gamepad_Connected, .Gamepad_Disconnected: + event.gamepad.id = string(event.gamepad._id_buf[:event.gamepad._id_len]) + event.gamepad.mapping = string(event.gamepad._mapping_buf[:event.gamepad._mapping_len]) + } + + callback(event) + } +}
\ No newline at end of file diff --git a/core/sys/wasm/js/events_all_targets.odin b/core/sys/wasm/js/events_all_targets.odin new file mode 100644 index 000000000..b7e01ca10 --- /dev/null +++ b/core/sys/wasm/js/events_all_targets.odin @@ -0,0 +1,287 @@ +#+build !js +package wasm_js_interface + + +Event_Kind :: enum u32 { + Invalid, + + Load, + Unload, + Error, + Resize, + Visibility_Change, + Fullscreen_Change, + Fullscreen_Error, + + Click, + Double_Click, + Mouse_Move, + Mouse_Over, + Mouse_Out, + Mouse_Up, + Mouse_Down, + + Key_Up, + Key_Down, + Key_Press, + + Scroll, + Wheel, + + Focus, + Submit, + Blur, + Change, + Select, + + Animation_Start, + Animation_End, + Animation_Iteration, + Animation_Cancel, + + Copy, + Cut, + Paste, + + // Drag, + // Drag_Start, + // Drag_End, + // Drag_Enter, + // Drag_Leave, + // Drag_Over, + // Drop, + + Pointer_Cancel, + Pointer_Down, + Pointer_Enter, + Pointer_Leave, + Pointer_Move, + Pointer_Over, + Pointer_Up, + Got_Pointer_Capture, + Lost_Pointer_Capture, + Pointer_Lock_Change, + Pointer_Lock_Error, + + Selection_Change, + Selection_Start, + + Touch_Cancel, + Touch_End, + Touch_Move, + Touch_Start, + + Transition_Start, + Transition_End, + Transition_Run, + Transition_Cancel, + + Context_Menu, + + Custom, + +} +event_kind_string := [Event_Kind]string{ + .Invalid = "", + + .Load = "load", + .Unload = "unload", + .Error = "error", + .Resize = "resize", + .Visibility_Change = "visibilitychange", + .Fullscreen_Change = "fullscreenchange", + .Fullscreen_Error = "fullscreenerror", + + .Click = "click", + .Double_Click = "dblclick", + .Mouse_Move = "mousemove", + .Mouse_Over = "mouseover", + .Mouse_Out = "mouseout", + .Mouse_Up = "mouseup", + .Mouse_Down = "mousedown", + + .Key_Up = "keyup", + .Key_Down = "keydown", + .Key_Press = "keypress", + + .Scroll = "scroll", + .Wheel = "wheel", + + .Focus = "focus", + .Submit = "submit", + .Blur = "blur", + .Change = "change", + .Select = "select", + + .Animation_Start = "animationstart", + .Animation_End = "animationend", + .Animation_Iteration = "animationiteration", + .Animation_Cancel = "animationcancel", + + .Copy = "copy", + .Cut = "cut", + .Paste = "paste", + + // .Drag, = "drag", + // .Drag_Start, = "dragstart", + // .Drag_End, = "dragend", + // .Drag_Enter, = "dragenter", + // .Drag_Leave, = "dragleave", + // .Drag_Over, = "dragover", + // .Drop, = "drop", + + .Pointer_Cancel = "pointercancel", + .Pointer_Down = "pointerdown", + .Pointer_Enter = "pointerenter", + .Pointer_Leave = "pointerleave", + .Pointer_Move = "pointermove", + .Pointer_Over = "pointerover", + .Pointer_Up = "pointerup", + .Got_Pointer_Capture = "gotpointercapture", + .Lost_Pointer_Capture = "lostpointercapture", + .Pointer_Lock_Change = "pointerlockchange", + .Pointer_Lock_Error = "pointerlockerror", + + .Selection_Change = "selectionchange", + .Selection_Start = "selectionstart", + + .Transition_Start = "transitionstart", + .Transition_End = "transitionend", + .Transition_Run = "transitionrun", + .Transition_Cancel = "transitioncancel", + + .Touch_Cancel = "touchcancel", + .Touch_End = "touchend", + .Touch_Move = "touchmove", + .Touch_Start = "touchstart", + + .Context_Menu = "contextmenu", + + .Custom = "?custom?", +} + +Delta_Mode :: enum u32 { + Pixel = 0, + Line = 1, + Page = 2, +} + +Key_Location :: enum u8 { + Standard = 0, + Left = 1, + Right = 2, + Numpad = 3, +} + +KEYBOARD_MAX_KEY_SIZE :: 16 +KEYBOARD_MAX_CODE_SIZE :: 16 + +Event_Target_Kind :: enum u32 { + Element = 0, + Document = 1, + Window = 2, +} + +Event_Phase :: enum u8 { + None = 0, + Capturing_Phase = 1, + At_Target = 2, + Bubbling_Phase = 3, +} + +Event_Option :: enum u8 { + Bubbles = 0, + Cancelable = 1, + Composed = 2, +} +Event_Options :: distinct bit_set[Event_Option; u8] + +Event :: struct { + kind: Event_Kind, + target_kind: Event_Target_Kind, + current_target_kind: Event_Target_Kind, + id: string, + timestamp: f64, + + phase: Event_Phase, + options: Event_Options, + is_composing: bool, + is_trusted: bool, + + using data: struct #raw_union #align(8) { + scroll: struct { + delta: [2]f64, + }, + visibility_change: struct { + is_visible: bool, + }, + wheel: struct { + delta: [3]f64, + delta_mode: Delta_Mode, + }, + + key: struct { + key: string, + code: string, + location: Key_Location, + + ctrl: bool, + shift: bool, + alt: bool, + meta: bool, + + repeat: bool, + + _key_buf: [KEYBOARD_MAX_KEY_SIZE]byte, + _code_buf: [KEYBOARD_MAX_KEY_SIZE]byte, + }, + + mouse: struct { + screen: [2]i64, + client: [2]i64, + offset: [2]i64, + page: [2]i64, + movement: [2]i64, + + ctrl: bool, + shift: bool, + alt: bool, + meta: bool, + + button: i16, + buttons: bit_set[0..<16; u16], + }, + }, + + + user_data: rawptr, + callback: proc(e: Event), +} + + +add_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { + panic("vendor:wasm/js not supported on non JS targets") +} + +remove_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, callback: proc(e: Event)) -> bool { + panic("vendor:wasm/js not supported on non JS targets") +} + +add_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { + panic("vendor:wasm/js not supported on non JS targets") +} + +remove_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event)) -> bool { + panic("vendor:wasm/js not supported on non JS targets") +} + +remove_event_listener_from_event :: proc(e: Event) -> bool { + panic("vendor:wasm/js not supported on non JS targets") +} + +add_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { + panic("vendor:wasm/js not supported on non JS targets") +} +remove_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event)) -> bool { + panic("vendor:wasm/js not supported on non JS targets") +}
\ No newline at end of file diff --git a/core/sys/wasm/js/general.odin b/core/sys/wasm/js/general.odin new file mode 100644 index 000000000..4ed2ae298 --- /dev/null +++ b/core/sys/wasm/js/general.odin @@ -0,0 +1,12 @@ +#+build js wasm32, js wasm64p32 +package wasm_js_interface + +foreign import "odin_env" + +@(default_calling_convention="contextless") +foreign odin_env { + trap :: proc() -> ! --- + abort :: proc() -> ! --- + alert :: proc(msg: string) --- + evaluate :: proc(str: string) --- +}
\ No newline at end of file diff --git a/core/sys/wasm/js/memory_all_targets.odin b/core/sys/wasm/js/memory_all_targets.odin new file mode 100644 index 000000000..e80d13c0b --- /dev/null +++ b/core/sys/wasm/js/memory_all_targets.odin @@ -0,0 +1,14 @@ +#+build !js +package wasm_js_interface + +import "core:mem" + +PAGE_SIZE :: 64 * 1024 +page_alloc :: proc(page_count: int) -> (data: []byte, err: mem.Allocator_Error) { + panic("vendor:wasm/js not supported on non-js targets") +} + +page_allocator :: proc() -> mem.Allocator { + panic("vendor:wasm/js not supported on non-js targets") +} + diff --git a/core/sys/wasm/js/memory_js.odin b/core/sys/wasm/js/memory_js.odin new file mode 100644 index 000000000..8232cd0c9 --- /dev/null +++ b/core/sys/wasm/js/memory_js.odin @@ -0,0 +1,44 @@ +#+build js wasm32, js wasm64p32 +package wasm_js_interface + +import "core:mem" +import "base:intrinsics" + +PAGE_SIZE :: 64 * 1024 +page_alloc :: proc(page_count: int) -> (data: []byte, err: mem.Allocator_Error) { + prev_page_count := intrinsics.wasm_memory_grow(0, uintptr(page_count)) + if prev_page_count < 0 { + return nil, .Out_Of_Memory + } + + ptr := ([^]u8)(uintptr(prev_page_count) * PAGE_SIZE) + return ptr[:page_count * PAGE_SIZE], nil +} + +page_allocator :: proc() -> mem.Allocator { + procedure :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, + location := #caller_location) -> ([]byte, mem.Allocator_Error) { + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + assert(size % PAGE_SIZE == 0) + return page_alloc(size/PAGE_SIZE) + case .Resize, .Free, .Free_All, .Query_Info, .Resize_Non_Zeroed: + return nil, .Mode_Not_Implemented + case .Query_Features: + set := (^mem.Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Query_Features} + } + } + + return nil, nil + } + + return { + procedure = procedure, + data = nil, + } +} + diff --git a/core/sys/wasm/js/odin.js b/core/sys/wasm/js/odin.js new file mode 100644 index 000000000..bf002da74 --- /dev/null +++ b/core/sys/wasm/js/odin.js @@ -0,0 +1,1983 @@ +"use strict"; + +(function() { + +function getElement(name) { + if (name) { + return document.getElementById(name); + } + return undefined; +} + +function stripNewline(str) { + return str.replace(/\n/, ' ') +} + +class WasmMemoryInterface { + constructor() { + this.memory = null; + this.exports = null; + this.listenerMap = {}; + + // Size (in bytes) of the integer type, should be 4 on `js_wasm32` and 8 on `js_wasm64p32` + this.intSize = 4; + } + + setIntSize(size) { + this.intSize = size; + } + + setMemory(memory) { + this.memory = memory; + } + + setExports(exports) { + this.exports = exports; + } + + get mem() { + return new DataView(this.memory.buffer); + } + + + loadF32Array(addr, len) { + let array = new Float32Array(this.memory.buffer, addr, len); + return array; + } + loadF64Array(addr, len) { + let array = new Float64Array(this.memory.buffer, addr, len); + return array; + } + loadU32Array(addr, len) { + let array = new Uint32Array(this.memory.buffer, addr, len); + return array; + } + loadI32Array(addr, len) { + let array = new Int32Array(this.memory.buffer, addr, len); + return array; + } + + + loadU8(addr) { return this.mem.getUint8 (addr); } + loadI8(addr) { return this.mem.getInt8 (addr); } + loadU16(addr) { return this.mem.getUint16 (addr, true); } + loadI16(addr) { return this.mem.getInt16 (addr, true); } + loadU32(addr) { return this.mem.getUint32 (addr, true); } + loadI32(addr) { return this.mem.getInt32 (addr, true); } + loadU64(addr) { + const lo = this.mem.getUint32(addr + 0, true); + const hi = this.mem.getUint32(addr + 4, true); + return lo + hi*4294967296; + }; + loadI64(addr) { + const lo = this.mem.getUint32(addr + 0, true); + const hi = this.mem.getInt32 (addr + 4, true); + return lo + hi*4294967296; + }; + loadF32(addr) { return this.mem.getFloat32(addr, true); } + loadF64(addr) { return this.mem.getFloat64(addr, true); } + loadInt(addr) { + if (this.intSize == 8) { + return this.loadI64(addr); + } else if (this.intSize == 4) { + return this.loadI32(addr); + } else { + throw new Error('Unhandled `intSize`, expected `4` or `8`'); + } + }; + loadUint(addr) { + if (this.intSize == 8) { + return this.loadU64(addr); + } else if (this.intSize == 4) { + return this.loadU32(addr); + } else { + throw new Error('Unhandled `intSize`, expected `4` or `8`'); + } + }; + loadPtr(addr) { return this.loadU32(addr); } + + loadB32(addr) { + return this.loadU32(addr) != 0; + } + + loadBytes(ptr, len) { + return new Uint8Array(this.memory.buffer, ptr, Number(len)); + } + + loadString(ptr, len) { + const bytes = this.loadBytes(ptr, Number(len)); + return new TextDecoder().decode(bytes); + } + + loadCstring(ptr) { + const start = this.loadPtr(ptr); + if (start == 0) { + return null; + } + let len = 0; + for (; this.mem.getUint8(start+len) != 0; len += 1) {} + return this.loadString(start, len); + } + + storeU8(addr, value) { this.mem.setUint8 (addr, value); } + storeI8(addr, value) { this.mem.setInt8 (addr, value); } + storeU16(addr, value) { this.mem.setUint16 (addr, value, true); } + storeI16(addr, value) { this.mem.setInt16 (addr, value, true); } + storeU32(addr, value) { this.mem.setUint32 (addr, value, true); } + storeI32(addr, value) { this.mem.setInt32 (addr, value, true); } + storeU64(addr, value) { + this.mem.setUint32(addr + 0, Number(value), true); + + let div = 4294967296; + if (typeof value == 'bigint') { + div = BigInt(div); + } + + this.mem.setUint32(addr + 4, Math.floor(Number(value / div)), true); + } + storeI64(addr, value) { + this.mem.setUint32(addr + 0, Number(value), true); + + let div = 4294967296; + if (typeof value == 'bigint') { + div = BigInt(div); + } + + this.mem.setInt32(addr + 4, Math.floor(Number(value / div)), true); + } + storeF32(addr, value) { this.mem.setFloat32(addr, value, true); } + storeF64(addr, value) { this.mem.setFloat64(addr, value, true); } + storeInt(addr, value) { + if (this.intSize == 8) { + this.storeI64(addr, value); + } else if (this.intSize == 4) { + this.storeI32(addr, value); + } else { + throw new Error('Unhandled `intSize`, expected `4` or `8`'); + } + } + storeUint(addr, value) { + if (this.intSize == 8) { + this.storeU64(addr, value); + } else if (this.intSize == 4) { + this.storeU32(addr, value); + } else { + throw new Error('Unhandled `intSize`, expected `4` or `8`'); + } + } + + // Returned length might not be the same as `value.length` if non-ascii strings are given. + storeString(addr, value) { + const src = new TextEncoder().encode(value); + const dst = new Uint8Array(this.memory.buffer, addr, src.length); + dst.set(src); + return src.length; + } +}; + +class WebGLInterface { + constructor(wasmMemoryInterface) { + this.wasmMemoryInterface = wasmMemoryInterface; + this.ctxElement = null; + this.ctx = null; + this.ctxVersion = 1.0; + this.counter = 1; + this.lastError = 0; + this.buffers = []; + this.mappedBuffers = {}; + this.programs = []; + this.framebuffers = []; + this.renderbuffers = []; + this.textures = []; + this.uniforms = []; + this.shaders = []; + this.vaos = []; + this.contexts = []; + this.currentContext = null; + this.offscreenCanvases = {}; + this.timerQueriesEXT = []; + this.queries = []; + this.samplers = []; + this.transformFeedbacks = []; + this.syncs = []; + this.programInfos = {}; + } + + get mem() { + return this.wasmMemoryInterface + } + + setCurrentContext(element, contextSettings) { + if (!element) { + return false; + } + if (this.ctxElement == element) { + return true; + } + + contextSettings = contextSettings ?? {}; + this.ctx = element.getContext("webgl2", contextSettings) || element.getContext("webgl", contextSettings); + if (!this.ctx) { + return false; + } + this.ctxElement = element; + if (this.ctx.getParameter(0x1F02).indexOf("WebGL 2.0") !== -1) { + this.ctxVersion = 2.0; + } else { + this.ctxVersion = 1.0; + } + return true; + } + + assertWebGL2() { + if (this.ctxVersion < 2) { + throw new Error("WebGL2 procedure called in a canvas without a WebGL2 context"); + } + } + getNewId(table) { + for (var ret = this.counter++, i = table.length; i < ret; i++) { + table[i] = null; + } + return ret; + } + recordError(errorCode) { + this.lastError || (this.lastError = errorCode); + } + populateUniformTable(program) { + let p = this.programs[program]; + this.programInfos[program] = { + uniforms: {}, + maxUniformLength: 0, + maxAttributeLength: -1, + maxUniformBlockNameLength: -1, + }; + for (let ptable = this.programInfos[program], utable = ptable.uniforms, numUniforms = this.ctx.getProgramParameter(p, this.ctx.ACTIVE_UNIFORMS), i = 0; i < numUniforms; ++i) { + let u = this.ctx.getActiveUniform(p, i); + let name = u.name; + if (ptable.maxUniformLength = Math.max(ptable.maxUniformLength, name.length + 1), name.indexOf("]", name.length - 1) !== -1) { + name = name.slice(0, name.lastIndexOf("[")); + } + let loc = this.ctx.getUniformLocation(p, name); + if (loc !== null) { + let id = this.getNewId(this.uniforms); + utable[name] = [u.size, id], this.uniforms[id] = loc; + for (let j = 1; j < u.size; ++j) { + let n = name + "[" + j + "]"; + let loc = this.ctx.getUniformLocation(p, n); + let id = this.getNewId(this.uniforms); + this.uniforms[id] = loc; + } + } + } + } + getSource(shader, strings_ptr, strings_length) { + const stringSize = this.mem.intSize*2; + let source = ""; + for (let i = 0; i < strings_length; i++) { + let ptr = this.mem.loadPtr(strings_ptr + i*stringSize); + let len = this.mem.loadPtr(strings_ptr + i*stringSize + 4); + let str = this.mem.loadString(ptr, len); + source += str; + } + return source; + } + + getWebGL1Interface() { + return { + SetCurrentContextById: (name_ptr, name_len) => { + let name = this.mem.loadString(name_ptr, name_len); + let element = getElement(name); + return this.setCurrentContext(element, {alpha: true, antialias: true, depth: true, premultipliedAlpha: true}); + }, + CreateCurrentContextById: (name_ptr, name_len, attributes) => { + let name = this.mem.loadString(name_ptr, name_len); + let element = getElement(name); + + let contextSettings = { + alpha: !(attributes & (1<<0)), + antialias: !(attributes & (1<<1)), + depth: !(attributes & (1<<2)), + failIfMajorPerformanceCaveat: !!(attributes & (1<<3)), + premultipliedAlpha: !(attributes & (1<<4)), + preserveDrawingBuffer: !!(attributes & (1<<5)), + stencil: !!(attributes & (1<<6)), + desynchronized: !!(attributes & (1<<7)), + }; + + return this.setCurrentContext(element, contextSettings); + }, + GetCurrentContextAttributes: () => { + if (!this.ctx) { + return 0; + } + let attrs = this.ctx.getContextAttributes(); + let res = 0; + if (!attrs.alpha) res |= 1<<0; + if (!attrs.antialias) res |= 1<<1; + if (!attrs.depth) res |= 1<<2; + if (attrs.failIfMajorPerformanceCaveat) res |= 1<<3; + if (!attrs.premultipliedAlpha) res |= 1<<4; + if (attrs.preserveDrawingBuffer) res |= 1<<5; + if (attrs.stencil) res |= 1<<6; + if (attrs.desynchronized) res |= 1<<7; + return res; + }, + + DrawingBufferWidth: () => this.ctx.drawingBufferWidth, + DrawingBufferHeight: () => this.ctx.drawingBufferHeight, + + IsExtensionSupported: (name_ptr, name_len) => { + let name = this.mem.loadString(name_ptr, name_len); + let extensions = this.ctx.getSupportedExtensions(); + return extensions.indexOf(name) !== -1 + }, + + + GetError: () => { + let err = this.lastError; + this.recordError(0); + if (err) { + return err; + } + return this.ctx.getError(); + }, + + GetWebGLVersion: (major_ptr, minor_ptr) => { + let version = this.ctx.getParameter(0x1F02); + if (version.indexOf("WebGL 2.0") !== -1) { + this.mem.storeI32(major_ptr, 2); + this.mem.storeI32(minor_ptr, 0); + return; + } + + this.mem.storeI32(major_ptr, 1); + this.mem.storeI32(minor_ptr, 0); + }, + GetESVersion: (major_ptr, minor_ptr) => { + let version = this.ctx.getParameter(0x1F02); + if (version.indexOf("OpenGL ES 3.0") !== -1) { + this.mem.storeI32(major_ptr, 3); + this.mem.storeI32(minor_ptr, 0); + return; + } + + this.mem.storeI32(major_ptr, 2); + this.mem.storeI32(minor_ptr, 0); + }, + + + ActiveTexture: (x) => { + this.ctx.activeTexture(x); + }, + AttachShader: (program, shader) => { + this.ctx.attachShader(this.programs[program], this.shaders[shader]); + }, + BindAttribLocation: (program, index, name_ptr, name_len) => { + let name = this.mem.loadString(name_ptr, name_len); + this.ctx.bindAttribLocation(this.programs[program], index, name) + }, + BindBuffer: (target, buffer) => { + let bufferObj = buffer ? this.buffers[buffer] : null; + if (target == 35051) { + this.ctx.currentPixelPackBufferBinding = buffer; + } else { + if (target == 35052) { + this.ctx.currentPixelUnpackBufferBinding = buffer; + } + this.ctx.bindBuffer(target, bufferObj) + } + }, + BindFramebuffer: (target, framebuffer) => { + this.ctx.bindFramebuffer(target, framebuffer ? this.framebuffers[framebuffer] : null) + }, + BindTexture: (target, texture) => { + this.ctx.bindTexture(target, texture ? this.textures[texture] : null) + }, + BlendColor: (red, green, blue, alpha) => { + this.ctx.blendColor(red, green, blue, alpha); + }, + BlendEquation: (mode) => { + this.ctx.blendEquation(mode); + }, + BlendFunc: (sfactor, dfactor) => { + this.ctx.blendFunc(sfactor, dfactor); + }, + BlendFuncSeparate: (srcRGB, dstRGB, srcAlpha, dstAlpha) => { + this.ctx.blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha); + }, + + + BufferData: (target, size, data, usage) => { + if (data) { + this.ctx.bufferData(target, this.mem.loadBytes(data, size), usage); + } else { + this.ctx.bufferData(target, size, usage); + } + }, + BufferSubData: (target, offset, size, data) => { + if (data) { + this.ctx.bufferSubData(target, offset, this.mem.loadBytes(data, size)); + } else { + this.ctx.bufferSubData(target, offset, null); + } + }, + + + Clear: (x) => { + this.ctx.clear(x); + }, + ClearColor: (r, g, b, a) => { + this.ctx.clearColor(r, g, b, a); + }, + ClearDepth: (x) => { + this.ctx.clearDepth(x); + }, + ClearStencil: (x) => { + this.ctx.clearStencil(x); + }, + ColorMask: (r, g, b, a) => { + this.ctx.colorMask(!!r, !!g, !!b, !!a); + }, + CompileShader: (shader) => { + this.ctx.compileShader(this.shaders[shader]); + }, + + + CompressedTexImage2D: (target, level, internalformat, width, height, border, imageSize, data) => { + if (data) { + this.ctx.compressedTexImage2D(target, level, internalformat, width, height, border, this.mem.loadBytes(data, imageSize)); + } else { + this.ctx.compressedTexImage2D(target, level, internalformat, width, height, border, null); + } + }, + CompressedTexSubImage2D: (target, level, xoffset, yoffset, width, height, format, imageSize, data) => { + if (data) { + this.ctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, this.mem.loadBytes(data, imageSize)); + } else { + this.ctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, null); + } + }, + + CopyTexImage2D: (target, level, internalformat, x, y, width, height, border) => { + this.ctx.copyTexImage2D(target, level, internalformat, x, y, width, height, border); + }, + CopyTexSubImage2D: (target, level, xoffset, yoffset, x, y, width, height) => { + this.ctx.copyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); + }, + + + CreateBuffer: () => { + let buffer = this.ctx.createBuffer(); + if (!buffer) { + this.recordError(1282); + return 0; + } + let id = this.getNewId(this.buffers); + buffer.name = id + this.buffers[id] = buffer; + return id; + }, + CreateFramebuffer: () => { + let buffer = this.ctx.createFramebuffer(); + let id = this.getNewId(this.framebuffers); + buffer.name = id + this.framebuffers[id] = buffer; + return id; + }, + CreateProgram: () => { + let program = this.ctx.createProgram(); + let id = this.getNewId(this.programs); + program.name = id; + this.programs[id] = program; + return id; + }, + CreateRenderbuffer: () => { + let buffer = this.ctx.createRenderbuffer(); + let id = this.getNewId(this.renderbuffers); + buffer.name = id; + this.renderbuffers[id] = buffer; + return id; + }, + CreateShader: (shaderType) => { + let shader = this.ctx.createShader(shaderType); + let id = this.getNewId(this.shaders); + shader.name = id; + this.shaders[id] = shader; + return id; + }, + CreateTexture: () => { + let texture = this.ctx.createTexture(); + if (!texture) { + this.recordError(1282) + return 0; + } + let id = this.getNewId(this.textures); + texture.name = id; + this.textures[id] = texture; + return id; + }, + + + CullFace: (mode) => { + this.ctx.cullFace(mode); + }, + + + DeleteBuffer: (id) => { + let obj = this.buffers[id]; + if (obj && id != 0) { + this.ctx.deleteBuffer(obj); + this.buffers[id] = null; + } + }, + DeleteFramebuffer: (id) => { + let obj = this.framebuffers[id]; + if (obj && id != 0) { + this.ctx.deleteFramebuffer(obj); + this.framebuffers[id] = null; + } + }, + DeleteProgram: (id) => { + let obj = this.programs[id]; + if (obj && id != 0) { + this.ctx.deleteProgram(obj); + this.programs[id] = null; + } + }, + DeleteRenderbuffer: (id) => { + let obj = this.renderbuffers[id]; + if (obj && id != 0) { + this.ctx.deleteRenderbuffer(obj); + this.renderbuffers[id] = null; + } + }, + DeleteShader: (id) => { + let obj = this.shaders[id]; + if (obj && id != 0) { + this.ctx.deleteShader(obj); + this.shaders[id] = null; + } + }, + DeleteTexture: (id) => { + let obj = this.textures[id]; + if (obj && id != 0) { + this.ctx.deleteTexture(obj); + this.textures[id] = null; + } + }, + + + DepthFunc: (func) => { + this.ctx.depthFunc(func); + }, + DepthMask: (flag) => { + this.ctx.depthMask(!!flag); + }, + DepthRange: (zNear, zFar) => { + this.ctx.depthRange(zNear, zFar); + }, + DetachShader: (program, shader) => { + this.ctx.detachShader(this.programs[program], this.shaders[shader]); + }, + Disable: (cap) => { + this.ctx.disable(cap); + }, + DisableVertexAttribArray: (index) => { + this.ctx.disableVertexAttribArray(index); + }, + DrawArrays: (mode, first, count) => { + this.ctx.drawArrays(mode, first, count); + }, + DrawElements: (mode, count, type, indices) => { + this.ctx.drawElements(mode, count, type, indices); + }, + + + Enable: (cap) => { + this.ctx.enable(cap); + }, + EnableVertexAttribArray: (index) => { + this.ctx.enableVertexAttribArray(index); + }, + Finish: () => { + this.ctx.finish(); + }, + Flush: () => { + this.ctx.flush(); + }, + FramebufferRenderbuffer: (target, attachment, renderbuffertarget, renderbuffer) => { + this.ctx.framebufferRenderbuffer(target, attachment, renderbuffertarget, this.renderbuffers[renderbuffer]); + }, + FramebufferTexture2D: (target, attachment, textarget, texture, level) => { + this.ctx.framebufferTexture2D(target, attachment, textarget, this.textures[texture], level); + }, + FrontFace: (mode) => { + this.ctx.frontFace(mode); + }, + + + GenerateMipmap: (target) => { + this.ctx.generateMipmap(target); + }, + + + GetAttribLocation: (program, name_ptr, name_len) => { + let name = this.mem.loadString(name_ptr, name_len); + return this.ctx.getAttribLocation(this.programs[program], name); + }, + + + GetParameter: (pname) => { + return this.ctx.getParameter(pname); + }, + GetProgramParameter: (program, pname) => { + return this.ctx.getProgramParameter(this.programs[program], pname) + }, + GetProgramInfoLog: (program, buf_ptr, buf_len, length_ptr) => { + let log = this.ctx.getProgramInfoLog(this.programs[program]); + if (log === null) { + log = "(unknown error)"; + } + if (buf_len > 0 && buf_ptr) { + let n = Math.min(buf_len, log.length); + log = log.substring(0, n); + this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(log)) + + this.mem.storeInt(length_ptr, n); + } + }, + GetShaderInfoLog: (shader, buf_ptr, buf_len, length_ptr) => { + let log = this.ctx.getShaderInfoLog(this.shaders[shader]); + if (log === null) { + log = "(unknown error)"; + } + if (buf_len > 0 && buf_ptr) { + let n = Math.min(buf_len, log.length); + log = log.substring(0, n); + this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(log)) + + this.mem.storeInt(length_ptr, n); + } + }, + GetShaderiv: (shader, pname, p) => { + if (p) { + if (pname == 35716) { + let log = this.ctx.getShaderInfoLog(this.shaders[shader]); + if (log === null) { + log = "(unknown error)"; + } + this.mem.storeInt(p, log.length+1); + } else if (pname == 35720) { + let source = this.ctx.getShaderSource(this.shaders[shader]); + let sourceLength = (source === null || source.length == 0) ? 0 : source.length+1; + this.mem.storeInt(p, sourceLength); + } else { + let param = this.ctx.getShaderParameter(this.shaders[shader], pname); + this.mem.storeI32(p, param); + } + } else { + this.recordError(1281); + } + }, + + + GetUniformLocation: (program, name_ptr, name_len) => { + let name = this.mem.loadString(name_ptr, name_len); + let arrayOffset = 0; + if (name.indexOf("]", name.length - 1) !== -1) { + let ls = name.lastIndexOf("["), + arrayIndex = name.slice(ls + 1, -1); + if (arrayIndex.length > 0 && (arrayOffset = parseInt(arrayIndex)) < 0) { + return -1; + } + name = name.slice(0, ls) + } + var ptable = this.programInfos[program]; + if (!ptable) { + return -1; + } + var uniformInfo = ptable.uniforms[name]; + return (uniformInfo && arrayOffset < uniformInfo[0]) ? uniformInfo[1] + arrayOffset : -1 + }, + + + GetVertexAttribOffset: (index, pname) => { + return this.ctx.getVertexAttribOffset(index, pname); + }, + + + Hint: (target, mode) => { + this.ctx.hint(target, mode); + }, + + + IsBuffer: (buffer) => this.ctx.isBuffer(this.buffers[buffer]), + IsEnabled: (cap) => this.ctx.isEnabled(cap), + IsFramebuffer: (framebuffer) => this.ctx.isFramebuffer(this.framebuffers[framebuffer]), + IsProgram: (program) => this.ctx.isProgram(this.programs[program]), + IsRenderbuffer: (renderbuffer) => this.ctx.isRenderbuffer(this.renderbuffers[renderbuffer]), + IsShader: (shader) => this.ctx.isShader(this.shaders[shader]), + IsTexture: (texture) => this.ctx.isTexture(this.textures[texture]), + + LineWidth: (width) => { + this.ctx.lineWidth(width); + }, + LinkProgram: (program) => { + this.ctx.linkProgram(this.programs[program]); + this.programInfos[program] = null; + this.populateUniformTable(program); + }, + PixelStorei: (pname, param) => { + this.ctx.pixelStorei(pname, param); + }, + PolygonOffset: (factor, units) => { + this.ctx.polygonOffset(factor, units); + }, + + + ReadnPixels: (x, y, width, height, format, type, bufSize, data) => { + this.ctx.readPixels(x, y, width, height, format, type, this.mem.loadBytes(data, bufSize)); + }, + RenderbufferStorage: (target, internalformat, width, height) => { + this.ctx.renderbufferStorage(target, internalformat, width, height); + }, + SampleCoverage: (value, invert) => { + this.ctx.sampleCoverage(value, !!invert); + }, + Scissor: (x, y, width, height) => { + this.ctx.scissor(x, y, width, height); + }, + ShaderSource: (shader, strings_ptr, strings_length) => { + let source = this.getSource(shader, strings_ptr, strings_length); + this.ctx.shaderSource(this.shaders[shader], source); + }, + + StencilFunc: (func, ref, mask) => { + this.ctx.stencilFunc(func, ref, mask); + }, + StencilFuncSeparate: (face, func, ref, mask) => { + this.ctx.stencilFuncSeparate(face, func, ref, mask); + }, + StencilMask: (mask) => { + this.ctx.stencilMask(mask); + }, + StencilMaskSeparate: (face, mask) => { + this.ctx.stencilMaskSeparate(face, mask); + }, + StencilOp: (fail, zfail, zpass) => { + this.ctx.stencilOp(fail, zfail, zpass); + }, + StencilOpSeparate: (face, fail, zfail, zpass) => { + this.ctx.stencilOpSeparate(face, fail, zfail, zpass); + }, + + + TexImage2D: (target, level, internalformat, width, height, border, format, type, size, data) => { + if (data) { + this.ctx.texImage2D(target, level, internalformat, width, height, border, format, type, this.mem.loadBytes(data, size)); + } else { + this.ctx.texImage2D(target, level, internalformat, width, height, border, format, type, null); + } + }, + TexParameterf: (target, pname, param) => { + this.ctx.texParameterf(target, pname, param); + }, + TexParameteri: (target, pname, param) => { + this.ctx.texParameteri(target, pname, param); + }, + TexSubImage2D: (target, level, xoffset, yoffset, width, height, format, type, size, data) => { + this.ctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, this.mem.loadBytes(data, size)); + }, + + + Uniform1f: (location, v0) => { this.ctx.uniform1f(this.uniforms[location], v0); }, + Uniform2f: (location, v0, v1) => { this.ctx.uniform2f(this.uniforms[location], v0, v1); }, + Uniform3f: (location, v0, v1, v2) => { this.ctx.uniform3f(this.uniforms[location], v0, v1, v2); }, + Uniform4f: (location, v0, v1, v2, v3) => { this.ctx.uniform4f(this.uniforms[location], v0, v1, v2, v3); }, + + Uniform1i: (location, v0) => { this.ctx.uniform1i(this.uniforms[location], v0); }, + Uniform2i: (location, v0, v1) => { this.ctx.uniform2i(this.uniforms[location], v0, v1); }, + Uniform3i: (location, v0, v1, v2) => { this.ctx.uniform3i(this.uniforms[location], v0, v1, v2); }, + Uniform4i: (location, v0, v1, v2, v3) => { this.ctx.uniform4i(this.uniforms[location], v0, v1, v2, v3); }, + + UniformMatrix2fv: (location, addr) => { + let array = this.mem.loadF32Array(addr, 2*2); + this.ctx.uniformMatrix2fv(this.uniforms[location], false, array); + }, + UniformMatrix3fv: (location, addr) => { + let array = this.mem.loadF32Array(addr, 3*3); + this.ctx.uniformMatrix3fv(this.uniforms[location], false, array); + }, + UniformMatrix4fv: (location, addr) => { + let array = this.mem.loadF32Array(addr, 4*4); + this.ctx.uniformMatrix4fv(this.uniforms[location], false, array); + }, + + UseProgram: (program) => { + if (program) this.ctx.useProgram(this.programs[program]); + }, + ValidateProgram: (program) => { + if (program) this.ctx.validateProgram(this.programs[program]); + }, + + + VertexAttrib1f: (index, x) => { + this.ctx.vertexAttrib1f(index, x); + }, + VertexAttrib2f: (index, x, y) => { + this.ctx.vertexAttrib2f(index, x, y); + }, + VertexAttrib3f: (index, x, y, z) => { + this.ctx.vertexAttrib3f(index, x, y, z); + }, + VertexAttrib4f: (index, x, y, z, w) => { + this.ctx.vertexAttrib4f(index, x, y, z, w); + }, + VertexAttribPointer: (index, size, type, normalized, stride, ptr) => { + this.ctx.vertexAttribPointer(index, size, type, !!normalized, stride, ptr); + }, + + Viewport: (x, y, w, h) => { + this.ctx.viewport(x, y, w, h); + }, + }; + } + + getWebGL2Interface() { + return { + /* Buffer objects */ + CopyBufferSubData: (readTarget, writeTarget, readOffset, writeOffset, size) => { + this.assertWebGL2(); + this.ctx.copyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size); + }, + GetBufferSubData: (target, srcByteOffset, dst_buffer_ptr, dst_buffer_len, dstOffset, length) => { + this.assertWebGL2(); + this.ctx.getBufferSubData(target, srcByteOffset, this.mem.loadBytes(dst_buffer_ptr, dst_buffer_len), dstOffset, length); + }, + + /* Framebuffer objects */ + BlitFramebuffer: (srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter) => { + this.assertWebGL2(); + this.ctx.blitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); + }, + FramebufferTextureLayer: (target, attachment, texture, level, layer) => { + this.assertWebGL2(); + this.ctx.framebufferTextureLayer(target, attachment, this.textures[texture], level, layer); + }, + InvalidateFramebuffer: (target, attachments_ptr, attachments_len) => { + this.assertWebGL2(); + let attachments = this.mem.loadU32Array(attachments_ptr, attachments_len); + this.ctx.invalidateFramebuffer(target, attachments); + }, + InvalidateSubFramebuffer: (target, attachments_ptr, attachments_len, x, y, width, height) => { + this.assertWebGL2(); + let attachments = this.mem.loadU32Array(attachments_ptr, attachments_len); + this.ctx.invalidateSubFramebuffer(target, attachments, x, y, width, height); + }, + ReadBuffer: (src) => { + this.assertWebGL2(); + this.ctx.readBuffer(src); + }, + + /* Renderbuffer objects */ + RenderbufferStorageMultisample: (target, samples, internalformat, width, height) => { + this.assertWebGL2(); + this.ctx.renderbufferStorageMultisample(target, samples, internalformat, width, height); + }, + + /* Texture objects */ + + TexStorage3D: (target, levels, internalformat, width, height, depth) => { + this.assertWebGL2(); + this.ctx.texStorage3D(target, levels, internalformat, width, height, depth); + }, + TexImage3D: (target, level, internalformat, width, height, depth, border, format, type, size, data) => { + this.assertWebGL2(); + if (data) { + this.ctx.texImage3D(target, level, internalformat, width, height, depth, border, format, type, this.mem.loadBytes(data, size)); + } else { + this.ctx.texImage3D(target, level, internalformat, width, height, depth, border, format, type, null); + } + }, + TexSubImage3D: (target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, size, data) => { + this.assertWebGL2(); + this.ctx.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, this.mem.loadBytes(data, size)); + }, + CompressedTexImage3D: (target, level, internalformat, width, height, depth, border, imageSize, data) => { + this.assertWebGL2(); + if (data) { + this.ctx.compressedTexImage3D(target, level, internalformat, width, height, depth, border, this.mem.loadBytes(data, imageSize)); + } else { + this.ctx.compressedTexImage3D(target, level, internalformat, width, height, depth, border, null); + } + }, + CompressedTexSubImage3D: (target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data) => { + this.assertWebGL2(); + if (data) { + this.ctx.compressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, this.mem.loadBytes(data, imageSize)); + } else { + this.ctx.compressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, null); + } + }, + + CopyTexSubImage3D: (target, level, xoffset, yoffset, zoffset, x, y, width, height) => { + this.assertWebGL2(); + this.ctx.copyTexSubImage3D(target, level, xoffset, yoffset, zoffset, x, y, width, height); + }, + + /* Programs and shaders */ + GetFragDataLocation: (program, name_ptr, name_len) => { + this.assertWebGL2(); + return this.ctx.getFragDataLocation(this.programs[program], this.mem.loadString(name_ptr, name_len)); + }, + + /* Uniforms */ + Uniform1ui: (location, v0) => { + this.assertWebGL2(); + this.ctx.uniform1ui(this.uniforms[location], v0); + }, + Uniform2ui: (location, v0, v1) => { + this.assertWebGL2(); + this.ctx.uniform2ui(this.uniforms[location], v0, v1); + }, + Uniform3ui: (location, v0, v1, v2) => { + this.assertWebGL2(); + this.ctx.uniform3ui(this.uniforms[location], v0, v1, v2); + }, + Uniform4ui: (location, v0, v1, v2, v3) => { + this.assertWebGL2(); + this.ctx.uniform4ui(this.uniforms[location], v0, v1, v2, v3); + }, + + UniformMatrix3x2fv: (location, addr) => { + this.assertWebGL2(); + let array = this.mem.loadF32Array(addr, 3*2); + this.ctx.uniformMatrix3x2fv(this.uniforms[location], false, array); + }, + UniformMatrix4x2fv: (location, addr) => { + this.assertWebGL2(); + let array = this.mem.loadF32Array(addr, 4*2); + this.ctx.uniformMatrix4x2fv(this.uniforms[location], false, array); + }, + UniformMatrix2x3fv: (location, addr) => { + this.assertWebGL2(); + let array = this.mem.loadF32Array(addr, 2*3); + this.ctx.uniformMatrix2x3fv(this.uniforms[location], false, array); + }, + UniformMatrix4x3fv: (location, addr) => { + this.assertWebGL2(); + let array = this.mem.loadF32Array(addr, 4*3); + this.ctx.uniformMatrix4x3fv(this.uniforms[location], false, array); + }, + UniformMatrix2x4fv: (location, addr) => { + this.assertWebGL2(); + let array = this.mem.loadF32Array(addr, 2*4); + this.ctx.uniformMatrix2x4fv(this.uniforms[location], false, array); + }, + UniformMatrix3x4fv: (location, addr) => { + this.assertWebGL2(); + let array = this.mem.loadF32Array(addr, 3*4); + this.ctx.uniformMatrix3x4fv(this.uniforms[location], false, array); + }, + + /* Vertex attribs */ + VertexAttribI4i: (index, x, y, z, w) => { + this.assertWebGL2(); + this.ctx.vertexAttribI4i(index, x, y, z, w); + }, + VertexAttribI4ui: (index, x, y, z, w) => { + this.assertWebGL2(); + this.ctx.vertexAttribI4ui(index, x, y, z, w); + }, + VertexAttribIPointer: (index, size, type, stride, offset) => { + this.assertWebGL2(); + this.ctx.vertexAttribIPointer(index, size, type, stride, offset); + }, + + /* Writing to the drawing buffer */ + VertexAttribDivisor: (index, divisor) => { + this.assertWebGL2(); + this.ctx.vertexAttribDivisor(index, divisor); + }, + DrawArraysInstanced: (mode, first, count, instanceCount) => { + this.assertWebGL2(); + this.ctx.drawArraysInstanced(mode, first, count, instanceCount); + }, + DrawElementsInstanced: (mode, count, type, offset, instanceCount) => { + this.assertWebGL2(); + this.ctx.drawElementsInstanced(mode, count, type, offset, instanceCount); + }, + DrawRangeElements: (mode, start, end, count, type, offset) => { + this.assertWebGL2(); + this.ctx.drawRangeElements(mode, start, end, count, type, offset); + }, + + /* Multiple Render Targets */ + DrawBuffers: (buffers_ptr, buffers_len) => { + this.assertWebGL2(); + let array = this.mem.loadU32Array(buffers_ptr, buffers_len); + this.ctx.drawBuffers(array); + }, + ClearBufferfv: (buffer, drawbuffer, values_ptr, values_len) => { + this.assertWebGL2(); + let array = this.mem.loadF32Array(values_ptr, values_len); + this.ctx.clearBufferfv(buffer, drawbuffer, array); + }, + ClearBufferiv: (buffer, drawbuffer, values_ptr, values_len) => { + this.assertWebGL2(); + let array = this.mem.loadI32Array(values_ptr, values_len); + this.ctx.clearBufferiv(buffer, drawbuffer, array); + }, + ClearBufferuiv: (buffer, drawbuffer, values_ptr, values_len) => { + this.assertWebGL2(); + let array = this.mem.loadU32Array(values_ptr, values_len); + this.ctx.clearBufferuiv(buffer, drawbuffer, array); + }, + ClearBufferfi: (buffer, drawbuffer, depth, stencil) => { + this.assertWebGL2(); + this.ctx.clearBufferfi(buffer, drawbuffer, depth, stencil); + }, + + /* Query Objects */ + CreateQuery: () => { + this.assertWebGL2(); + let query = this.ctx.createQuery(); + let id = this.getNewId(this.queries); + query.name = id; + this.queries[id] = query; + return id; + }, + DeleteQuery: (id) => { + this.assertWebGL2(); + let obj = this.queries[id]; + if (obj && id != 0) { + this.ctx.deleteQuery(obj); + this.queries[id] = null; + } + }, + IsQuery: (query) => { + this.assertWebGL2(); + return this.ctx.isQuery(this.queries[query]); + }, + BeginQuery: (target, query) => { + this.assertWebGL2(); + this.ctx.beginQuery(target, this.queries[query]) + }, + EndQuery: (target) => { + this.assertWebGL2(); + this.ctx.endQuery(target); + }, + GetQuery: (target, pname) => { + this.assertWebGL2(); + let query = this.ctx.getQuery(target, pname); + if (!query) { + return 0; + } + if (this.queries.indexOf(query) !== -1) { + return query.name; + } + let id = this.getNewId(this.queries); + query.name = id; + this.queries[id] = query; + return id; + }, + + /* Sampler Objects */ + CreateSampler: () => { + this.assertWebGL2(); + let sampler = this.ctx.createSampler(); + let id = this.getNewId(this.samplers); + sampler.name = id; + this.samplers[id] = sampler; + return id; + }, + DeleteSampler: (id) => { + this.assertWebGL2(); + let obj = this.samplers[id]; + if (obj && id != 0) { + this.ctx.deleteSampler(obj); + this.samplers[id] = null; + } + }, + IsSampler: (sampler) => { + this.assertWebGL2(); + return this.ctx.isSampler(this.samplers[sampler]); + }, + BindSampler: (unit, sampler) => { + this.assertWebGL2(); + this.ctx.bindSampler(unit, this.samplers[sampler]); + }, + SamplerParameteri: (sampler, pname, param) => { + this.assertWebGL2(); + this.ctx.samplerParameteri(this.samplers[sampler], pname, param); + }, + SamplerParameterf: (sampler, pname, param) => { + this.assertWebGL2(); + this.ctx.samplerParameterf(this.samplers[sampler], pname, param); + }, + + /* Sync objects */ + FenceSync: (condition, flags) => { + this.assertWebGL2(); + let sync = this.ctx.fenceSync(condition, flags); + let id = this.getNewId(this.syncs); + sync.name = id; + this.syncs[id] = sync; + return id; + }, + IsSync: (sync) => { + this.assertWebGL2(); + return this.ctx.isSync(this.syncs[sync]); + }, + DeleteSync: (id) => { + this.assertWebGL2(); + let obj = this.syncs[id]; + if (obj && id != 0) { + this.ctx.deleteSampler(obj); + this.syncs[id] = null; + } + }, + ClientWaitSync: (sync, flags, timeout) => { + this.assertWebGL2(); + return this.ctx.clientWaitSync(this.syncs[sync], flags, timeout); + }, + WaitSync: (sync, flags, timeout) => { + this.assertWebGL2(); + this.ctx.waitSync(this.syncs[sync], flags, timeout) ; + }, + + + /* Transform Feedback */ + CreateTransformFeedback: () => { + this.assertWebGL2(); + let transformFeedback = this.ctx.createTransformFeedback(); + let id = this.getNewId(this.transformFeedbacks); + transformFeedback.name = id; + this.transformFeedbacks[id] = transformFeedback; + return id; + }, + DeleteTransformFeedback: (id) => { + this.assertWebGL2(); + let obj = this.transformFeedbacks[id]; + if (obj && id != 0) { + this.ctx.deleteTransformFeedback(obj); + this.transformFeedbacks[id] = null; + } + }, + IsTransformFeedback: (tf) => { + this.assertWebGL2(); + return this.ctx.isTransformFeedback(this.transformFeedbacks[tf]); + }, + BindTransformFeedback: (target, tf) => { + this.assertWebGL2(); + this.ctx.bindTransformFeedback(target, this.transformFeedbacks[tf]); + }, + BeginTransformFeedback: (primitiveMode) => { + this.assertWebGL2(); + this.ctx.beginTransformFeedback(primitiveMode); + }, + EndTransformFeedback: () => { + this.assertWebGL2(); + this.ctx.endTransformFeedback(); + }, + TransformFeedbackVaryings: (program, varyings_ptr, varyings_len, bufferMode) => { + this.assertWebGL2(); + const stringSize = this.mem.intSize*2; + let varyings = []; + for (let i = 0; i < varyings_len; i++) { + let ptr = this.mem.loadPtr(varyings_ptr + i*stringSize + 0*4); + let len = this.mem.loadPtr(varyings_ptr + i*stringSize + 1*4); + varyings.push(this.mem.loadString(ptr, len)); + } + this.ctx.transformFeedbackVaryings(this.programs[program], varyings, bufferMode); + }, + PauseTransformFeedback: () => { + this.assertWebGL2(); + this.ctx.pauseTransformFeedback(); + }, + ResumeTransformFeedback: () => { + this.assertWebGL2(); + this.ctx.resumeTransformFeedback(); + }, + + + /* Uniform Buffer Objects and Transform Feedback Buffers */ + BindBufferBase: (target, index, buffer) => { + this.assertWebGL2(); + this.ctx.bindBufferBase(target, index, this.buffers[buffer]); + }, + BindBufferRange: (target, index, buffer, offset, size) => { + this.assertWebGL2(); + this.ctx.bindBufferRange(target, index, this.buffers[buffer], offset, size); + }, + GetUniformBlockIndex: (program, uniformBlockName_ptr, uniformBlockName_len) => { + this.assertWebGL2(); + return this.ctx.getUniformBlockIndex(this.programs[program], this.mem.loadString(uniformBlockName_ptr, uniformBlockName_len)); + }, + // any getActiveUniformBlockParameter(WebGLProgram program, GLuint uniformBlockIndex, GLenum pname); + GetActiveUniformBlockName: (program, uniformBlockIndex, buf_ptr, buf_len, length_ptr) => { + this.assertWebGL2(); + let name = this.ctx.getActiveUniformBlockName(this.programs[program], uniformBlockIndex); + + let n = Math.min(buf_len, name.length); + name = name.substring(0, n); + this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(name)) + this.mem.storeInt(length_ptr, n); + }, + UniformBlockBinding: (program, uniformBlockIndex, uniformBlockBinding) => { + this.assertWebGL2(); + this.ctx.uniformBlockBinding(this.programs[program], uniformBlockIndex, uniformBlockBinding); + }, + + /* Vertex Array Objects */ + CreateVertexArray: () => { + this.assertWebGL2(); + let vao = this.ctx.createVertexArray(); + let id = this.getNewId(this.vaos); + vao.name = id; + this.vaos[id] = vao; + return id; + }, + DeleteVertexArray: (id) => { + this.assertWebGL2(); + let obj = this.vaos[id]; + if (obj && id != 0) { + this.ctx.deleteVertexArray(obj); + this.vaos[id] = null; + } + }, + IsVertexArray: (vertexArray) => { + this.assertWebGL2(); + return this.ctx.isVertexArray(this.vaos[vertexArray]); + }, + BindVertexArray: (vertexArray) => { + this.assertWebGL2(); + this.ctx.bindVertexArray(this.vaos[vertexArray]); + }, + }; + } +}; + + +function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory, eventQueue, event_temp) { + const MAX_INFO_CONSOLE_LINES = 512; + let infoConsoleLines = new Array(); + let currentLine = {}; + currentLine[false] = ""; + currentLine[true] = ""; + let prevIsError = false; + + const writeToConsole = (line, isError) => { + if (!line) { + return; + } + + const println = (text, forceIsError) => { + let style = [ + "color: #eee", + "background-color: #d20", + "padding: 2px 4px", + "border-radius: 2px", + ].join(";"); + let doIsError = isError; + if (forceIsError !== undefined) { + doIsError = forceIsError; + } + + if (doIsError) { + console.log("%c"+text, style); + } else { + console.log(text); + } + + }; + + // Print to console + if (line == "\n") { + println(currentLine[isError]); + currentLine[isError] = ""; + } else if (!line.includes("\n")) { + currentLine[isError] = currentLine[isError].concat(line); + } else { + let lines = line.split("\n"); + let printLast = lines.length > 1 && line.endsWith("\n"); + println(currentLine[isError].concat(lines[0])); + currentLine[isError] = ""; + for (let i = 1; i < lines.length-1; i++) { + println(lines[i]); + } + let last = lines[lines.length-1]; + if (printLast) { + println(last); + } else { + currentLine[isError] = last; + } + } + + if (prevIsError != isError) { + if (prevIsError) { + println(currentLine[prevIsError], prevIsError); + currentLine[prevIsError] = ""; + } + } + prevIsError = isError; + + + // HTML based console + if (!consoleElement) { + return; + } + const wrap = (x) => { + if (isError) { + return '<span style="color:#f21">'+x+'</span>'; + } + return x; + }; + + if (line == "\n") { + infoConsoleLines.push(line); + } else if (!line.includes("\n")) { + let prevLine = ""; + if (infoConsoleLines.length > 0) { + prevLine = infoConsoleLines.pop(); + } + infoConsoleLines.push(prevLine.concat(wrap(line))); + } else { + let lines = line.split("\n"); + let lastHasNewline = lines.length > 1 && line.endsWith("\n"); + + let prevLine = ""; + if (infoConsoleLines.length > 0) { + prevLine = infoConsoleLines.pop(); + } + infoConsoleLines.push(prevLine.concat(wrap(lines[0]).concat("\n"))); + + for (let i = 1; i < lines.length-1; i++) { + infoConsoleLines.push(wrap(lines[i]).concat("\n")); + } + let last = lines[lines.length-1]; + if (lastHasNewline) { + infoConsoleLines.push(last.concat("\n")); + } else { + infoConsoleLines.push(last); + } + } + + if (infoConsoleLines.length > MAX_INFO_CONSOLE_LINES) { + infoConsoleLines.shift(MAX_INFO_CONSOLE_LINES); + } + + let data = ""; + for (let i = 0; i < infoConsoleLines.length; i++) { + data = data.concat(infoConsoleLines[i]); + } + + let info = consoleElement; + info.innerHTML = data; + info.scrollTop = info.scrollHeight; + }; + + let webglContext = new WebGLInterface(wasmMemoryInterface); + + const env = {}; + + if (memory) { + env.memory = memory; + } + + return { + env, + "odin_env": { + write: (fd, ptr, len) => { + const str = wasmMemoryInterface.loadString(ptr, len); + if (fd == 1) { + writeToConsole(str, false); + return; + } else if (fd == 2) { + writeToConsole(str, true); + return; + } else { + throw new Error("Invalid fd to 'write'" + stripNewline(str)); + } + }, + trap: () => { throw new Error() }, + alert: (ptr, len) => { alert(wasmMemoryInterface.loadString(ptr, len)) }, + abort: () => { Module.abort() }, + evaluate: (str_ptr, str_len) => { eval.call(null, wasmMemoryInterface.loadString(str_ptr, str_len)); }, + + // return a bigint to be converted to i64 + time_now: () => BigInt(Date.now()), + tick_now: () => performance.now(), + time_sleep: (duration_ms) => { + if (duration_ms > 0) { + // TODO(bill): Does this even make any sense? + } + }, + + sqrt: Math.sqrt, + sin: Math.sin, + cos: Math.cos, + pow: Math.pow, + fmuladd: (x, y, z) => x*y + z, + ln: Math.log, + exp: Math.exp, + ldexp: (x, exp) => x * Math.pow(2, exp), + + rand_bytes: (ptr, len) => { + const view = new Uint8Array(wasmMemoryInterface.memory.buffer, ptr, len) + crypto.getRandomValues(view) + }, + }, + "odin_dom": { + init_event_raw: (ep) => { + const W = wasmMemoryInterface.intSize; + let offset = ep; + let off = (amount, alignment) => { + if (alignment === undefined) { + alignment = Math.min(amount, W); + } + if (offset % alignment != 0) { + offset += alignment - (offset%alignment); + } + let x = offset; + offset += amount; + return x; + }; + + let align = (alignment) => { + const modulo = offset & (alignment-1); + if (modulo != 0) { + offset += alignment - modulo + } + }; + + let wmi = wasmMemoryInterface; + + if (!event_temp.data) { + return; + } + + let e = event_temp.data.event; + + wmi.storeU32(off(4), event_temp.data.name_code); + if (e.target == document) { + wmi.storeU32(off(4), 1); + } else if (e.target == window) { + wmi.storeU32(off(4), 2); + } else { + wmi.storeU32(off(4), 0); + } + if (e.currentTarget == document) { + wmi.storeU32(off(4), 1); + } else if (e.currentTarget == window) { + wmi.storeU32(off(4), 2); + } else { + wmi.storeU32(off(4), 0); + } + + align(W); + + wmi.storeI32(off(W), event_temp.data.id_ptr); + wmi.storeUint(off(W), event_temp.data.id_len); + + align(8); + wmi.storeF64(off(8), e.timeStamp*1e-3); + + wmi.storeU8(off(1), e.eventPhase); + let options = 0; + if (!!e.bubbles) { options |= 1<<0; } + if (!!e.cancelable) { options |= 1<<1; } + if (!!e.composed) { options |= 1<<2; } + wmi.storeU8(off(1), options); + wmi.storeU8(off(1), !!e.isComposing); + wmi.storeU8(off(1), !!e.isTrusted); + + align(8); + if (e instanceof WheelEvent) { + wmi.storeF64(off(8), e.deltaX); + wmi.storeF64(off(8), e.deltaY); + wmi.storeF64(off(8), e.deltaZ); + wmi.storeU32(off(4), e.deltaMode); + } else if (e instanceof MouseEvent) { + wmi.storeI64(off(8), e.screenX); + wmi.storeI64(off(8), e.screenY); + wmi.storeI64(off(8), e.clientX); + wmi.storeI64(off(8), e.clientY); + wmi.storeI64(off(8), e.offsetX); + wmi.storeI64(off(8), e.offsetY); + wmi.storeI64(off(8), e.pageX); + wmi.storeI64(off(8), e.pageY); + wmi.storeI64(off(8), e.movementX); + wmi.storeI64(off(8), e.movementY); + + wmi.storeU8(off(1), !!e.ctrlKey); + wmi.storeU8(off(1), !!e.shiftKey); + wmi.storeU8(off(1), !!e.altKey); + wmi.storeU8(off(1), !!e.metaKey); + + wmi.storeI16(off(2), e.button); + wmi.storeU16(off(2), e.buttons); + } else if (e instanceof KeyboardEvent) { + // Note: those strings are constructed + // on the native side from buffers that + // are filled later, so skip them + const keyPtr = off(W*2, W); + const codePtr = off(W*2, W); + + wmi.storeU8(off(1), e.location); + + wmi.storeU8(off(1), !!e.ctrlKey); + wmi.storeU8(off(1), !!e.shiftKey); + wmi.storeU8(off(1), !!e.altKey); + wmi.storeU8(off(1), !!e.metaKey); + + wmi.storeU8(off(1), !!e.repeat); + + wmi.storeInt(off(W, W), e.key.length) + wmi.storeInt(off(W, W), e.code.length) + wmi.storeString(off(16, 1), e.key); + wmi.storeString(off(16, 1), e.code); + } else if (e.type === 'scroll') { + wmi.storeF64(off(8, 8), window.scrollX); + wmi.storeF64(off(8, 8), window.scrollY); + } else if (e.type === 'visibilitychange') { + wmi.storeU8(off(1), !document.hidden); + } else if (e instanceof GamepadEvent) { + const idPtr = off(W*2, W); + const mappingPtr = off(W*2, W); + + wmi.storeI32(off(W, W), e.gamepad.index); + wmi.storeU8(off(1), !!e.gamepad.connected); + wmi.storeF64(off(8, 8), e.gamepad.timestamp); + + wmi.storeInt(off(W, W), e.gamepad.buttons.length); + wmi.storeInt(off(W, W), e.gamepad.axes.length); + + for (let i = 0; i < 64; i++) { + if (i < e.gamepad.buttons.length) { + let b = e.gamepad.buttons[i]; + wmi.storeF64(off(8, 8), b.value); + wmi.storeU8(off(1), !!b.pressed); + wmi.storeU8(off(1), !!b.touched); + } else { + off(16, 8); + } + } + for (let i = 0; i < 16; i++) { + if (i < e.gamepad.axes.length) { + let a = e.gamepad.axes[i]; + wmi.storeF64(off(8, 8), a); + } else { + off(8, 8); + } + } + + wmi.storeInt(off(W, W), e.gamepad.id.length) + wmi.storeInt(off(W, W), e.gamepad.mapping.length) + wmi.storeString(off(64, 1), e.gamepad.id); + wmi.storeString(off(64, 1), e.gamepad.mapping); + } + }, + + add_event_listener: (id_ptr, id_len, name_ptr, name_len, name_code, data, callback, use_capture) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let element = getElement(id); + if (element == undefined) { + return false; + } + + let listener = (e) => { + let event_data = {}; + event_data.id_ptr = id_ptr; + event_data.id_len = id_len; + event_data.event = e; + event_data.name_code = name_code; + + eventQueue.push({event_data: event_data, data: data, callback: callback}); + }; + wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; + element.addEventListener(name, listener, !!use_capture); + return true; + }, + + add_window_event_listener: (name_ptr, name_len, name_code, data, callback, use_capture) => { + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let element = window; + let listener = (e) => { + let event_data = {}; + event_data.id_ptr = 0; + event_data.id_len = 0; + event_data.event = e; + event_data.name_code = name_code; + + eventQueue.push({event_data: event_data, data: data, callback: callback}); + }; + wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; + element.addEventListener(name, listener, !!use_capture); + return true; + }, + + remove_event_listener: (id_ptr, id_len, name_ptr, name_len, data, callback) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let element = getElement(id); + if (element == undefined) { + return false; + } + + let listener = wasmMemoryInterface.listenerMap[{data: data, callback: callback}]; + if (listener == undefined) { + return false; + } + element.removeEventListener(name, listener); + return true; + }, + remove_window_event_listener: (name_ptr, name_len, data, callback) => { + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let element = window; + let key = {data: data, callback: callback}; + let listener = wasmMemoryInterface.listenerMap[key]; + if (!listener) { + return false; + } + wasmMemoryInterface.listenerMap[key] = undefined; + + element.removeEventListener(name, listener); + return true; + }, + + event_stop_propagation: () => { + if (event_temp.data && event_temp.data.event) { + event_temp.data.event.stopPropagation(); + } + }, + event_stop_immediate_propagation: () => { + if (event_temp.data && event_temp.data.event) { + event_temp.data.event.stopImmediatePropagation(); + } + }, + event_prevent_default: () => { + if (event_temp.data && event_temp.data.event) { + event_temp.data.event.preventDefault(); + } + }, + + dispatch_custom_event: (id_ptr, id_len, name_ptr, name_len, options_bits) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let options = { + bubbles: (options_bits & (1<<0)) !== 0, + cancelable: (options_bits & (1<<1)) !== 0, + composed: (options_bits & (1<<2)) !== 0, + }; + + let element = getElement(id); + if (element) { + element.dispatchEvent(new Event(name, options)); + return true; + } + return false; + }, + + get_gamepad_state: (gamepad_id, ep) => { + let index = gamepad_id; + let gps = navigator.getGamepads(); + if (0 <= index && index < gps.length) { + let gamepad = gps[index]; + if (!gamepad) { + return false; + } + + const W = wasmMemoryInterface.intSize; + let offset = ep; + let off = (amount, alignment) => { + if (alignment === undefined) { + alignment = Math.min(amount, W); + } + if (offset % alignment != 0) { + offset += alignment - (offset%alignment); + } + let x = offset; + offset += amount; + return x; + }; + + let align = (alignment) => { + const modulo = offset & (alignment-1); + if (modulo != 0) { + offset += alignment - modulo + } + }; + + let wmi = wasmMemoryInterface; + + const idPtr = off(W*2, W); + const mappingPtr = off(W*2, W); + + wmi.storeI32(off(W), gamepad.index); + wmi.storeU8(off(1), !!gamepad.connected); + wmi.storeF64(off(8), gamepad.timestamp); + + wmi.storeInt(off(W), gamepad.buttons.length); + wmi.storeInt(off(W), gamepad.axes.length); + + for (let i = 0; i < 64; i++) { + if (i < gamepad.buttons.length) { + let b = gamepad.buttons[i]; + wmi.storeF64(off(8, 8), b.value); + wmi.storeU8(off(1), !!b.pressed); + wmi.storeU8(off(1), !!b.touched); + } else { + off(16, 8); + } + } + for (let i = 0; i < 16; i++) { + if (i < gamepad.axes.length) { + wmi.storeF64(off(8, 8), gamepad.axes[i]); + } else { + off(8, 8); + } + } + + wmi.storeInt(off(W, W), gamepad.id.length) + wmi.storeInt(off(W, W), gamepad.mapping.length) + wmi.storeString(off(64, 1), gamepad.id); + wmi.storeString(off(64, 1), gamepad.mapping); + + return true; + } + return false; + }, + + get_element_value_f64: (id_ptr, id_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let element = getElement(id); + return element ? element.value : 0; + }, + get_element_value_string: (id_ptr, id_len, buf_ptr, buf_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let element = getElement(id); + if (element) { + let str = element.value; + if (buf_len > 0 && buf_ptr) { + let n = Math.min(buf_len, str.length); + str = str.substring(0, n); + this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str)) + return n; + } + } + return 0; + }, + get_element_value_string_length: (id_ptr, id_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let element = getElement(id); + if (element) { + return element.value.length; + } + return 0; + }, + get_element_min_max: (ptr_array2_f64, id_ptr, id_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let element = getElement(id); + if (element) { + let values = wasmMemoryInterface.loadF64Array(ptr_array2_f64, 2); + values[0] = element.min; + values[1] = element.max; + } + }, + set_element_value_f64: (id_ptr, id_len, value) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let element = getElement(id); + if (element) { + element.value = value; + } + }, + set_element_value_string: (id_ptr, id_len, value_ptr, value_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let value = wasmMemoryInterface.loadString(value_ptr, value_len); + let element = getElement(id); + if (element) { + element.value = value; + } + }, + + get_element_key_f64: (id_ptr, id_len, key_ptr, key_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let element = getElement(id); + return element ? element[key] : 0; + }, + get_element_key_string: (id_ptr, id_len, key_ptr, key_len, buf_ptr, buf_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let element = getElement(id); + if (element) { + let str = element[key]; + if (buf_len > 0 && buf_ptr) { + let n = Math.min(buf_len, str.length); + str = str.substring(0, n); + this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str)) + return n; + } + } + return 0; + }, + get_element_key_string_length: (id_ptr, id_len, key_ptr, key_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let element = getElement(id); + if (element && element[key]) { + return element[key].length; + } + return 0; + }, + + set_element_key_f64: (id_ptr, id_len, key_ptr, key_len, value) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let element = getElement(id); + if (element) { + element[key] = value; + } + }, + set_element_key_string: (id_ptr, id_len, key_ptr, key_len, value_ptr, value_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let value = wasmMemoryInterface.loadString(value_ptr, value_len); + let element = getElement(id); + if (element) { + element[key] = value; + } + }, + + + get_bounding_client_rect: (rect_ptr, id_ptr, id_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let element = getElement(id); + if (element) { + let values = wasmMemoryInterface.loadF64Array(rect_ptr, 4); + let rect = element.getBoundingClientRect(); + values[0] = rect.left; + values[1] = rect.top; + values[2] = rect.right - rect.left; + values[3] = rect.bottom - rect.top; + } + }, + window_get_rect: (rect_ptr) => { + let values = wasmMemoryInterface.loadF64Array(rect_ptr, 4); + values[0] = window.screenX; + values[1] = window.screenY; + values[2] = window.screen.width; + values[3] = window.screen.height; + }, + + window_get_scroll: (pos_ptr) => { + let values = wasmMemoryInterface.loadF64Array(pos_ptr, 2); + values[0] = window.scrollX; + values[1] = window.scrollY; + }, + window_set_scroll: (x, y) => { + window.scroll(x, y); + }, + + device_pixel_ratio: () => { + return window.devicePixelRatio; + }, + + }, + + "webgl": webglContext.getWebGL1Interface(), + "webgl2": webglContext.getWebGL2Interface(), + }; +}; + +/** + * @param {string} wasmPath - Path to the WASM module to run + * @param {?HTMLPreElement} consoleElement - Optional console/pre element to append output to, in addition to the console + * @param {any} extraForeignImports - Imports, in addition to the default runtime to provide the module + * @param {?WasmMemoryInterface} wasmMemoryInterface - Optional memory to use instead of the defaults + * @param {?int} intSize - Size (in bytes) of the integer type, should be 4 on `js_wasm32` and 8 on `js_wasm64p32` + */ +async function runWasm(wasmPath, consoleElement, extraForeignImports, wasmMemoryInterface, intSize = 4) { + if (!wasmMemoryInterface) { + wasmMemoryInterface = new WasmMemoryInterface(); + } + wasmMemoryInterface.setIntSize(intSize); + + let eventQueue = new Array(); + let event_temp = {}; + + let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement, wasmMemoryInterface.memory, eventQueue, event_temp); + let exports = {}; + + if (extraForeignImports !== undefined) { + imports = { + ...imports, + ...extraForeignImports, + }; + } + + const response = await fetch(wasmPath); + const file = await response.arrayBuffer(); + const wasm = await WebAssembly.instantiate(file, imports); + exports = wasm.instance.exports; + wasmMemoryInterface.setExports(exports); + + if (exports.memory) { + if (wasmMemoryInterface.memory) { + console.warn("WASM module exports memory, but `runWasm` was given an interface with existing memory too"); + } + wasmMemoryInterface.setMemory(exports.memory); + } + + exports._start(); + + // Define a `@export step :: proc(current_type: f64) -> (keep_going: bool) {` + // in your app and it will get called every frame. + // return `false` to stop the execution of the module. + if (exports.step) { + const odin_ctx = exports.default_context_ptr(); + + let prevTimeStamp = undefined; + function step(currTimeStamp) { + if (prevTimeStamp == undefined) { + prevTimeStamp = currTimeStamp; + } + + const dt = (currTimeStamp - prevTimeStamp)*0.001; + prevTimeStamp = currTimeStamp; + + while (eventQueue.length > 0) { + let e = eventQueue.shift() + event_temp.data = e.event_data; + exports.odin_dom_do_event_callback(e.data, e.callback, odin_ctx); + } + event_temp.data = null; + + if (!exports.step(currTimeStamp*0.001, odin_ctx)) { + exports._end(); + return; + } + + window.requestAnimationFrame(step); + } + + window.requestAnimationFrame(step); + } else { + exports._end(); + } + + return; +}; + +window.odin = { + // Interface Types + WasmMemoryInterface: WasmMemoryInterface, + WebGLInterface: WebGLInterface, + + // Functions + setupDefaultImports: odinSetupDefaultImports, + runWasm: runWasm, +}; +})(); diff --git a/core/sys/wasm/wasi/wasi_api.odin b/core/sys/wasm/wasi/wasi_api.odin index 38d95e754..8d50f1690 100644 --- a/core/sys/wasm/wasi/wasi_api.odin +++ b/core/sys/wasm/wasi/wasi_api.odin @@ -1,4 +1,4 @@ -//+build wasm32 +#+build wasm32 package sys_wasi foreign import wasi "wasi_snapshot_preview1" diff --git a/core/sys/windows/advapi32.odin b/core/sys/windows/advapi32.odin index 1e34f1fd6..f834511d4 100644 --- a/core/sys/windows/advapi32.odin +++ b/core/sys/windows/advapi32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import advapi32 "system:Advapi32.lib" diff --git a/core/sys/windows/bcrypt.odin b/core/sys/windows/bcrypt.odin index d891aa92b..f15f1e305 100644 --- a/core/sys/windows/bcrypt.odin +++ b/core/sys/windows/bcrypt.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import bcrypt "system:Bcrypt.lib" diff --git a/core/sys/windows/bluetooth.odin b/core/sys/windows/bluetooth.odin index 7bfb7ea96..86c66b9a1 100644 --- a/core/sys/windows/bluetooth.odin +++ b/core/sys/windows/bluetooth.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import "system:bthprops.lib" diff --git a/core/sys/windows/codepage.odin b/core/sys/windows/codepage.odin index 90040f1ee..527289f03 100644 --- a/core/sys/windows/codepage.odin +++ b/core/sys/windows/codepage.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows // https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers diff --git a/core/sys/windows/comctl32.odin b/core/sys/windows/comctl32.odin index 9c4404a9d..477800413 100644 --- a/core/sys/windows/comctl32.odin +++ b/core/sys/windows/comctl32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import "system:Comctl32.lib" diff --git a/core/sys/windows/comdlg32.odin b/core/sys/windows/comdlg32.odin index 30d9b169c..a9800b47a 100644 --- a/core/sys/windows/comdlg32.odin +++ b/core/sys/windows/comdlg32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import "system:Comdlg32.lib" diff --git a/core/sys/windows/dbghelp.odin b/core/sys/windows/dbghelp.odin index cb5458248..336992b4a 100644 --- a/core/sys/windows/dbghelp.odin +++ b/core/sys/windows/dbghelp.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import "system:Dbghelp.lib" diff --git a/core/sys/windows/dnsapi.odin b/core/sys/windows/dnsapi.odin index dd2d1acee..4fd9f7a19 100644 --- a/core/sys/windows/dnsapi.odin +++ b/core/sys/windows/dnsapi.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import "system:Dnsapi.lib" diff --git a/core/sys/windows/dwmapi.odin b/core/sys/windows/dwmapi.odin index 91911baae..11a46f53a 100644 --- a/core/sys/windows/dwmapi.odin +++ b/core/sys/windows/dwmapi.odin @@ -1,10 +1,10 @@ -// +build windows +#+build windows package sys_windows foreign import dwmapi "system:Dwmapi.lib" DWMWINDOWATTRIBUTE :: enum { - DWMWA_NCRENDERING_ENABLED, + DWMWA_NCRENDERING_ENABLED = 1, DWMWA_NCRENDERING_POLICY, DWMWA_TRANSITIONS_FORCEDISABLED, DWMWA_ALLOW_NCPAINT, @@ -28,7 +28,7 @@ DWMWINDOWATTRIBUTE :: enum { DWMWA_TEXT_COLOR, DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, DWMWA_SYSTEMBACKDROP_TYPE, - DWMWA_LAST, + DWMWA_LAST, } DWMNCRENDERINGPOLICY :: enum { diff --git a/core/sys/windows/gdi32.odin b/core/sys/windows/gdi32.odin index 5cbafddba..1d7a93d85 100644 --- a/core/sys/windows/gdi32.odin +++ b/core/sys/windows/gdi32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows import "core:math/fixed" diff --git a/core/sys/windows/hidpi.odin b/core/sys/windows/hidpi.odin index bea03694e..5e9787527 100644 --- a/core/sys/windows/hidpi.odin +++ b/core/sys/windows/hidpi.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows import "core:c" diff --git a/core/sys/windows/hidusage.odin b/core/sys/windows/hidusage.odin index a32aa7b9f..eb2a85f2e 100644 --- a/core/sys/windows/hidusage.odin +++ b/core/sys/windows/hidusage.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows USAGE :: distinct USHORT diff --git a/core/sys/windows/ip_helper.odin b/core/sys/windows/ip_helper.odin index 4c2534c10..7a6e545ac 100644 --- a/core/sys/windows/ip_helper.odin +++ b/core/sys/windows/ip_helper.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import "system:iphlpapi.lib" diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 54dc11389..2771581e6 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import kernel32 "system:Kernel32.lib" @@ -400,6 +400,9 @@ foreign kernel32 { GlobalAlloc :: proc(flags: UINT, bytes: SIZE_T) -> LPVOID --- GlobalReAlloc :: proc(mem: LPVOID, bytes: SIZE_T, flags: UINT) -> LPVOID --- GlobalFree :: proc(mem: LPVOID) -> LPVOID --- + + GlobalLock :: proc(hMem: HGLOBAL) -> LPVOID --- + GlobalUnlock :: proc(hMem: HGLOBAL) -> BOOL --- ReadDirectoryChangesW :: proc( hDirectory: HANDLE, @@ -1175,17 +1178,17 @@ SYSTEM_POWER_STATUS :: struct { } AC_Line_Status :: enum BYTE { - Offline = 0, - Online = 1, - Unknown = 255, + Offline = 0, + Online = 1, + Unknown = 255, } Battery_Flag :: enum BYTE { - High = 0, - Low = 1, - Critical = 2, - Charging = 3, - No_Battery = 7, + High = 0, + Low = 1, + Critical = 2, + Charging = 3, + No_Battery = 7, } Battery_Flags :: bit_set[Battery_Flag; BYTE] diff --git a/core/sys/windows/key_codes.odin b/core/sys/windows/key_codes.odin index 284b0e437..0991ca4b3 100644 --- a/core/sys/windows/key_codes.odin +++ b/core/sys/windows/key_codes.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows // https://docs.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input diff --git a/core/sys/windows/known_folders.odin b/core/sys/windows/known_folders.odin index 439d65faf..cbaf5eeeb 100644 --- a/core/sys/windows/known_folders.odin +++ b/core/sys/windows/known_folders.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows FOLDERID_NetworkFolder :: GUID {0xD20BEEC4, 0x5CA8, 0x4905, {0xAE, 0x3B, 0xBF, 0x25, 0x1E, 0xA0, 0x9B, 0x53}} diff --git a/core/sys/windows/netapi32.odin b/core/sys/windows/netapi32.odin index d9f75c623..9442193ca 100644 --- a/core/sys/windows/netapi32.odin +++ b/core/sys/windows/netapi32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import netapi32 "system:Netapi32.lib" diff --git a/core/sys/windows/ntdll.odin b/core/sys/windows/ntdll.odin index 23444ff34..747130749 100644 --- a/core/sys/windows/ntdll.odin +++ b/core/sys/windows/ntdll.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import ntdll_lib "system:ntdll.lib" diff --git a/core/sys/windows/shcore.odin b/core/sys/windows/shcore.odin index 54f67989e..08a76ebe6 100644 --- a/core/sys/windows/shcore.odin +++ b/core/sys/windows/shcore.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows diff --git a/core/sys/windows/shell32.odin b/core/sys/windows/shell32.odin index 7340ae4d4..54cee718c 100644 --- a/core/sys/windows/shell32.odin +++ b/core/sys/windows/shell32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import shell32 "system:Shell32.lib" diff --git a/core/sys/windows/shlwapi.odin b/core/sys/windows/shlwapi.odin index bf9d2d1e8..095fff304 100644 --- a/core/sys/windows/shlwapi.odin +++ b/core/sys/windows/shlwapi.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import shlwapi "system:shlwapi.lib" diff --git a/core/sys/windows/synchronization.odin b/core/sys/windows/synchronization.odin index 79efaab34..bcaeb3f5f 100644 --- a/core/sys/windows/synchronization.odin +++ b/core/sys/windows/synchronization.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import Synchronization "system:Synchronization.lib" diff --git a/core/sys/windows/system_params.odin b/core/sys/windows/system_params.odin index e94d777bf..e463feb7e 100644 --- a/core/sys/windows/system_params.odin +++ b/core/sys/windows/system_params.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows // Parameter for SystemParametersInfo. diff --git a/core/sys/windows/tlhelp.odin b/core/sys/windows/tlhelp.odin index 45d5a3ff9..006c9c330 100644 --- a/core/sys/windows/tlhelp.odin +++ b/core/sys/windows/tlhelp.odin @@ -1,4 +1,4 @@ -//+build windows +#+build windows package sys_windows foreign import kernel32 "system:Kernel32.lib" diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index d9a6bd1fd..934d103e5 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -3546,11 +3546,11 @@ SIGDN :: enum c_int { } SIATTRIBFLAGS :: enum c_int { - AND = 0x1, - OR = 0x2, - APPCOMPAT = 0x3, - MASK = 0x3, - ALLITEMS = 0x4000, + AND = 0x1, + OR = 0x2, + APPCOMPAT = 0x3, + MASK = 0x3, + ALLITEMS = 0x4000, } FDAP :: enum c_int { @@ -4503,35 +4503,35 @@ DNS_INFO_NO_RECORDS :: 9501 DNS_QUERY_NO_RECURSION :: 0x00000004 DNS_RECORD :: struct { // aka DNS_RECORDA - pNext: ^DNS_RECORD, - pName: cstring, - wType: WORD, - wDataLength: USHORT, - Flags: DWORD, - dwTtl: DWORD, - _: DWORD, - Data: struct #raw_union { - CNAME: DNS_PTR_DATAA, - A: u32be, // Ipv4 Address - AAAA: u128be, // Ipv6 Address - TXT: DNS_TXT_DATAA, - NS: DNS_PTR_DATAA, - MX: DNS_MX_DATAA, - SRV: DNS_SRV_DATAA, - }, + pNext: ^DNS_RECORD, + pName: cstring, + wType: WORD, + wDataLength: USHORT, + Flags: DWORD, + dwTtl: DWORD, + _: DWORD, + Data: struct #raw_union { + CNAME: DNS_PTR_DATAA, + A: u32be, // Ipv4 Address + AAAA: u128be, // Ipv6 Address + TXT: DNS_TXT_DATAA, + NS: DNS_PTR_DATAA, + MX: DNS_MX_DATAA, + SRV: DNS_SRV_DATAA, + }, } DNS_TXT_DATAA :: struct { - dwStringCount: DWORD, - pStringArray: cstring, + dwStringCount: DWORD, + pStringArray: cstring, } DNS_PTR_DATAA :: cstring DNS_MX_DATAA :: struct { - pNameExchange: cstring, // the hostname - wPreference: WORD, // lower values preferred - _: WORD, // padding. + pNameExchange: cstring, // the hostname + wPreference: WORD, // lower values preferred + _: WORD, // padding. } DNS_SRV_DATAA :: struct { pNameTarget: cstring, diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin index f1232c680..514592e7b 100644 --- a/core/sys/windows/user32.odin +++ b/core/sys/windows/user32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows import "base:intrinsics" @@ -66,7 +66,7 @@ foreign user32 { RemovePropW :: proc(hWnd: HWND, lpString: LPCWSTR) -> HANDLE --- EnumPropsW :: proc(hWnd: HWND, lpEnumFunc: PROPENUMPROCW) -> INT --- EnumPropsExW :: proc(hWnd: HWND, lpEnumFunc: PROPENUMPROCW, lParam: LPARAM) -> INT --- - GetMessageW :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> BOOL --- + GetMessageW :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> INT --- TranslateMessage :: proc(lpMsg: ^MSG) -> BOOL --- DispatchMessageW :: proc(lpMsg: ^MSG) -> LRESULT --- @@ -142,7 +142,7 @@ foreign user32 { AppendMenuW :: proc(hMenu: HMENU, uFlags: UINT, uIDNewItem: UINT_PTR, lpNewItem: LPCWSTR) -> BOOL --- GetMenu :: proc(hWnd: HWND) -> HMENU --- SetMenu :: proc(hWnd: HWND, hMenu: HMENU) -> BOOL --- - TrackPopupMenu :: proc(hMenu: HMENU, uFlags: UINT, x, y: INT, nReserved: INT, hWnd: HWND, prcRect: ^RECT) -> BOOL --- + TrackPopupMenu :: proc(hMenu: HMENU, uFlags: UINT, x, y: INT, nReserved: INT, hWnd: HWND, prcRect: ^RECT) -> INT --- RegisterWindowMessageW :: proc(lpString: LPCWSTR) -> UINT --- CreateAcceleratorTableW :: proc(paccel: LPACCEL, cAccel: INT) -> HACCEL --- @@ -305,6 +305,13 @@ foreign user32 { GetProcessWindowStation :: proc() -> HWINSTA --- GetUserObjectInformationW :: proc(hObj: HANDLE, nIndex: GetUserObjectInformationFlags, pvInfo: PVOID, nLength: DWORD, lpnLengthNeeded: LPDWORD) -> BOOL --- + + OpenClipboard :: proc(hWndNewOwner: HWND) -> BOOL --- + CloseClipboard :: proc() -> BOOL --- + GetClipboardData :: proc(uFormat: UINT) -> HANDLE --- + SetClipboardData :: proc(uFormat: UINT, hMem: HANDLE) -> HANDLE --- + IsClipboardFormatAvailable :: proc(format: UINT) -> BOOL --- + EmptyClipboard :: proc() -> BOOL --- } CreateWindowW :: #force_inline proc "system" ( @@ -746,3 +753,31 @@ WinEventFlag :: enum DWORD { SKIPOWNPROCESS = 1, INCONTEXT = 2, } + +// Standard Clipboard Formats +CF_TEXT :: 1 +CF_BITMAP :: 2 +CF_METAFILEPICT :: 3 +CF_SYLK :: 4 +CF_DIF :: 5 +CF_TIFF :: 6 +CF_OEMTEXT :: 7 +CF_DIB :: 8 +CF_PALETTE :: 9 +CF_PENDATA :: 10 +CF_RIFF :: 11 +CF_WAVE :: 12 +CF_UNICODETEXT :: 13 +CF_ENHMETAFILE :: 14 +CF_HDROP :: 15 +CF_LOCALE :: 16 +CF_DIBV5 :: 17 +CF_DSPBITMAP :: 0x0082 +CF_DSPENHMETAFILE :: 0x008E +CF_DSPMETAFILEPICT :: 0x0083 +CF_DSPTEXT :: 0x0081 +CF_GDIOBJFIRST :: 0x0300 +CF_GDIOBJLAST :: 0x03FF +CF_OWNERDISPLAY :: 0x0080 +CF_PRIVATEFIRST :: 0x0200 +CF_PRIVATELAST :: 0x02FF diff --git a/core/sys/windows/userenv.odin b/core/sys/windows/userenv.odin index a31e363e1..2a2209d2c 100644 --- a/core/sys/windows/userenv.odin +++ b/core/sys/windows/userenv.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import userenv "system:Userenv.lib" diff --git a/core/sys/windows/util.odin b/core/sys/windows/util.odin index 929df1765..b3eb800bc 100644 --- a/core/sys/windows/util.odin +++ b/core/sys/windows/util.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows import "base:runtime" diff --git a/core/sys/windows/ux_theme.odin b/core/sys/windows/ux_theme.odin index 7af399361..527abd62f 100644 --- a/core/sys/windows/ux_theme.odin +++ b/core/sys/windows/ux_theme.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import uxtheme "system:UxTheme.lib" diff --git a/core/sys/windows/wgl.odin b/core/sys/windows/wgl.odin index d0d96d90b..8fea55c3d 100644 --- a/core/sys/windows/wgl.odin +++ b/core/sys/windows/wgl.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows import "core:c" diff --git a/core/sys/windows/wglext.odin b/core/sys/windows/wglext.odin index 0c4b51d65..4c76b39ec 100644 --- a/core/sys/windows/wglext.odin +++ b/core/sys/windows/wglext.odin @@ -1,4 +1,4 @@ -// +build windows
+#+build windows
package sys_windows
// WGL_ARB_buffer_region
diff --git a/core/sys/windows/window_messages.odin b/core/sys/windows/window_messages.odin index 888c5ccf9..d69771bdf 100644 --- a/core/sys/windows/window_messages.odin +++ b/core/sys/windows/window_messages.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows WM_NULL :: 0x0000 diff --git a/core/sys/windows/winerror.odin b/core/sys/windows/winerror.odin index 8882dad71..b3b470619 100644 --- a/core/sys/windows/winerror.odin +++ b/core/sys/windows/winerror.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows // https://learn.microsoft.com/en-us/windows/win32/api/winerror/ @@ -213,6 +213,7 @@ ERROR_BROKEN_PIPE : DWORD : 109 ERROR_CALL_NOT_IMPLEMENTED : DWORD : 120 ERROR_INSUFFICIENT_BUFFER : DWORD : 122 ERROR_INVALID_NAME : DWORD : 123 +ERROR_NEGATIVE_SEEK : DWORD : 131 ERROR_BAD_ARGUMENTS : DWORD : 160 ERROR_LOCK_FAILED : DWORD : 167 ERROR_ALREADY_EXISTS : DWORD : 183 diff --git a/core/sys/windows/winmm.odin b/core/sys/windows/winmm.odin index a1786c27a..3c7ec80e7 100644 --- a/core/sys/windows/winmm.odin +++ b/core/sys/windows/winmm.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import winmm "system:Winmm.lib" diff --git a/core/sys/windows/winnls.odin b/core/sys/windows/winnls.odin index 292d2fad2..ffb2638d5 100644 --- a/core/sys/windows/winnls.odin +++ b/core/sys/windows/winnls.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows LCTYPE :: distinct DWORD diff --git a/core/sys/windows/winver.odin b/core/sys/windows/winver.odin index 091d53d3a..47751dab7 100644 --- a/core/sys/windows/winver.odin +++ b/core/sys/windows/winver.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import version "system:version.lib" diff --git a/core/sys/windows/wow64_apiset.odin b/core/sys/windows/wow64_apiset.odin index 28558e9ca..3d29b786e 100644 --- a/core/sys/windows/wow64_apiset.odin +++ b/core/sys/windows/wow64_apiset.odin @@ -1,4 +1,4 @@ -//+build windows +#+build windows package sys_windows foreign import kernel32 "system:Kernel32.lib" diff --git a/core/sys/windows/ws2_32.odin b/core/sys/windows/ws2_32.odin index e9bf8abc9..5b2952495 100644 --- a/core/sys/windows/ws2_32.odin +++ b/core/sys/windows/ws2_32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows // Define flags to be used with the WSAAsyncSelect() call. diff --git a/core/testing/events.odin b/core/testing/events.odin index c9c4b0271..1a47e2d68 100644 --- a/core/testing/events.odin +++ b/core/testing/events.odin @@ -1,4 +1,4 @@ -//+private +#+private package testing /* diff --git a/core/testing/logging.odin b/core/testing/logging.odin index 1c3fc4603..041489dab 100644 --- a/core/testing/logging.odin +++ b/core/testing/logging.odin @@ -1,4 +1,4 @@ -//+private +#+private package testing /* diff --git a/core/testing/reporting.odin b/core/testing/reporting.odin index 81f1d0646..6752cd79b 100644 --- a/core/testing/reporting.odin +++ b/core/testing/reporting.odin @@ -1,4 +1,4 @@ -//+private +#+private package testing /* diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 386ba8cb5..6b9d610ed 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -1,4 +1,4 @@ -//+private +#+private package testing /* @@ -204,6 +204,10 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } + when ODIN_OS == .Windows { + console_ansi_init() + } + stdout := io.to_writer(os.stream_from_handle(os.stdout)) stderr := io.to_writer(os.stream_from_handle(os.stderr)) diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin new file mode 100644 index 000000000..401804c71 --- /dev/null +++ b/core/testing/runner_windows.odin @@ -0,0 +1,22 @@ +#+private +package testing + +import win32 "core:sys/windows" + +console_ansi_init :: proc() { + stdout := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE) + if stdout != win32.INVALID_HANDLE && stdout != nil { + old_console_mode: u32 + if win32.GetConsoleMode(stdout, &old_console_mode) { + win32.SetConsoleMode(stdout, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING) + } + } + + stderr := win32.GetStdHandle(win32.STD_ERROR_HANDLE) + if stderr != win32.INVALID_HANDLE && stderr != nil { + old_console_mode: u32 + if win32.GetConsoleMode(stderr, &old_console_mode) { + win32.SetConsoleMode(stderr, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING) + } + } +} diff --git a/core/testing/signal_handler.odin b/core/testing/signal_handler.odin index 047ea0b3a..2f1f7c89a 100644 --- a/core/testing/signal_handler.odin +++ b/core/testing/signal_handler.odin @@ -1,4 +1,4 @@ -//+private +#+private package testing /* diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index 27d1a0735..7442c100c 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -1,5 +1,5 @@ -//+private -//+build windows, linux, darwin, freebsd, openbsd, netbsd, haiku +#+private +#+build windows, linux, darwin, freebsd, openbsd, netbsd, haiku package testing /* @@ -26,6 +26,8 @@ import "core:os" @(private="file", thread_local) local_test_index: libc.sig_atomic_t +@(private="file", thread_local) +local_test_index_set: bool // Windows does not appear to have a SIGTRAP, so this is defined here, instead // of in the libc package, just so there's no confusion about it being @@ -45,6 +47,13 @@ stop_runner_callback :: proc "c" (sig: libc.int) { @(private="file") stop_test_callback :: proc "c" (sig: libc.int) { + if !local_test_index_set { + // We're a thread created by a test thread. + // + // There's nothing we can do to inform the test runner about who + // signalled, so hopefully the test will handle their own sub-threads. + return + } if local_test_index == -1 { // We're the test runner, and we ourselves have caught a signal from // which there is no recovery. @@ -114,6 +123,7 @@ This is a dire bug and should be reported to the Odin developers. _setup_signal_handler :: proc() { local_test_index = -1 + local_test_index_set = true // Catch user interrupt / CTRL-C. libc.signal(libc.SIGINT, stop_runner_callback) @@ -135,6 +145,7 @@ _setup_signal_handler :: proc() { _setup_task_signal_handler :: proc(test_index: int) { local_test_index = cast(libc.sig_atomic_t)test_index + local_test_index_set = true } _should_stop_runner :: proc() -> bool { diff --git a/core/testing/signal_handler_other.odin b/core/testing/signal_handler_other.odin index 6f39205c7..81f575495 100644 --- a/core/testing/signal_handler_other.odin +++ b/core/testing/signal_handler_other.odin @@ -1,5 +1,11 @@ -//+private -//+build !windows !linux !darwin !freebsd !openbsd !netbsd !haiku +#+private +#+build !windows +#+build !linux +#+build !darwin +#+build !freebsd +#+build !openbsd +#+build !netbsd +#+build !haiku package testing /* diff --git a/core/testing/testing.odin b/core/testing/testing.odin index d5e7c6830..09bf6dc0e 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -105,9 +105,13 @@ cleanup :: proc(t: ^T, procedure: proc(rawptr), user_data: rawptr) { append(&t.cleanups, Internal_Cleanup{procedure, user_data, context}) } -expect :: proc(t: ^T, ok: bool, msg: string = "", loc := #caller_location) -> bool { +expect :: proc(t: ^T, ok: bool, msg := "", expr := #caller_expression(ok), loc := #caller_location) -> bool { if !ok { - log.error(msg, location=loc) + if msg == "" { + log.errorf("expected %v to be true", expr, location=loc) + } else { + log.error(msg, location=loc) + } } return ok } @@ -119,10 +123,10 @@ expectf :: proc(t: ^T, ok: bool, format: string, args: ..any, loc := #caller_loc return ok } -expect_value :: proc(t: ^T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { +expect_value :: proc(t: ^T, value, expected: $T, loc := #caller_location, value_expr := #caller_expression(value)) -> bool where intrinsics.type_is_comparable(T) { ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) if !ok { - log.errorf("expected %v, got %v", expected, value, location=loc) + log.errorf("expected %v to be %v, got %v", value_expr, expected, value, location=loc) } return ok } diff --git a/core/text/edit/text_edit.odin b/core/text/edit/text_edit.odin index 521a658e1..49adad4d9 100644 --- a/core/text/edit/text_edit.odin +++ b/core/text/edit/text_edit.odin @@ -1,10 +1,9 @@ -package text_edit - /* - Based off the articles by rxi: - * https://rxi.github.io/textbox_behaviour.html - * https://rxi.github.io/a_simple_undo_system.html +Based off the articles by rxi: +- [[ https://rxi.github.io/textbox_behaviour.html ]] +- [[ https://rxi.github.io/a_simple_undo_system.html ]] */ +package text_edit import "base:runtime" import "core:time" diff --git a/core/text/table/doc.odin b/core/text/table/doc.odin index 955ab8d21..63275bbc1 100644 --- a/core/text/table/doc.odin +++ b/core/text/table/doc.odin @@ -1,8 +1,8 @@ /* The package `table` implements plain-text/markdown/HTML/custom rendering of tables. -**Custom rendering example:** - +**Custom rendering.** +Example: package main import "core:io" @@ -24,13 +24,12 @@ The package `table` implements plain-text/markdown/HTML/custom rendering of tabl } } -This outputs: - +Output: A_LONG_ENUM = 54, // A comment about A_LONG_ENUM AN_EVEN_LONGER_ENUM = 1, // A comment about AN_EVEN_LONGER_ENUM -**Plain-text rendering example:** - +**Plain-text rendering.** +Example: package main import "core:fmt" @@ -81,8 +80,7 @@ This outputs: table.write_markdown_table(stdout, tbl) } -This outputs: - +Output: +-----------------------------------------------+ | This is a table caption and it is very long | +------------------+-----------------+----------+ @@ -93,16 +91,12 @@ This outputs: | a | bbb | c | +------------------+-----------------+----------+ -and - | AAAAAAAAA | B | C | |:-----------------|:---------------:|---------:| | 123 | foo | | | 000000005 | 6.283185 | | | a | bbb | c | -respectively. - Additionally, if you want to set the alignment and values in-line while constructing a table, you can use `aligned_row_of_values` or @@ -116,8 +110,7 @@ constructing a table, you can use `aligned_row_of_values` or If you only need to build a table once but display it potentially many times, it may be more efficient to cache the results of your write into a string. -Here's an example of how you can do that: - +Example: package main import "core:fmt" @@ -191,8 +184,7 @@ This package makes use of the `grapheme_count` procedure from the implementation for counting graphemes and calculating visual width of a Unicode grapheme cluster in monospace cells. -Here is a full example of how well-supported Unicode is with this package: - +Example: package main import "core:fmt" @@ -237,7 +229,7 @@ Here is a full example of how well-supported Unicode is with this package: scripts(stdout) } -This will print out: +Output: +----------------------------------------------------------------------------------------------------------------------------+ | Tést Suite | @@ -271,8 +263,7 @@ If you'd prefer to change the borders used by the plain-text table printing, there is the `write_decorated_table` procedure that allows you to change the corners and dividers. -Here is a complete example: - +Example: package main import "core:fmt" diff --git a/core/text/table/table.odin b/core/text/table/table.odin index 27c99b1f1..66a7d442b 100644 --- a/core/text/table/table.odin +++ b/core/text/table/table.odin @@ -56,7 +56,7 @@ Decorations :: struct { // Connecting decorations: nw, n, ne, - w, x, e, + w, x, e, sw, s, se: string, // Straight lines: diff --git a/core/thread/thread.odin b/core/thread/thread.odin index 17ba1a0a2..c1cbceb42 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -272,7 +272,7 @@ create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, t := create(thread_proc, priority) t.data = rawptr(fn) if self_cleanup { - t.flags += {.Self_Cleanup} + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) } t.init_context = init_context start(t) @@ -307,7 +307,7 @@ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_co t.user_index = 1 t.user_args[0] = data if self_cleanup { - t.flags += {.Self_Cleanup} + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) } t.init_context = init_context start(t) @@ -347,7 +347,7 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex mem.copy(&t.user_args[0], &data, size_of(T)) if self_cleanup { - t.flags += {.Self_Cleanup} + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) } t.init_context = init_context @@ -394,7 +394,7 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), _ = copy(user_args[n:], mem.ptr_to_bytes(&arg2)) if self_cleanup { - t.flags += {.Self_Cleanup} + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) } t.init_context = init_context @@ -443,7 +443,7 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr _ = copy(user_args[n:], mem.ptr_to_bytes(&arg3)) if self_cleanup { - t.flags += {.Self_Cleanup} + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) } t.init_context = init_context @@ -494,7 +494,7 @@ create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: _ = copy(user_args[n:], mem.ptr_to_bytes(&arg4)) if self_cleanup { - t.flags += {.Self_Cleanup} + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) } t.init_context = init_context diff --git a/core/thread/thread_other.odin b/core/thread/thread_other.odin index 34bbfda08..dde2a8e48 100644 --- a/core/thread/thread_other.odin +++ b/core/thread/thread_other.odin @@ -1,4 +1,4 @@ -//+build js, wasi, orca +#+build js, wasi, orca package thread import "base:intrinsics" diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index da5e116ff..d9166b450 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -60,6 +60,7 @@ pool_thread_runner :: proc(t: ^Thread) { if task, ok := pool_pop_waiting(pool); ok { data.task = task pool_do_work(pool, task) + sync.guard(&pool.mutex) data.task = {} } } @@ -122,9 +123,10 @@ pool_join :: proc(pool: ^Pool) { for started_count < len(pool.threads) { started_count = 0 for t in pool.threads { - if .Started in t.flags { + flags := intrinsics.atomic_load(&t.flags) + if .Started in flags { started_count += 1 - if .Joined not_in t.flags { + if .Joined not_in flags { join(t) } } @@ -175,10 +177,12 @@ pool_stop_task :: proc(pool: ^Pool, user_index: int, exit_code: int = 1) -> bool intrinsics.atomic_sub(&pool.num_outstanding, 1) intrinsics.atomic_sub(&pool.num_in_processing, 1) + old_thread_user_index := t.user_index + destroy(t) replacement := create(pool_thread_runner) - replacement.user_index = t.user_index + replacement.user_index = old_thread_user_index replacement.data = data data.task = {} pool.threads[i] = replacement @@ -207,10 +211,12 @@ pool_stop_all_tasks :: proc(pool: ^Pool, exit_code: int = 1) { intrinsics.atomic_sub(&pool.num_outstanding, 1) intrinsics.atomic_sub(&pool.num_in_processing, 1) + old_thread_user_index := t.user_index + destroy(t) replacement := create(pool_thread_runner) - replacement.user_index = t.user_index + replacement.user_index = old_thread_user_index replacement.data = data data.task = {} pool.threads[i] = replacement diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index f56454bfc..9576a3040 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -1,21 +1,18 @@ -// +build linux, darwin, freebsd, openbsd, netbsd, haiku -// +private +#+build linux, darwin, freebsd, openbsd, netbsd, haiku +#+private package thread +import "base:runtime" import "core:sync" import "core:sys/unix" -import "core:time" _IS_SUPPORTED :: true -CAS :: sync.atomic_compare_exchange_strong - // NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. // Also see core/sys/darwin/mach_darwin.odin/semaphore_t. Thread_Os_Specific :: struct #align(16) { unix_thread: unix.pthread_t, // NOTE: very large on Darwin, small on Linux. - cond: sync.Cond, - mutex: sync.Mutex, + start_ok: sync.Sema, } // // Creates a thread which will run the given procedure. @@ -28,14 +25,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // We need to give the thread a moment to start up before we enable cancellation. can_set_thread_cancel_state := unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_ENABLE, nil) == 0 - sync.lock(&t.mutex) - t.id = sync.current_thread_id() - for (.Started not_in sync.atomic_load(&t.flags)) { - // HACK: use a timeout so in the event that the condition is signalled at THIS comment's exact point - // (after checking flags, before starting the wait) it gets itself out of that deadlock after a ms. - sync.wait_with_timeout(&t.cond, &t.mutex, time.Millisecond) + if .Started not_in sync.atomic_load(&t.flags) { + sync.wait(&t.start_ok) } if .Joined in sync.atomic_load(&t.flags) { @@ -55,16 +48,20 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // Here on Unix, we start the OS thread in a running state, and so we manually have it wait on a condition // variable above. We must perform that waiting BEFORE we select the context! context = _select_context_for_thread(init_context) - defer _maybe_destroy_default_temp_allocator(init_context) + defer { + _maybe_destroy_default_temp_allocator(init_context) + runtime.run_thread_local_cleaners() + } t.procedure(t) } sync.atomic_or(&t.flags, { .Done }) - sync.unlock(&t.mutex) - if .Self_Cleanup in sync.atomic_load(&t.flags) { + res := unix.pthread_detach(t.unix_thread) + assert_contextless(res == 0) + t.unix_thread = {} // NOTE(ftphikari): It doesn't matter which context 'free' received, right? context = {} @@ -125,7 +122,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { _start :: proc(t: ^Thread) { sync.atomic_or(&t.flags, { .Started }) - sync.signal(&t.cond) + sync.post(&t.start_ok) } _is_done :: proc(t: ^Thread) -> bool { @@ -133,24 +130,18 @@ _is_done :: proc(t: ^Thread) -> bool { } _join :: proc(t: ^Thread) { - // sync.guard(&t.mutex) - if unix.pthread_equal(unix.pthread_self(), t.unix_thread) { return } - // Preserve other flags besides `.Joined`, like `.Started`. - unjoined := sync.atomic_load(&t.flags) - {.Joined} - joined := unjoined + {.Joined} - - // Try to set `t.flags` from unjoined to joined. If it returns joined, - // it means the previous value had that flag set and we can return. - if res, ok := CAS(&t.flags, unjoined, joined); res == joined && !ok { + // If the previous value was already `Joined`, then we can return. + if .Joined in sync.atomic_or(&t.flags, {.Joined}) { return } + // Prevent non-started threads from blocking main thread with initial wait // condition. - if .Started not_in unjoined { + if .Started not_in sync.atomic_load(&t.flags) { _start(t) } unix.pthread_join(t.unix_thread, nil) diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 8da75a2d9..cc73a2d6a 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -1,8 +1,9 @@ -//+build windows -//+private +#+build windows +#+private package thread import "base:intrinsics" +import "base:runtime" import "core:sync" import win32 "core:sys/windows" @@ -26,7 +27,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { __windows_thread_entry_proc :: proc "system" (t_: rawptr) -> win32.DWORD { t := (^Thread)(t_) - if .Joined in t.flags { + if .Joined in sync.atomic_load(&t.flags) { return 0 } @@ -39,14 +40,17 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // Here on Windows, the thread is created in a suspended state, and so we can select the context anywhere before the call // to t.procedure(). context = _select_context_for_thread(init_context) - defer _maybe_destroy_default_temp_allocator(init_context) + defer { + _maybe_destroy_default_temp_allocator(init_context) + runtime.run_thread_local_cleaners() + } t.procedure(t) } - intrinsics.atomic_store(&t.flags, t.flags + {.Done}) + intrinsics.atomic_or(&t.flags, {.Done}) - if .Self_Cleanup in t.flags { + if .Self_Cleanup in sync.atomic_load(&t.flags) { win32.CloseHandle(t.win32_thread) t.win32_thread = win32.INVALID_HANDLE // NOTE(ftphikari): It doesn't matter which context 'free' received, right? diff --git a/core/time/datetime/internal.odin b/core/time/datetime/internal.odin index e7129548e..3477a47f3 100644 --- a/core/time/datetime/internal.odin +++ b/core/time/datetime/internal.odin @@ -1,4 +1,4 @@ -//+private +#+private package datetime // Internal helper functions for calendrical conversions diff --git a/core/time/rfc3339.odin b/core/time/rfc3339.odin index e4c6565d6..20e8ea0bb 100644 --- a/core/time/rfc3339.odin +++ b/core/time/rfc3339.odin @@ -187,4 +187,110 @@ scan_digits :: proc(s: string, sep: string, count: int) -> (res: int, ok: bool) found_sep |= rune(s[count]) == v } return res, found_sep -}
\ No newline at end of file +} + +/* +Serialize the timestamp as a RFC 3339 string. + +The boolean `ok` is false if the `time` is not a valid datetime, or if allocating the result string fails. + +**Inputs**: +- `utc_offset`: offset in minutes wrt UTC (ie. the timezone) +- `include_nanos`: whether to include nanoseconds in the result. +*/ +time_to_rfc3339 :: proc(time: Time, utc_offset : int = 0, include_nanos := true, allocator := context.allocator) -> (res: string, ok: bool) { + utc_offset := utc_offset + + // convert to datetime + datetime := time_to_datetime(time) or_return + + if datetime.year < 0 || datetime.year >= 10_000 { return "", false } + + temp_string := [36]u8{} + offset : uint = 0 + + print_as_fixed_int :: proc(dst: []u8, offset: ^uint, width: i8, i: i64) { + i := i + width := width + for digit_idx in 0..<width { + last_digit := i % 10 + dst[offset^ + uint(width) - uint(digit_idx)-1] = '0' + u8(last_digit) + i = i / 10 + } + + offset^ += uint(width) + } + + print_as_fixed_int(temp_string[:], &offset, 4, datetime.year) + temp_string[offset] = '-' + offset += 1 + print_as_fixed_int(temp_string[:], &offset, 2, i64(datetime.month)) + temp_string[offset] = '-' + offset += 1 + print_as_fixed_int(temp_string[:], &offset, 2, i64(datetime.day)) + temp_string[offset] = 'T' + offset += 1 + print_as_fixed_int(temp_string[:], &offset, 2, i64(datetime.hour)) + temp_string[offset] = ':' + offset += 1 + print_as_fixed_int(temp_string[:], &offset, 2, i64(datetime.minute)) + temp_string[offset] = ':' + offset += 1 + print_as_fixed_int(temp_string[:], &offset, 2, i64(datetime.second)) + + // turn 123_450_000 to 12345, 5 + strip_trailing_zeroes_nanos :: proc(n: i64) -> (res: i64, n_digits: i8) { + res = n + n_digits = 9 + for res % 10 == 0 { + res = res / 10 + n_digits -= 1 + } + return + } + + // pre-epoch times: turn, say, -400ms to +600ms for display + nanos := time._nsec % 1_000_000_000 + if nanos < 0 { + nanos += 1_000_000_000 + } + + if nanos != 0 && include_nanos { + temp_string[offset] = '.' + offset += 1 + + // remove trailing zeroes + nanos_nonzero, n_digits := strip_trailing_zeroes_nanos(nanos) + assert(nanos_nonzero != 0) + + // write digits, right-to-left + for digit_idx : i8 = n_digits-1; digit_idx >= 0; digit_idx -= 1 { + digit := u8(nanos_nonzero % 10) + temp_string[offset + uint(digit_idx)] = '0' + u8(digit) + nanos_nonzero /= 10 + } + offset += uint(n_digits) + } + + if utc_offset == 0 { + temp_string[offset] = 'Z' + offset += 1 + } else { + temp_string[offset] = utc_offset > 0 ? '+' : '-' + offset += 1 + utc_offset = abs(utc_offset) + print_as_fixed_int(temp_string[:], &offset, 2, i64(utc_offset / 60)) + temp_string[offset] = ':' + offset += 1 + print_as_fixed_int(temp_string[:], &offset, 2, i64(utc_offset % 60)) + } + + res_as_slice, res_alloc := make_slice([]u8, len=offset, allocator = allocator) + if res_alloc != nil { + return "", false + } + + copy(res_as_slice, temp_string[:offset]) + + return string(res_as_slice), true +} diff --git a/core/time/time.odin b/core/time/time.odin index 5903b212d..98639b36a 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -955,6 +955,24 @@ Convert datetime components into time. datetime_to_time :: proc{components_to_time, compound_to_time} /* +Convert time into datetime. +*/ +time_to_datetime :: proc "contextless" (t: Time) -> (dt.DateTime, bool) { + unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}} + + datetime, err := dt.add(unix_epoch, dt.Delta{ nanos = t._nsec }) + if err != .None { + return {}, false + } + return datetime, true +} + +/* +Alias for `time_to_datetime`. +*/ +time_to_compound :: time_to_datetime + +/* Check if a year is a leap year. */ is_leap_year :: proc "contextless" (year: int) -> (leap: bool) { diff --git a/core/time/time_essence.odin b/core/time/time_essence.odin index b7bc616d8..89883f0b9 100644 --- a/core/time/time_essence.odin +++ b/core/time/time_essence.odin @@ -1,4 +1,4 @@ -//+private +#+private package time import "core:sys/es" diff --git a/core/time/time_js.odin b/core/time/time_js.odin index c5090df90..9175fbfe9 100644 --- a/core/time/time_js.odin +++ b/core/time/time_js.odin @@ -1,5 +1,5 @@ -//+private -//+build js +#+private +#+build js package time foreign import "odin_env" diff --git a/core/time/time_orca.odin b/core/time/time_orca.odin index b2598fd6e..f529790a5 100644 --- a/core/time/time_orca.odin +++ b/core/time/time_orca.odin @@ -1,5 +1,5 @@ -//+private -//+build orca +#+private +#+build orca package time import "base:intrinsics" diff --git a/core/time/time_other.odin b/core/time/time_other.odin index 164d23f25..d89bcbd42 100644 --- a/core/time/time_other.odin +++ b/core/time/time_other.odin @@ -1,14 +1,14 @@ -//+private -//+build !essence -//+build !js -//+build !linux -//+build !openbsd -//+build !freebsd -//+build !netbsd -//+build !darwin -//+build !wasi -//+build !windows -//+build !orca +#+private +#+build !essence +#+build !js +#+build !linux +#+build !openbsd +#+build !freebsd +#+build !netbsd +#+build !darwin +#+build !wasi +#+build !windows +#+build !orca package time _IS_SUPPORTED :: false diff --git a/core/time/time_unix.odin b/core/time/time_unix.odin index 0d7a43aba..61c4e91d3 100644 --- a/core/time/time_unix.odin +++ b/core/time/time_unix.odin @@ -1,5 +1,5 @@ -//+private -//+build darwin, freebsd, openbsd, netbsd +#+private +#+build darwin, freebsd, openbsd, netbsd package time import "core:sys/posix" diff --git a/core/time/time_wasi.odin b/core/time/time_wasi.odin index 88bebe2e3..c16c40cce 100644 --- a/core/time/time_wasi.odin +++ b/core/time/time_wasi.odin @@ -1,5 +1,5 @@ -//+private -//+build wasi +#+private +#+build wasi package time import "base:intrinsics" diff --git a/core/time/time_windows.odin b/core/time/time_windows.odin index 378b914b0..553ea6d4e 100644 --- a/core/time/time_windows.odin +++ b/core/time/time_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package time import win32 "core:sys/windows" diff --git a/core/time/tsc_darwin.odin b/core/time/tsc_darwin.odin index 841c0b692..3726cff49 100644 --- a/core/time/tsc_darwin.odin +++ b/core/time/tsc_darwin.odin @@ -1,4 +1,4 @@ -//+private +#+private package time import "core:sys/unix" diff --git a/core/time/tsc_freebsd.odin b/core/time/tsc_freebsd.odin index f4d6ccc3a..dabcb69cb 100644 --- a/core/time/tsc_freebsd.odin +++ b/core/time/tsc_freebsd.odin @@ -1,5 +1,5 @@ -//+private -//+build freebsd +#+private +#+build freebsd package time import "core:c" diff --git a/core/time/tsc_linux.odin b/core/time/tsc_linux.odin index 77a79fe52..a83634414 100644 --- a/core/time/tsc_linux.odin +++ b/core/time/tsc_linux.odin @@ -1,5 +1,5 @@ -//+private -//+build linux +#+private +#+build linux package time import linux "core:sys/linux" diff --git a/core/unicode/tools/generate_entity_table.odin b/core/unicode/tools/generate_entity_table.odin index 16baa1adf..020ef94e4 100644 --- a/core/unicode/tools/generate_entity_table.odin +++ b/core/unicode/tools/generate_entity_table.odin @@ -161,7 +161,7 @@ generate_encoding_entity_table :: proc() { Input: entity_name - a string, like "copy" that describes a user-encoded Unicode entity as used in XML. - Output: + Returns: "decoded" - The decoded rune if found by name, or -1 otherwise. "ok" - true if found, false if not. |