From e7e1866e5047a85648e758a2d94c8247c65a6608 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 27 Apr 2021 13:09:37 +0100 Subject: Fix #893 --- core/fmt/fmt.odin | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'core') diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 6de6b0245..f9c386ffb 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -1908,17 +1908,6 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { } } - - handle_relative_pointer :: proc(ptr: ^$T) -> rawptr where intrinsics.type_is_integer(T) { - if ptr^ == 0 { - return nil; - } - when intrinsics.type_is_unsigned(T) { - return rawptr(uintptr(ptr) + uintptr(ptr^)); - } else { - return rawptr(uintptr(ptr) + uintptr(i64(ptr^))); - } - } } fmt_complex :: proc(fi: ^Info, c: complex128, bits: int, verb: rune) { -- cgit v1.2.3 From 96b60d8779c150842a3e36ffb7b733938dd7a8f6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 27 Apr 2021 16:56:11 +0100 Subject: Reimplement sync2.Sema on windows with WaitOnAddress primitives --- core/sync/sync2/primitives.odin | 25 +++---------------------- core/sync/sync2/primitives_atomic.odin | 30 ++++++++++++++++++++++++++++++ core/sync/sync2/primitives_pthreads.odin | 29 +++++++++++++++++++++++++++++ core/sync/sync2/primitives_windows.odin | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 22 deletions(-) (limited to 'core') diff --git a/core/sync/sync2/primitives.odin b/core/sync/sync2/primitives.odin index dd6688a50..e7c29afc8 100644 --- a/core/sync/sync2/primitives.odin +++ b/core/sync/sync2/primitives.odin @@ -153,33 +153,14 @@ cond_broadcast :: proc(c: ^Cond) { // // A Sema must not be copied after first use Sema :: struct { - // TODO(bill): Is this implementation too lazy? - // Can this be made to work on all OSes without construction and destruction, i.e. Zero is Initialized - - mutex: Mutex, - cond: Cond, - count: int, + impl: _Sema, } sema_wait :: proc(s: ^Sema) { - mutex_lock(&s.mutex); - defer mutex_unlock(&s.mutex); - - for s.count == 0 { - cond_wait(&s.cond, &s.mutex); - } - - s.count -= 1; - if s.count > 0 { - cond_signal(&s.cond); - } + _sema_wait(s); } sema_post :: proc(s: ^Sema, count := 1) { - mutex_lock(&s.mutex); - defer mutex_unlock(&s.mutex); - - s.count += count; - cond_signal(&s.cond); + _sema_post(s, count); } diff --git a/core/sync/sync2/primitives_atomic.odin b/core/sync/sync2/primitives_atomic.odin index 610ab7ee0..0d6b14987 100644 --- a/core/sync/sync2/primitives_atomic.odin +++ b/core/sync/sync2/primitives_atomic.odin @@ -240,5 +240,35 @@ _cond_broadcast :: proc(c: ^Cond) { } } +_Sema :: struct { + mutex: Mutex, + cond: Cond, + count: int, +} + +_sema_wait :: proc(s: ^Sema) { + mutex_lock(&s.impl.mutex); + defer mutex_unlock(&s.impl.mutex); + + for s.impl.count == 0 { + cond_wait(&s.impl.cond, &s.impl.mutex); + } + + s.impl.count -= 1; + if s.impl.count > 0 { + cond_signal(&s.impl.cond); + } +} + +_sema_post :: proc(s: ^Sema, count := 1) { + mutex_lock(&s.impl.mutex); + defer mutex_unlock(&s.impl.mutex); + + s.impl.count += count; + cond_signal(&s.impl.cond); +} + + + } // !ODIN_SYNC_USE_PTHREADS diff --git a/core/sync/sync2/primitives_pthreads.odin b/core/sync/sync2/primitives_pthreads.odin index e85cff7fc..17940c991 100644 --- a/core/sync/sync2/primitives_pthreads.odin +++ b/core/sync/sync2/primitives_pthreads.odin @@ -150,5 +150,34 @@ _cond_broadcast :: proc(c: ^Cond) { assert(err == 0); } +_Sema :: struct { + mutex: Mutex, + cond: Cond, + count: int, +} + +_sema_wait :: proc(s: ^Sema) { + mutex_lock(&s.impl.mutex); + defer mutex_unlock(&s.impl.mutex); + + for s.impl.count == 0 { + cond_wait(&s.impl.cond, &s.impl.mutex); + } + + s.impl.count -= 1; + if s.impl.count > 0 { + cond_signal(&s.impl.cond); + } +} + +_sema_post :: proc(s: ^Sema, count := 1) { + mutex_lock(&s.impl.mutex); + defer mutex_unlock(&s.impl.mutex); + + s.impl.count += count; + cond_signal(&s.impl.cond); +} + + } // ODIN_SYNC_USE_PTHREADS diff --git a/core/sync/sync2/primitives_windows.odin b/core/sync/sync2/primitives_windows.odin index 02b6cd733..0f8bce5ca 100644 --- a/core/sync/sync2/primitives_windows.odin +++ b/core/sync/sync2/primitives_windows.odin @@ -71,3 +71,35 @@ _cond_signal :: proc(c: ^Cond) { _cond_broadcast :: proc(c: ^Cond) { win32.WakeAllConditionVariable(&c.impl.cond); } + + +_Sema :: struct { + count: int, +} + +_sema_wait :: proc(s: ^Sema) { + for { + original_count := s.impl.count; + for original_count == 0 { + win32.WaitOnAddress( + &s.impl.count, + &original_count, + size_of(original_count), + win32.INFINITE, + ); + original_count = s.impl.count; + } + if original_count == atomic_cxchg(&s.impl.count, original_count-1, original_count) { + return; + } + } +} + +_sema_post :: proc(s: ^Sema, count := 1) { + atomic_add(&s.impl.count, count); + if count == 1 { + win32.WakeByAddressSingle(&s.impl.count); + } else { + win32.WakeByAddressAll(&s.impl.count); + } +} -- cgit v1.2.3 From 7ac80544a1e36b81c0b96bac9e7a94d6207058f9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 27 Apr 2021 16:59:25 +0100 Subject: Max sync2.Sema on windows be `i32` for the counter internally. --- core/sync/sync2/primitives_windows.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'core') diff --git a/core/sync/sync2/primitives_windows.odin b/core/sync/sync2/primitives_windows.odin index 0f8bce5ca..0eb6579a7 100644 --- a/core/sync/sync2/primitives_windows.odin +++ b/core/sync/sync2/primitives_windows.odin @@ -74,7 +74,7 @@ _cond_broadcast :: proc(c: ^Cond) { _Sema :: struct { - count: int, + count: i32, } _sema_wait :: proc(s: ^Sema) { @@ -96,7 +96,7 @@ _sema_wait :: proc(s: ^Sema) { } _sema_post :: proc(s: ^Sema, count := 1) { - atomic_add(&s.impl.count, count); + atomic_add(&s.impl.count, i32(count)); if count == 1 { win32.WakeByAddressSingle(&s.impl.count); } else { -- cgit v1.2.3 From 17390cd317192345d7c2a03eefb4af2032705753 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 27 Apr 2021 17:19:12 +0100 Subject: Implement sync2.Recursive_Mutex using `WaitOnAddress` and friends on Windows --- core/sync/sync2/primitives.odin | 41 +++----------------------- core/sync/sync2/primitives_atomic.odin | 49 +++++++++++++++++++++++++++++++ core/sync/sync2/primitives_pthreads.odin | 48 ++++++++++++++++++++++++++++++ core/sync/sync2/primitives_windows.odin | 50 ++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+), 37 deletions(-) (limited to 'core') diff --git a/core/sync/sync2/primitives.odin b/core/sync/sync2/primitives.odin index e7c29afc8..07ac91515 100644 --- a/core/sync/sync2/primitives.odin +++ b/core/sync/sync2/primitives.odin @@ -72,52 +72,19 @@ rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool { // // A Recursive_Mutex must not be copied after first use Recursive_Mutex :: struct { - // TODO(bill): Is this implementation too lazy? - // Can this be made to work on all OSes without construction and destruction, i.e. Zero is Initialized - // CRITICAL_SECTION would be a perfect candidate for this on Windows but that cannot be "dumb" - - owner: int, - recursion: int, - mutex: Mutex, + impl: _Recursive_Mutex, } recursive_mutex_lock :: proc(m: ^Recursive_Mutex) { - tid := runtime.current_thread_id(); - if tid != m.owner { - mutex_lock(&m.mutex); - } - // inside the lock - m.owner = tid; - m.recursion += 1; + _recursive_mutex_lock(m); } recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) { - tid := runtime.current_thread_id(); - assert(tid == m.owner); - m.recursion -= 1; - recursion := m.recursion; - if recursion == 0 { - m.owner = 0; - } - if recursion == 0 { - mutex_unlock(&m.mutex); - } - // outside the lock - + _recursive_mutex_unlock(m); } recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool { - tid := runtime.current_thread_id(); - if m.owner == tid { - return mutex_try_lock(&m.mutex); - } - if !mutex_try_lock(&m.mutex) { - return false; - } - // inside the lock - m.owner = tid; - m.recursion += 1; - return true; + return _recursive_mutex_try_lock(m); } diff --git a/core/sync/sync2/primitives_atomic.odin b/core/sync/sync2/primitives_atomic.odin index 0d6b14987..7043f8c84 100644 --- a/core/sync/sync2/primitives_atomic.odin +++ b/core/sync/sync2/primitives_atomic.odin @@ -5,6 +5,7 @@ package sync2 when !#config(ODIN_SYNC_USE_PTHREADS, true) { import "core:time" +import "core:runtime" _Mutex_State :: enum i32 { Unlocked = 0, @@ -160,6 +161,54 @@ _rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool { } +_Recursive_Mutex :: struct { + owner: int, + recursion: int, + mutex: Mutex, +} + +_recursive_mutex_lock :: proc(m: ^Recursive_Mutex) { + tid := runtime.current_thread_id(); + if tid != m.impl.owner { + mutex_lock(&m.impl.mutex); + } + // inside the lock + m.impl.owner = tid; + m.impl.recursion += 1; +} + +_recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) { + tid := runtime.current_thread_id(); + assert(tid == m.impl.owner); + m.impl.recursion -= 1; + recursion := m.impl.recursion; + if recursion == 0 { + m.impl.owner = 0; + } + if recursion == 0 { + mutex_unlock(&m.impl.mutex); + } + // outside the lock + +} + +_recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool { + tid := runtime.current_thread_id(); + if m.impl.owner == tid { + return mutex_try_lock(&m.impl.mutex); + } + if !mutex_try_lock(&m.impl.mutex) { + return false; + } + // inside the lock + m.impl.owner = tid; + m.impl.recursion += 1; + return true; +} + + + + Queue_Item :: struct { next: ^Queue_Item, diff --git a/core/sync/sync2/primitives_pthreads.odin b/core/sync/sync2/primitives_pthreads.odin index 17940c991..ea0d140d8 100644 --- a/core/sync/sync2/primitives_pthreads.odin +++ b/core/sync/sync2/primitives_pthreads.odin @@ -5,6 +5,7 @@ package sync2 when #config(ODIN_SYNC_USE_PTHREADS, true) { import "core:time" +import "core:runtime" import "core:sys/unix" _Mutex_State :: enum i32 { @@ -120,6 +121,53 @@ _rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool { return false; } + +_Recursive_Mutex :: struct { + owner: int, + recursion: int, + mutex: Mutex, +} + +_recursive_mutex_lock :: proc(m: ^Recursive_Mutex) { + tid := runtime.current_thread_id(); + if tid != m.impl.owner { + mutex_lock(&m.impl.mutex); + } + // inside the lock + m.impl.owner = tid; + m.impl.recursion += 1; +} + +_recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) { + tid := runtime.current_thread_id(); + assert(tid == m.impl.owner); + m.impl.recursion -= 1; + recursion := m.impl.recursion; + if recursion == 0 { + m.impl.owner = 0; + } + if recursion == 0 { + mutex_unlock(&m.impl.mutex); + } + // outside the lock + +} + +_recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool { + tid := runtime.current_thread_id(); + if m.impl.owner == tid { + return mutex_try_lock(&m.impl.mutex); + } + if !mutex_try_lock(&m.impl.mutex) { + return false; + } + // inside the lock + m.impl.owner = tid; + m.impl.recursion += 1; + return true; +} + + _Cond :: struct { pthread_cond: unix.pthread_cond_t, } diff --git a/core/sync/sync2/primitives_windows.odin b/core/sync/sync2/primitives_windows.odin index 0eb6579a7..a66b4a9bd 100644 --- a/core/sync/sync2/primitives_windows.odin +++ b/core/sync/sync2/primitives_windows.odin @@ -50,6 +50,56 @@ _rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool { } +_Recursive_Mutex :: struct { + owner: u32, + claim_count: i32, +} + +_recursive_mutex_lock :: proc(m: ^Recursive_Mutex) { + tid := win32.GetCurrentThreadId(); + for { + prev_owner := atomic_cxchg_acq(&m.impl.owner, tid, 0); + switch prev_owner { + case 0, tid: + m.impl.claim_count += 1; + // inside the lock + return; + } + + win32.WaitOnAddress( + &m.impl.owner, + &prev_owner, + size_of(prev_owner), + win32.INFINITE, + ); + } +} + +_recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) { + m.impl.claim_count -= 1; + if m.impl.claim_count != 0 { + return; + } + atomic_xchg_rel(&m.impl.owner, 0); + win32.WakeByAddressSingle(&m.impl.owner); + // outside the lock + +} + +_recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool { + tid := win32.GetCurrentThreadId(); + prev_owner := atomic_cxchg_acq(&m.impl.owner, tid, 0); + switch prev_owner { + case 0, tid: + m.impl.claim_count += 1; + // inside the lock + return true; + } + return false; +} + + + _Cond :: struct { cond: win32.CONDITION_VARIABLE, -- cgit v1.2.3 From ffffb04d8564f22b0e594c024c4dddf305428d6c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 27 Apr 2021 17:21:52 +0100 Subject: Remove unused import --- core/sync/sync2/primitives.odin | 1 - 1 file changed, 1 deletion(-) (limited to 'core') diff --git a/core/sync/sync2/primitives.odin b/core/sync/sync2/primitives.odin index 07ac91515..bf16ea735 100644 --- a/core/sync/sync2/primitives.odin +++ b/core/sync/sync2/primitives.odin @@ -1,7 +1,6 @@ package sync2 import "core:time" -import "core:runtime" // A Mutex is a mutual exclusion lock // The zero value for a Mutex is an unlocked mutex -- cgit v1.2.3 From 24fce21d9055ac4cd2124c0fe5396f430e99f24f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 28 Apr 2021 10:49:30 +0100 Subject: Add "naked" calling convention (removes prologue and epilogue) --- core/runtime/core.odin | 1 + src/docs_writer.cpp | 3 +++ src/llvm_backend.cpp | 4 ++++ src/llvm_backend.hpp | 1 + src/parser.cpp | 1 + src/parser.hpp | 3 ++- src/types.cpp | 3 +++ 7 files changed, 15 insertions(+), 1 deletion(-) (limited to 'core') diff --git a/core/runtime/core.odin b/core/runtime/core.odin index 0033aad9a..5f016579f 100644 --- a/core/runtime/core.odin +++ b/core/runtime/core.odin @@ -32,6 +32,7 @@ Calling_Convention :: enum u8 { Fast_Call = 5, None = 6, + Naked = 7, } Type_Info_Enum_Value :: distinct i64; diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index f4fd02376..03ea25930 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -697,6 +697,9 @@ OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { case ProcCC_None: calling_convention = str_lit("none"); break; + case ProcCC_Naked: + calling_convention = str_lit("naked"); + break; case ProcCC_InlineAsm: calling_convention = str_lit("inline-assembly"); break; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 74d5bbe01..f81077b47 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2588,6 +2588,10 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity) { lb_add_attribute_to_proc(m, p->value, "noreturn"); } + if (pt->Proc.calling_convention == ProcCC_Naked) { + lb_add_attribute_to_proc(m, p->value, "naked"); + } + switch (p->inlining) { case ProcInlining_inline: lb_add_attribute_to_proc(m, p->value, "alwaysinline"); diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index e8811a91e..cd6a99c20 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -472,6 +472,7 @@ lbCallingConventionKind const lb_calling_convention_map[ProcCC_MAX] = { lbCallingConvention_X86_FastCall, // ProcCC_FastCall, lbCallingConvention_C, // ProcCC_None, + lbCallingConvention_C, // ProcCC_Naked, lbCallingConvention_C, // ProcCC_InlineAsm, }; diff --git a/src/parser.cpp b/src/parser.cpp index a7e4c9162..a6764de08 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -3220,6 +3220,7 @@ ProcCallingConvention string_to_calling_convention(String s) { if (s == "fastcall") return ProcCC_FastCall; if (s == "fast") return ProcCC_FastCall; if (s == "none") return ProcCC_None; + if (s == "naked") return ProcCC_Naked; return ProcCC_Invalid; } diff --git a/src/parser.hpp b/src/parser.hpp index 8c2eadb46..93c0363ab 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -215,8 +215,9 @@ enum ProcCallingConvention { ProcCC_FastCall = 5, ProcCC_None = 6, + ProcCC_Naked = 7, - ProcCC_InlineAsm = 7, + ProcCC_InlineAsm = 8, ProcCC_MAX, diff --git a/src/types.cpp b/src/types.cpp index 56081acc8..7b5b81c43 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -3618,6 +3618,9 @@ gbString write_type_to_string(gbString str, Type *type) { case ProcCC_None: str = gb_string_appendc(str, " \"none\" "); break; + case ProcCC_Naked: + str = gb_string_appendc(str, " \"naked\" "); + break; // case ProcCC_VectorCall: // str = gb_string_appendc(str, " \"vectorcall\" "); // break; -- cgit v1.2.3 From 58e023e0cf581db71b4bf3c341b781a2f09ff50a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 30 Apr 2021 00:21:52 +0200 Subject: Add `compress` and `image` to core. --- core/compress/common.odin | 203 +++++ core/compress/gzip/example.odin | 70 ++ core/compress/gzip/gzip.odin | 314 ++++++++ core/compress/zlib/example.odin | 42 ++ core/compress/zlib/zlib.odin | 602 +++++++++++++++ core/image/common.odin | 107 +++ core/image/png/example.odin | 327 ++++++++ core/image/png/helpers.odin | 521 +++++++++++++ core/image/png/png.odin | 1590 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 3776 insertions(+) create mode 100644 core/compress/common.odin create mode 100644 core/compress/gzip/example.odin create mode 100644 core/compress/gzip/gzip.odin create mode 100644 core/compress/zlib/example.odin create mode 100644 core/compress/zlib/zlib.odin create mode 100644 core/image/common.odin create mode 100644 core/image/png/example.odin create mode 100644 core/image/png/helpers.odin create mode 100644 core/image/png/png.odin (limited to 'core') diff --git a/core/compress/common.odin b/core/compress/common.odin new file mode 100644 index 000000000..99b054903 --- /dev/null +++ b/core/compress/common.odin @@ -0,0 +1,203 @@ +package compress + +import "core:io" +import "core:image" + +// Error helper, e.g. is_kind(err, General_Error.OK); +is_kind :: proc(u: $U, x: $V) -> bool { + v, ok := u.(V); + return ok && v == x; +} + +Error :: union { + General_Error, + Deflate_Error, + ZLIB_Error, + GZIP_Error, + ZIP_Error, + /* + This is here because png.load will return a this type of error union, + as it may involve an I/O error, a Deflate error, etc. + */ + image.PNG_Error, +} + +General_Error :: enum { + OK = 0, + File_Not_Found, + Cannot_Open_File, + File_Too_Short, + Stream_Too_Short, + Output_Too_Short, + Unknown_Compression_Method, + Checksum_Failed, + Incompatible_Options, + Unimplemented, +} + +GZIP_Error :: enum { + Invalid_GZIP_Signature, + Reserved_Flag_Set, + Invalid_Extra_Data, + Original_Name_Too_Long, + Comment_Too_Long, + Payload_Length_Invalid, + Payload_CRC_Invalid, +} + +ZIP_Error :: enum { + Invalid_ZIP_File_Signature, + Unexpected_Signature, + Insert_Next_Disk, + Expected_End_of_Central_Directory_Record, +} + +ZLIB_Error :: enum { + Unsupported_Window_Size, + FDICT_Unsupported, + Unsupported_Compression_Level, + Code_Buffer_Malformed, +} + +Deflate_Error :: enum { + Huffman_Bad_Sizes, + Huffman_Bad_Code_Lengths, + Inflate_Error, + Bad_Distance, + Bad_Huffman_Code, + Len_Nlen_Mismatch, + BType_3, +} + +// General context for ZLIB, LZW, etc. +Context :: struct { + code_buffer: u32, + num_bits: i8, + /* + num_bits will be set to -100 if the buffer is malformed + */ + eof: b8, + + input: io.Stream, + output: io.Stream, + bytes_written: i64, + // Used to update hash as we write instead of all at once + rolling_hash: u32, + + // Sliding window buffer. Size must be a power of two. + window_size: i64, + last: ^[dynamic]byte, +} + +// Stream helpers +/* + TODO: These need to be optimized. + + Streams should really only check if a certain method is available once, perhaps even during setup. + + Bit and byte readers may be merged so that reading bytes will grab them from the bit buffer first. + This simplifies end-of-stream handling where bits may be left in the bit buffer. +*/ + +read_data :: #force_inline proc(c: ^Context, $T: typeid) -> (res: T, err: io.Error) { + b := make([]u8, size_of(T), context.temp_allocator); + r, e1 := io.to_reader(c.input); + _, e2 := io.read(r, b); + if !e1 || e2 != .None { + return T{}, e2; + } + + res = (^T)(raw_data(b))^; + return res, .None; +} + +read_u8 :: #force_inline proc(z: ^Context) -> (res: u8, err: io.Error) { + return read_data(z, u8); +} + +peek_data :: #force_inline proc(c: ^Context, $T: typeid) -> (res: T, err: io.Error) { + // Get current position to read from. + curr, e1 := c.input->impl_seek(0, .Current); + if e1 != .None { + return T{}, e1; + } + r, e2 := io.to_reader_at(c.input); + if !e2 { + return T{}, .Empty; + } + b := make([]u8, size_of(T), context.temp_allocator); + _, e3 := io.read_at(r, b, curr); + if e3 != .None { + return T{}, .Empty; + } + + res = (^T)(raw_data(b))^; + return res, .None; +} + +// Sliding window read back +peek_back_byte :: proc(c: ^Context, offset: i64) -> (res: u8, err: io.Error) { + // Look back into the sliding window. + return c.last[offset % c.window_size], .None; +} + +// Generalized bit reader LSB +refill_lsb :: proc(z: ^Context, width := i8(24)) { + for { + if z.num_bits > width { + break; + } + if z.code_buffer == 0 && z.num_bits == -1 { + z.num_bits = 0; + } + if z.code_buffer >= 1 << uint(z.num_bits) { + // Code buffer is malformed. + z.num_bits = -100; + return; + } + c, err := read_u8(z); + if err != .None { + // This is fine at the end of the file. + z.num_bits = -42; + z.eof = true; + return; + } + z.code_buffer |= (u32(c) << u8(z.num_bits)); + z.num_bits += 8; + } +} + +consume_bits_lsb :: #force_inline proc(z: ^Context, width: u8) { + z.code_buffer >>= width; + z.num_bits -= i8(width); +} + +peek_bits_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 { + if z.num_bits < i8(width) { + refill_lsb(z); + } + // assert(z.num_bits >= i8(width)); + return z.code_buffer & ~(~u32(0) << width); +} + +peek_bits_no_refill_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 { + assert(z.num_bits >= i8(width)); + return z.code_buffer & ~(~u32(0) << width); +} + +read_bits_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 { + k := peek_bits_lsb(z, width); + consume_bits_lsb(z, width); + return k; +} + +read_bits_no_refill_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 { + k := peek_bits_no_refill_lsb(z, width); + consume_bits_lsb(z, width); + return k; +} + +discard_to_next_byte_lsb :: proc(z: ^Context) { + discard := u8(z.num_bits & 7); + consume_bits_lsb(z, discard); +} \ No newline at end of file diff --git a/core/compress/gzip/example.odin b/core/compress/gzip/example.odin new file mode 100644 index 000000000..fa0cba9db --- /dev/null +++ b/core/compress/gzip/example.odin @@ -0,0 +1,70 @@ +//+ignore +package gzip + +import "core:compress/gzip" +import "core:bytes" +import "core:os" + +// 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; + defer bytes.buffer_destroy(&buf); + + 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 := gzip.load(&TEST, &buf); + if gzip.is_kind(err, gzip.E_General.OK) { + stdout("Displaying test vector: "); + stdout(bytes.buffer_to_string(&buf)); + stdout("\n"); + } + } + + // The rest are all files. + args = args[1:]; + err: gzip.Error; + + for file in args { + if file == "-" { + // Read from stdin + s := os.stream_from_handle(os.stdin); + err = gzip.load(&s, &buf); + } else { + err = gzip.load(file, &buf); + } + if !gzip.is_kind(err, gzip.E_General.OK) { + if gzip.is_kind(err, gzip.E_General.File_Not_Found) { + stderr("File not found: "); + stderr(file); + stderr("\n"); + os.exit(1); + } + stderr("GZIP returned an error.\n"); + os.exit(2); + } + stdout(bytes.buffer_to_string(&buf)); + } + os.exit(0); +} \ No newline at end of file diff --git a/core/compress/gzip/gzip.odin b/core/compress/gzip/gzip.odin new file mode 100644 index 000000000..3278d7c1d --- /dev/null +++ b/core/compress/gzip/gzip.odin @@ -0,0 +1,314 @@ +package gzip + +import "core:compress/zlib" +import "core:compress" +import "core:os" +import "core:io" +import "core:bytes" +import "core:hash" + +/* + + This package implements support for the GZIP file format v4.3, + as specified in RFC 1952. + + It is implemented in such a way that it lends itself naturally + to be the input to a complementary TAR implementation. + +*/ + +Magic :: enum u16le { + GZIP = 0x8b << 8 | 0x1f, +} + +Header :: struct #packed { + magic: Magic, + compression_method: Compression, + flags: Header_Flags, + modification_time: u32le, + xfl: Compression_Flags, + os: OS, +} +#assert(size_of(Header) == 10); + +Header_Flag :: enum u8 { + // Order is important + text = 0, + header_crc = 1, + extra = 2, + name = 3, + comment = 4, + reserved_1 = 5, + reserved_2 = 6, + reserved_3 = 7, +} +Header_Flags :: distinct bit_set[Header_Flag; u8]; + +OS :: enum u8 { + FAT = 0, + Amiga = 1, + VMS = 2, + Unix = 3, + VM_CMS = 4, + Atari_TOS = 5, + HPFS = 6, + Macintosh = 7, + Z_System = 8, + CP_M = 9, + TOPS_20 = 10, + NTFS = 11, + QDOS = 12, + Acorn_RISCOS = 13, + _Unknown = 14, + Unknown = 255, +} +OS_Name :: #partial [OS]string{ + .FAT = "FAT", + .Amiga = "Amiga", + .VMS = "VMS/OpenVMS", + .Unix = "Unix", + .VM_CMS = "VM/CMS", + .Atari_TOS = "Atari TOS", + .HPFS = "HPFS", + .Macintosh = "Macintosh", + .Z_System = "Z-System", + .CP_M = "CP/M", + .TOPS_20 = "TOPS-20", + .NTFS = "NTFS", + .QDOS = "QDOS", + .Acorn_RISCOS = "Acorn RISCOS", + .Unknown = "Unknown", +}; + +Compression :: enum u8 { + DEFLATE = 8, +} + +Compression_Flags :: enum u8 { + Maximum_Compression = 2, + Fastest_Compression = 4, +} + +Error :: compress.Error; +E_General :: compress.General_Error; +E_GZIP :: compress.GZIP_Error; +E_ZLIB :: compress.ZLIB_Error; +E_Deflate :: compress.Deflate_Error; +is_kind :: compress.is_kind; + +load_from_slice :: proc(slice: ^[]u8, buf: ^bytes.Buffer, allocator := context.allocator) -> (err: Error) { + + r := bytes.Reader{}; + bytes.reader_init(&r, slice^); + stream := bytes.reader_to_stream(&r); + + err = load_from_stream(&stream, buf, allocator); + + return err; +} + +load_from_file :: proc(filename: string, buf: ^bytes.Buffer, allocator := context.allocator) -> (err: Error) { + data, ok := os.read_entire_file(filename, context.temp_allocator); + if ok { + err = load_from_slice(&data, buf, allocator); + return; + } else { + return E_General.File_Not_Found; + } +} + +load_from_stream :: proc(stream: ^io.Stream, buf: ^bytes.Buffer, allocator := context.allocator) -> (err: Error) { + + ctx := compress.Context{ + input = stream^, + }; + buf := buf; + ws := bytes.buffer_to_stream(buf); + ctx.output = ws; + + header, e := compress.read_data(&ctx, Header); + if e != .None { + return E_General.File_Too_Short; + } + + if header.magic != .GZIP { + return E_GZIP.Invalid_GZIP_Signature; + } + if header.compression_method != .DEFLATE { + return E_General.Unknown_Compression_Method; + } + + if header.os >= ._Unknown { + header.os = .Unknown; + } + + if .reserved_1 in header.flags || .reserved_2 in header.flags || .reserved_3 in header.flags { + return E_GZIP.Reserved_Flag_Set; + } + + // printf("signature: %v\n", header.magic); + // printf("compression: %v\n", header.compression_method); + // printf("flags: %v\n", header.flags); + // printf("modification time: %v\n", time.unix(i64(header.modification_time), 0)); + // printf("xfl: %v (%v)\n", header.xfl, int(header.xfl)); + // printf("os: %v\n", OS_Name[header.os]); + + if .extra in header.flags { + xlen, e_extra := compress.read_data(&ctx, u16le); + if e_extra != .None { + return E_General.Stream_Too_Short; + } + // printf("Extra data present (%v bytes)\n", xlen); + if xlen < 4 { + // Minimum length is 2 for ID + 2 for a field length, if set to zero. + return E_GZIP.Invalid_Extra_Data; + } + + field_id: [2]u8; + field_length: u16le; + field_error: io.Error; + + for xlen >= 4 { + // println("Parsing Extra field(s)."); + field_id, field_error = compress.read_data(&ctx, [2]u8); + if field_error != .None { + // printf("Parsing Extra returned: %v\n", field_error); + return E_General.Stream_Too_Short; + } + xlen -= 2; + + field_length, field_error = compress.read_data(&ctx, u16le); + if field_error != .None { + // printf("Parsing Extra returned: %v\n", field_error); + return E_General.Stream_Too_Short; + } + xlen -= 2; + + if xlen <= 0 { + // We're not going to try and recover by scanning for a ZLIB header. + // Who knows what else is wrong with this file. + return E_GZIP.Invalid_Extra_Data; + } + + // printf(" Field \"%v\" of length %v found: ", string(field_id[:]), field_length); + if field_length > 0 { + field_data := make([]u8, field_length, context.temp_allocator); + _, field_error = ctx.input->impl_read(field_data); + if field_error != .None { + // printf("Parsing Extra returned: %v\n", field_error); + return E_General.Stream_Too_Short; + } + xlen -= field_length; + + // printf("%v\n", string(field_data)); + } + + if xlen != 0 { + return E_GZIP.Invalid_Extra_Data; + } + } + } + + if .name in header.flags { + // Should be enough. + name: [1024]u8; + b: [1]u8; + i := 0; + name_error: io.Error; + + for i < len(name) { + _, name_error = ctx.input->impl_read(b[:]); + if name_error != .None { + return E_General.Stream_Too_Short; + } + if b == 0 { + break; + } + name[i] = b[0]; + i += 1; + if i >= len(name) { + return E_GZIP.Original_Name_Too_Long; + } + } + // printf("Original filename: %v\n", string(name[:i])); + } + + if .comment in header.flags { + // Should be enough. + comment: [1024]u8; + b: [1]u8; + i := 0; + comment_error: io.Error; + + for i < len(comment) { + _, comment_error = ctx.input->impl_read(b[:]); + if comment_error != .None { + return E_General.Stream_Too_Short; + } + if b == 0 { + break; + } + comment[i] = b[0]; + i += 1; + if i >= len(comment) { + return E_GZIP.Comment_Too_Long; + } + } + // printf("Comment: %v\n", string(comment[:i])); + } + + if .header_crc in header.flags { + crc16: [2]u8; + crc_error: io.Error; + _, crc_error = ctx.input->impl_read(crc16[:]); + if crc_error != .None { + return E_General.Stream_Too_Short; + } + /* + We don't actually check the CRC16 (lower 2 bytes of CRC32 of header data until the CRC field). + If we find a gzip file in the wild that sets this field, we can add proper support for it. + */ + } + + /* + We should have arrived at the ZLIB payload. + */ + + zlib_error := zlib.inflate_raw(&ctx); + + // fmt.printf("ZLIB returned: %v\n", zlib_error); + + if !is_kind(zlib_error, E_General.OK) || zlib_error == nil { + return zlib_error; + } + + /* + Read CRC32 using the ctx bit reader because zlib may leave bytes in there. + */ + compress.discard_to_next_byte_lsb(&ctx); + + payload_crc_b: [4]u8; + payload_len_b: [4]u8; + for i in 0..3 { + payload_crc_b[i] = u8(compress.read_bits_lsb(&ctx, 8)); + } + payload_crc := transmute(u32le)payload_crc_b; + for i in 0..3 { + payload_len_b[i] = u8(compress.read_bits_lsb(&ctx, 8)); + } + payload_len := int(transmute(u32le)payload_len_b); + + payload := bytes.buffer_to_bytes(buf); + crc32 := u32le(hash.crc32(payload)); + + if crc32 != payload_crc { + return E_GZIP.Payload_CRC_Invalid; + } + + if len(payload) != payload_len { + return E_GZIP.Payload_Length_Invalid; + } + return E_General.OK; +} + +load :: proc{load_from_file, load_from_slice, load_from_stream}; \ No newline at end of file diff --git a/core/compress/zlib/example.odin b/core/compress/zlib/example.odin new file mode 100644 index 000000000..a7fa76d62 --- /dev/null +++ b/core/compress/zlib/example.odin @@ -0,0 +1,42 @@ +//+ignore +package zlib + +import "core:compress/zlib" +import "core:bytes" +import "core:fmt" + +main :: proc() { + + ODIN_DEMO: []u8 = { + 120, 156, 101, 144, 77, 110, 131, 48, 16, 133, 215, 204, 41, 158, 44, + 69, 73, 32, 148, 182, 75, 35, 14, 208, 125, 47, 96, 185, 195, 143, + 130, 13, 50, 38, 81, 84, 101, 213, 75, 116, 215, 43, 246, 8, 53, + 82, 126, 8, 181, 188, 152, 153, 111, 222, 147, 159, 123, 165, 247, 170, + 98, 24, 213, 88, 162, 198, 244, 157, 243, 16, 186, 115, 44, 75, 227, + 5, 77, 115, 72, 137, 222, 117, 122, 179, 197, 39, 69, 161, 170, 156, + 50, 144, 5, 68, 130, 4, 49, 126, 127, 190, 191, 144, 34, 19, 57, + 69, 74, 235, 209, 140, 173, 242, 157, 155, 54, 158, 115, 162, 168, 12, + 181, 239, 246, 108, 17, 188, 174, 242, 224, 20, 13, 199, 198, 235, 250, + 194, 166, 129, 86, 3, 99, 157, 172, 37, 230, 62, 73, 129, 151, 252, + 70, 211, 5, 77, 31, 104, 188, 160, 113, 129, 215, 59, 205, 22, 52, + 123, 160, 83, 142, 255, 242, 89, 123, 93, 149, 200, 50, 188, 85, 54, + 252, 18, 248, 192, 238, 228, 235, 198, 86, 224, 118, 224, 176, 113, 166, + 112, 67, 106, 227, 159, 122, 215, 88, 95, 110, 196, 123, 205, 183, 224, + 98, 53, 8, 104, 213, 234, 201, 147, 7, 248, 192, 14, 170, 29, 25, + 171, 15, 18, 59, 138, 112, 63, 23, 205, 110, 254, 136, 109, 78, 231, + 63, 234, 138, 133, 204, + }; + + buf: bytes.Buffer; + + // We can pass ", true" to inflate a raw DEFLATE stream instead of a ZLIB wrapped one. + err := zlib.inflate(&ODIN_DEMO, &buf); + defer bytes.buffer_destroy(&buf); + + if !zlib.is_kind(err, zlib.E_General.OK) { + 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) == 438); +} \ No newline at end of file diff --git a/core/compress/zlib/zlib.odin b/core/compress/zlib/zlib.odin new file mode 100644 index 000000000..34a7984a7 --- /dev/null +++ b/core/compress/zlib/zlib.odin @@ -0,0 +1,602 @@ +package zlib + +import "core:compress" + +import "core:mem" +import "core:io" +import "core:bytes" +import "core:hash" +/* + zlib.inflate decompresses a ZLIB stream passed in as a []u8 or io.Stream. + Returns: Error. You can use zlib.is_kind or compress.is_kind to easily test for OK. +*/ + +Context :: compress.Context; + +Compression_Method :: enum u8 { + DEFLATE = 8, + Reserved = 15, +} + +Compression_Level :: enum u8 { + Fastest = 0, + Fast = 1, + Default = 2, + Maximum = 3, +} + +Options :: struct { + window_size: u16, + level: u8, +} + +Error :: compress.Error; +E_General :: compress.General_Error; +E_ZLIB :: compress.ZLIB_Error; +E_Deflate :: compress.Deflate_Error; +is_kind :: compress.is_kind; + +DEFLATE_MAX_CHUNK_SIZE :: 65535; +DEFLATE_MAX_LITERAL_SIZE :: 65535; +DEFLATE_MAX_DISTANCE :: 32768; +DEFLATE_MAX_LENGTH :: 258; + +HUFFMAN_MAX_BITS :: 16; +HUFFMAN_FAST_BITS :: 9; +HUFFMAN_FAST_MASK :: ((1 << HUFFMAN_FAST_BITS) - 1); + +Z_LENGTH_BASE := [31]u16{ + 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0, +}; + +Z_LENGTH_EXTRA := [31]u8{ + 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0, +}; + +Z_DIST_BASE := [32]u16{ + 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, + 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0, +}; + +Z_DIST_EXTRA := [32]u8{ + 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0, +}; + +Z_LENGTH_DEZIGZAG := []u8{ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15, +}; + +Z_FIXED_LENGTH := [288]u8{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8, +}; + +Z_FIXED_DIST := [32]u8{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, +}; + +/* + Accelerate all cases in default tables. +*/ +ZFAST_BITS :: 9; +ZFAST_MASK :: ((1 << ZFAST_BITS) - 1); + +/* + ZLIB-style Huffman encoding. + JPEG packs from left, ZLIB from right. We can't share code. +*/ +Huffman_Table :: struct { + fast: [1 << ZFAST_BITS]u16, + firstcode: [16]u16, + maxcode: [17]int, + firstsymbol: [16]u16, + size: [288]u8, + value: [288]u16, +}; + +// Implementation starts here + +z_bit_reverse :: #force_inline proc(n: u16, bits: u8) -> (r: u16) { + assert(bits <= 16); + // NOTE: Can optimize with llvm.bitreverse.i64 or some bit twiddling + // by reversing all of the bits and masking out the unneeded ones. + r = n; + r = ((r & 0xAAAA) >> 1) | ((r & 0x5555) << 1); + r = ((r & 0xCCCC) >> 2) | ((r & 0x3333) << 2); + r = ((r & 0xF0F0) >> 4) | ((r & 0x0F0F) << 4); + r = ((r & 0xFF00) >> 8) | ((r & 0x00FF) << 8); + + r >>= (16 - bits); + return; +} + +write_byte :: #force_inline proc(z: ^Context, c: u8) -> (err: io.Error) #no_bounds_check { + c := c; + buf := transmute([]u8)mem.Raw_Slice{data=&c, len=1}; + z.rolling_hash = hash.adler32(buf, z.rolling_hash); + + _, e := z.output->impl_write(buf); + if e != .None { + return e; + } + z.last[z.bytes_written % z.window_size] = c; + + z.bytes_written += 1; + return .None; +} + +allocate_huffman_table :: proc(allocator := context.allocator) -> (z: ^Huffman_Table, err: Error) { + + z = new(Huffman_Table, allocator); + return z, E_General.OK; +} + +build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) { + sizes: [HUFFMAN_MAX_BITS+1]int; + next_code: [HUFFMAN_MAX_BITS]int; + + k := int(0); + + mem.zero_slice(sizes[:]); + mem.zero_slice(z.fast[:]); + + for v, _ in code_lengths { + sizes[v] += 1; + } + sizes[0] = 0; + + for i in 1..16 { + if sizes[i] > (1 << uint(i)) { + return E_Deflate.Huffman_Bad_Sizes; + } + } + code := int(0); + + for i in 1..<16 { + next_code[i] = code; + z.firstcode[i] = u16(code); + z.firstsymbol[i] = u16(k); + code = code + sizes[i]; + if sizes[i] != 0 { + if (code - 1 >= (1 << u16(i))) { + return E_Deflate.Huffman_Bad_Code_Lengths; + } + } + z.maxcode[i] = code << (16 - uint(i)); + code <<= 1; + k += int(sizes[i]); + } + + z.maxcode[16] = 0x10000; // Sentinel + c: int; + + for v, ci in code_lengths { + if v != 0 { + c = next_code[v] - int(z.firstcode[v]) + int(z.firstsymbol[v]); + fastv := u16((u16(v) << 9) | u16(ci)); + z.size[c] = u8(v); + z.value[c] = u16(ci); + if (v <= ZFAST_BITS) { + j := z_bit_reverse(u16(next_code[v]), v); + for j < (1 << ZFAST_BITS) { + z.fast[j] = fastv; + j += (1 << v); + } + } + next_code[v] += 1; + } + } + return E_General.OK; +} + +decode_huffman_slowpath :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check { + + r = 0; + err = E_General.OK; + + k: int; + s: u8; + + code := u16(compress.peek_bits_lsb(z, 16)); + + k = int(z_bit_reverse(code, 16)); + + #no_bounds_check for s = HUFFMAN_FAST_BITS+1; ; { + if k < t.maxcode[s] { + break; + } + s += 1; + } + if (s >= 16) { + return 0, E_Deflate.Bad_Huffman_Code; + } + // code size is s, so: + b := (k >> (16-s)) - int(t.firstcode[s]) + int(t.firstsymbol[s]); + if b >= size_of(t.size) { + return 0, E_Deflate.Bad_Huffman_Code; + } + if t.size[b] != s { + return 0, E_Deflate.Bad_Huffman_Code; + } + + compress.consume_bits_lsb(z, s); + + r = t.value[b]; + return r, E_General.OK; +} + +decode_huffman :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check { + + if z.num_bits < 16 { + if z.num_bits == -100 { + return 0, E_ZLIB.Code_Buffer_Malformed; + } + compress.refill_lsb(z); + if z.eof { + return 0, E_General.Stream_Too_Short; + } + } + #no_bounds_check b := t.fast[z.code_buffer & ZFAST_MASK]; + if b != 0 { + s := u8(b >> ZFAST_BITS); + compress.consume_bits_lsb(z, s); + return b & 511, E_General.OK; + } + return decode_huffman_slowpath(z, t); +} + +parse_huffman_block :: proc(z: ^Context, z_repeat, z_offset: ^Huffman_Table) -> (err: Error) #no_bounds_check { + + #no_bounds_check for { + value, e := decode_huffman(z, z_repeat); + if !is_kind(e, E_General.OK) { + return err; + } + if value < 256 { + e := write_byte(z, u8(value)); + if e != .None { + return E_General.Output_Too_Short; + } + } else { + if value == 256 { + // End of block + return E_General.OK; + } + + value -= 257; + length := Z_LENGTH_BASE[value]; + if Z_LENGTH_EXTRA[value] > 0 { + length += u16(compress.read_bits_lsb(z, Z_LENGTH_EXTRA[value])); + } + + value, e = decode_huffman(z, z_offset); + if !is_kind(e, E_General.OK) { + return E_Deflate.Bad_Huffman_Code; + } + + distance := Z_DIST_BASE[value]; + if Z_DIST_EXTRA[value] > 0 { + distance += u16(compress.read_bits_lsb(z, Z_DIST_EXTRA[value])); + } + + if z.bytes_written < i64(distance) { + // Distance is longer than we've decoded so far. + return E_Deflate.Bad_Distance; + } + + offset := i64(z.bytes_written - i64(distance)); + /* + These might be sped up with a repl_byte call that copies + from the already written output more directly, and that + update the Adler checksum once after. + + That way we'd suffer less Stream vtable overhead. + */ + if distance == 1 { + /* + Replicate the last outputted byte, length times. + */ + if length > 0 { + b, e := compress.peek_back_byte(z, offset); + if e != .None { + return E_General.Output_Too_Short; + } + #no_bounds_check for _ in 0.. 0 { + #no_bounds_check for _ in 0.. (err: Error) #no_bounds_check { + /* + ctx.input must be an io.Stream backed by an implementation that supports: + - read + - size + + ctx.output must be an io.Stream backed by an implementation that supports: + - write + + raw determines whether the ZLIB header is processed, or we're inflating a raw + DEFLATE stream. + */ + + if !raw { + data_size := io.size(ctx.input); + if data_size < 6 { + return E_General.Stream_Too_Short; + } + + cmf, _ := compress.read_u8(ctx); + + method := Compression_Method(cmf & 0xf); + if method != .DEFLATE { + return E_General.Unknown_Compression_Method; + } + + cinfo := (cmf >> 4) & 0xf; + if cinfo > 7 { + return E_ZLIB.Unsupported_Window_Size; + } + ctx.window_size = 1 << (cinfo + 8); + + flg, _ := compress.read_u8(ctx); + + fcheck := flg & 0x1f; + fcheck_computed := (cmf << 8 | flg) & 0x1f; + if fcheck != fcheck_computed { + return E_General.Checksum_Failed; + } + + fdict := (flg >> 5) & 1; + /* + We don't handle built-in dictionaries for now. + They're application specific and PNG doesn't use them. + */ + if fdict != 0 { + return E_ZLIB.FDICT_Unsupported; + } + + // flevel := Compression_Level((flg >> 6) & 3); + /* + Inflate can consume bits belonging to the Adler checksum. + We pass the entire stream to Inflate and will unget bytes if we need to + at the end to compare checksums. + */ + + // Seed the Adler32 rolling checksum. + ctx.rolling_hash = 1; + } + + // Parse ZLIB stream without header. + err = inflate_raw(ctx); + if !is_kind(err, E_General.OK) { + return err; + } + + if !raw { + compress.discard_to_next_byte_lsb(ctx); + + adler32 := compress.read_bits_lsb(ctx, 8) << 24 | compress.read_bits_lsb(ctx, 8) << 16 | compress.read_bits_lsb(ctx, 8) << 8 | compress.read_bits_lsb(ctx, 8); + if ctx.rolling_hash != u32(adler32) { + return E_General.Checksum_Failed; + } + } + return E_General.OK; +} + +// @(optimization_mode="speed") +inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> (err: Error) #no_bounds_check { + final := u32(0); + type := u32(0); + + z.num_bits = 0; + z.code_buffer = 0; + + z_repeat: ^Huffman_Table; + z_offset: ^Huffman_Table; + codelength_ht: ^Huffman_Table; + + z_repeat, err = allocate_huffman_table(allocator=context.allocator); + if !is_kind(err, E_General.OK) { + return err; + } + z_offset, err = allocate_huffman_table(allocator=context.allocator); + if !is_kind(err, E_General.OK) { + return err; + } + codelength_ht, err = allocate_huffman_table(allocator=context.allocator); + if !is_kind(err, E_General.OK) { + return err; + } + defer free(z_repeat); + defer free(z_offset); + defer free(codelength_ht); + + if z.window_size == 0 { + z.window_size = DEFLATE_MAX_DISTANCE; + } + + // Allocate rolling window buffer. + last_b := mem.make_dynamic_array_len_cap([dynamic]u8, z.window_size, z.window_size, allocator); + z.last = &last_b; + defer delete(last_b); + + for { + final = compress.read_bits_lsb(z, 1); + type = compress.read_bits_lsb(z, 2); + + // log.debugf("Final: %v | Type: %v\n", final, type); + + if type == 0 { + // Uncompressed block + + // Discard bits until next byte boundary + compress.discard_to_next_byte_lsb(z); + + uncompressed_len := int(compress.read_bits_lsb(z, 16)); + length_check := int(compress.read_bits_lsb(z, 16)); + if uncompressed_len != ~length_check { + return E_Deflate.Len_Nlen_Mismatch; + } + + /* + TODO: Maybe speed this up with a stream-to-stream copy (read_from) + and a single Adler32 update after. + */ + #no_bounds_check for uncompressed_len > 0 { + compress.refill_lsb(z); + lit := compress.read_bits_lsb(z, 8); + write_byte(z, u8(lit)); + uncompressed_len -= 1; + } + } else if type == 3 { + return E_Deflate.BType_3; + } else { + // log.debugf("Err: %v | Final: %v | Type: %v\n", err, final, type); + if type == 1 { + // Use fixed code lengths. + err = build_huffman(z_repeat, Z_FIXED_LENGTH[:]); + if !is_kind(err, E_General.OK) { + return err; + } + err = build_huffman(z_offset, Z_FIXED_DIST[:]); + if !is_kind(err, E_General.OK) { + return err; + } + } else { + lencodes: [286+32+137]u8; + codelength_sizes: [19]u8; + + //i: u32; + n: u32; + + compress.refill_lsb(z, 14); + hlit := compress.read_bits_no_refill_lsb(z, 5) + 257; + hdist := compress.read_bits_no_refill_lsb(z, 5) + 1; + hclen := compress.read_bits_no_refill_lsb(z, 4) + 4; + ntot := hlit + hdist; + + #no_bounds_check for i in 0..= 19 { + return E_Deflate.Huffman_Bad_Code_Lengths; + } + if c < 16 { + lencodes[n] = u8(c); + n += 1; + } else { + fill := u8(0); + compress.refill_lsb(z, 7); + if c == 16 { + c = u16(compress.read_bits_no_refill_lsb(z, 2) + 3); + if n == 0 { + return E_Deflate.Huffman_Bad_Code_Lengths; + } + fill = lencodes[n - 1]; + } else if c == 17 { + c = u16(compress.read_bits_no_refill_lsb(z, 3) + 3); + } else if c == 18 { + c = u16(compress.read_bits_no_refill_lsb(z, 7) + 11); + } else { + return E_Deflate.Huffman_Bad_Code_Lengths; + } + + if ntot - n < u32(c) { + return E_Deflate.Huffman_Bad_Code_Lengths; + } + + nc := n + u32(c); + #no_bounds_check for ; n < nc; n += 1 { + lencodes[n] = fill; + } + } + } + + if n != ntot { + return E_Deflate.Huffman_Bad_Code_Lengths; + } + + err = build_huffman(z_repeat, lencodes[:hlit]); + if !is_kind(err, E_General.OK) { + return err; + } + + err = build_huffman(z_offset, lencodes[hlit:ntot]); + if !is_kind(err, E_General.OK) { + return err; + } + } + err = parse_huffman_block(z, z_repeat, z_offset); + // log.debugf("Err: %v | Final: %v | Type: %v\n", err, final, type); + if !is_kind(err, E_General.OK) { + return err; + } + } + if final == 1 { + break; + } + } + return E_General.OK; +} + +inflate_from_byte_array :: proc(input: ^[]u8, buf: ^bytes.Buffer, raw := false) -> (err: Error) { + ctx := Context{}; + + r := bytes.Reader{}; + bytes.reader_init(&r, input^); + rs := bytes.reader_to_stream(&r); + ctx.input = rs; + + buf := buf; + ws := bytes.buffer_to_stream(buf); + ctx.output = ws; + + err = inflate_from_stream(&ctx, raw); + + return err; +} + +inflate_from_byte_array_raw :: proc(input: ^[]u8, buf: ^bytes.Buffer, raw := false) -> (err: Error) { + return inflate_from_byte_array(input, buf, true); +} + +inflate :: proc{inflate_from_stream, inflate_from_byte_array}; +inflate_raw :: proc{inflate_from_stream_raw, inflate_from_byte_array_raw}; \ No newline at end of file diff --git a/core/image/common.odin b/core/image/common.odin new file mode 100644 index 000000000..d05feabaa --- /dev/null +++ b/core/image/common.odin @@ -0,0 +1,107 @@ +package image + +import "core:bytes" + +Image :: struct { + width: int, + height: int, + channels: int, + depth: u8, + pixels: bytes.Buffer, + /* + Some image loaders/writers can return/take an optional background color. + For convenience, we return them as u16 so we don't need to switch on the type + in our viewer, and can just test against nil. + */ + background: Maybe([3]u16), + sidecar: any, +} + +/* +Image_Option: + `.info` + This option behaves as `return_ihdr` and `do_not_decompress_image` and can be used + to gather an image's dimensions and color information. + + `.return_header` + Fill out img.sidecar.header with the image's format-specific header struct. + If we only care about the image specs, we can set `return_header` + + `do_not_decompress_image`, or `.info`, which works as if both of these were set. + + `.return_metadata` + Returns all chunks not needed to decode the data. + It also returns the header as if `.return_header` is set. + + `do_not_decompress_image` + Skip decompressing IDAT chunk, defiltering and the rest. + + `alpha_add_if_missing` + If the image has no alpha channel, it'll add one set to max(type). + Turns RGB into RGBA and Gray into Gray+Alpha + + `alpha_drop_if_present` + If the image has an alpha channel, drop it. + You may want to use `alpha_premultiply` in this case. + + NOTE: For PNG, this also skips handling of the tRNS chunk, if present, + unless you select `alpha_premultiply`. + In this case it'll premultiply the specified pixels in question only, + as the others are implicitly fully opaque. + + `alpha_premultiply` + If the image has an alpha channel, returns image data as follows: + RGB *= A, Gray = Gray *= A + + `blend_background` + If a bKGD chunk is present in a PNG, we normally just set `img.background` + with its value and leave it up to the application to decide how to display the image, + as per the PNG specification. + + With `blend_background` selected, we blend the image against the background + color. As this negates the use for an alpha channel, we'll drop it _unless_ + you also specify `alpha_add_if_missing`. + + Options that don't apply to an image format will be ignored by their loader. +*/ + +Option :: enum { + info = 0, + do_not_decompress_image, + return_header, + return_metadata, + alpha_add_if_missing, + alpha_drop_if_present, + alpha_premultiply, + blend_background, +} +Options :: distinct bit_set[Option]; + +PNG_Error :: enum { + Invalid_PNG_Signature, + IHDR_Not_First_Chunk, + IHDR_Corrupt, + IDAT_Missing, + IDAT_Must_Be_Contiguous, + IDAT_Corrupt, + PNG_Does_Not_Adhere_to_Spec, + PLTE_Encountered_Unexpectedly, + PLTE_Invalid_Length, + TRNS_Encountered_Unexpectedly, + BKGD_Invalid_Length, + Invalid_Image_Dimensions, + Unknown_Color_Type, + Invalid_Color_Bit_Depth_Combo, + Unknown_Filter_Method, + Unknown_Interlace_Method, +} + + +/* + Functions to help with image buffer calculations +*/ + +compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) { + + size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height; + return; +} \ No newline at end of file diff --git a/core/image/png/example.odin b/core/image/png/example.odin new file mode 100644 index 000000000..8cac5a505 --- /dev/null +++ b/core/image/png/example.odin @@ -0,0 +1,327 @@ +//+ignore +package png + +import "core:compress" +import "core:image" +import "core:image/png" +import "core:bytes" +import "core:fmt" + +// For PPM writer +import "core:mem" +import "core:os" + +main :: proc() { + file: string; + + options := image.Options{}; + err: compress.Error; + img: ^image.Image; + + file = "../../../misc/logo-slim.png"; + + img, err = png.load(file, options); + defer png.destroy(img); + + if !png.is_kind(err, png.E_General.OK) { + fmt.printf("Trying to read PNG file %v returned %v\n", file, err); + } else { + v: png.Info; + ok: bool; + + fmt.printf("Image: %vx%vx%v, %v-bit.\n", img.width, img.height, img.channels, img.depth); + + if v, ok = img.sidecar.(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: + t, _ := png.core_time(c); + fmt.printf("[tIME]: %v\n", t); + case .gAMA: + fmt.printf("[gAMA]: %v\n", png.gamma(c)); + case .pHYs: + phys := png.phys(c); + if phys.unit == .Meter { + xm := f32(img.width) / f32(phys.ppu_x); + ym := f32(img.height) / f32(phys.ppu_y); + dpi_x, dpi_y := png.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 := png.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 png.text_destroy(res); + case .bKGD: + fmt.printf("[bKGD] %v\n", img.background); + case .eXIf: + res, ok_exif := png.exif(c); + if 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: + plte, plte_ok := png.plte(c); + if plte_ok { + fmt.printf("[PLTE] %v\n", plte); + } else { + fmt.printf("[PLTE] Error\n"); + } + case .hIST: + res, ok_hist := png.hist(c); + if ok_hist { + fmt.printf("[hIST] %v\n", res); + } + case .cHRM: + res, ok_chrm := png.chrm(c); + if ok_chrm { + fmt.printf("[cHRM] %v\n", res); + } + case .sPLT: + res, ok_splt := png.splt(c); + if ok_splt { + fmt.printf("[sPLT] %v\n", res); + } + png.splt_destroy(res); + case .sBIT: + if res, ok_sbit := png.sbit(c); ok_sbit { + fmt.printf("[sBIT] %v\n", res); + } + case .iCCP: + res, ok_iccp := png.iccp(c); + if ok_iccp { + fmt.printf("[iCCP] %v\n", res); + } + png.iccp_destroy(res); + case .sRGB: + if res, ok_srgb := png.srgb(c); ok_srgb { + fmt.printf("[sRGB] Rendering intent: %v\n", res); + } + case: + type := c.header.type; + name := png.chunk_type_to_name(&type); + fmt.printf("[%v]: %v\n", name, c.data); + } + } + } + } + + if is_kind(err, E_General.OK) && .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 != 0 { + return false; + } + defer close(fd); + + write_string(fd, + fmt.tprintf("P6\n%v %v\n%v\n", width, height, (1 << 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; +} \ No newline at end of file diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin new file mode 100644 index 000000000..a179cd23b --- /dev/null +++ b/core/image/png/helpers.odin @@ -0,0 +1,521 @@ +package png + +import "core:image" +import "core:compress/zlib" +import coretime "core:time" +import "core:strings" +import "core:bytes" +import "core:mem" + +/* + These are a few useful utility functions to work with PNG images. +*/ + +/* + Cleanup of image-specific data. + There are other helpers for cleanup of PNG-specific data. + Those are named *_destroy, where * is the name of the helper. +*/ + +destroy :: proc(img: ^Image) { + if img == nil { + /* + Nothing to do. + Load must've returned with an error. + */ + return; + } + + bytes.buffer_destroy(&img.pixels); + + /* + We don't need to do anything for the individual chunks. + They're allocated on the temp allocator, as is info.chunks + + See read_chunk. + */ + free(img); +} + +/* + Chunk helpers +*/ + +gamma :: proc(c: Chunk) -> f32 { + assert(c.header.type == .gAMA); + res := (^gAMA)(raw_data(c.data))^; + when true { + // Returns the wrong result on old backend + // Fixed for -llvm-api + return f32(res.gamma_100k) / 100_000.0; + } else { + return f32(u32(res.gamma_100k)) / 100_000.0; + } +} + +INCHES_PER_METER :: 1000.0 / 25.4; + +phys :: proc(c: Chunk) -> pHYs { + assert(c.header.type == .pHYs); + res := (^pHYs)(raw_data(c.data))^; + return res; +} + +phys_to_dpi :: proc(p: pHYs) -> (x_dpi, y_dpi: f32) { + return f32(p.ppu_x) / INCHES_PER_METER, f32(p.ppu_y) / INCHES_PER_METER; +} + +time :: proc(c: Chunk) -> tIME { + assert(c.header.type == .tIME); + res := (^tIME)(raw_data(c.data))^; + return res; +} + +core_time :: proc(c: Chunk) -> (t: coretime.Time, ok: bool) { + png_time := time(c); + using png_time; + return coretime.datetime_to_time( + int(year), int(month), int(day), + int(hour), int(minute), int(second)); +} + +text :: proc(c: Chunk) -> (res: Text, ok: bool) { + #partial switch c.header.type { + case .tEXt: + ok = true; + + fields := bytes.split(s=c.data, sep=[]u8{0}, allocator=context.temp_allocator); + if len(fields) == 2 { + res.keyword = strings.clone(string(fields[0])); + res.text = strings.clone(string(fields[1])); + } else { + ok = false; + } + return; + case .zTXt: + ok = true; + + fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator); + if len(fields) != 3 || len(fields[1]) != 0 { + // Compression method must be 0=Deflate, which thanks to the split above turns + // into an empty slice + ok = false; return; + } + + // Set up ZLIB context and decompress text payload. + buf: bytes.Buffer; + zlib_error := zlib.inflate_from_byte_array(&fields[2], &buf); + defer bytes.buffer_destroy(&buf); + if !is_kind(zlib_error, E_General.OK) { + ok = false; return; + } + + res.keyword = strings.clone(string(fields[0])); + res.text = strings.clone(bytes.buffer_to_string(&buf)); + return; + case .iTXt: + ok = true; + + s := string(c.data); + null := strings.index_byte(s, 0); + if null == -1 { + ok = false; return; + } + if len(c.data) < null + 4 { + // At a minimum, including the \0 following the keyword, we require 5 more bytes. + ok = false; return; + } + res.keyword = strings.clone(string(c.data[:null])); + rest := c.data[null+1:]; + + compression_flag := rest[:1][0]; + if compression_flag > 1 { + ok = false; return; + } + compression_method := rest[1:2][0]; + if compression_flag == 1 && compression_method > 0 { + // Only Deflate is supported + ok = false; return; + } + rest = rest[2:]; + + // We now expect an optional language keyword and translated keyword, both followed by a \0 + null = strings.index_byte(string(rest), 0); + if null == -1 { + ok = false; return; + } + res.language = strings.clone(string(rest[:null])); + rest = rest[null+1:]; + + null = strings.index_byte(string(rest), 0); + if null == -1 { + ok = false; return; + } + res.keyword_localized = strings.clone(string(rest[:null])); + rest = rest[null+1:]; + if compression_flag == 0 { + res.text = strings.clone(string(rest)); + } else { + // Set up ZLIB context and decompress text payload. + buf: bytes.Buffer; + zlib_error := zlib.inflate_from_byte_array(&rest, &buf); + defer bytes.buffer_destroy(&buf); + if !is_kind(zlib_error, E_General.OK) { + + ok = false; return; + } + + res.text = strings.clone(bytes.buffer_to_string(&buf)); + } + return; + case: + // PNG text helper called with an unrecognized chunk type. + ok = false; return; + + } +} + +text_destroy :: proc(text: Text) { + delete(text.keyword); + delete(text.keyword_localized); + delete(text.language); + delete(text.text); +} + +iccp :: proc(c: Chunk) -> (res: iCCP, ok: bool) { + ok = true; + + fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator); + + if len(fields[0]) < 1 || len(fields[0]) > 79 { + // Invalid profile name + ok = false; return; + } + + if len(fields[1]) != 0 { + // Compression method should be a zero, which the split turned into an empty slice. + ok = false; return; + } + + // Set up ZLIB context and decompress iCCP payload + buf: bytes.Buffer; + zlib_error := zlib.inflate_from_byte_array(&fields[2], &buf); + if !is_kind(zlib_error, E_General.OK) { + bytes.buffer_destroy(&buf); + ok = false; return; + } + + res.name = strings.clone(string(fields[0])); + res.profile = bytes.buffer_to_bytes(&buf); + + return; +} + +iccp_destroy :: proc(i: iCCP) { + delete(i.name); + + delete(i.profile); + +} + +srgb :: proc(c: Chunk) -> (res: sRGB, ok: bool) { + ok = true; + + if c.header.type != .sRGB || len(c.data) != 1 { + return {}, false; + } + + res.intent = sRGB_Rendering_Intent(c.data[0]); + if res.intent > max(sRGB_Rendering_Intent) { + ok = false; return; + } + return; +} + +plte :: proc(c: Chunk) -> (res: PLTE, ok: bool) { + if c.header.type != .PLTE { + return {}, false; + } + + i := 0; j := 0; ok = true; + for j < int(c.header.length) { + res.entries[i] = {c.data[j], c.data[j+1], c.data[j+2]}; + i += 1; j += 3; + } + res.used = u16(i); + return; +} + +splt :: proc(c: Chunk) -> (res: sPLT, ok: bool) { + if c.header.type != .sPLT { + return {}, false; + } + ok = true; + + fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=2, allocator=context.temp_allocator); + if len(fields) != 2 { + return {}, false; + } + + res.depth = fields[1][0]; + if res.depth != 8 && res.depth != 16 { + return {}, false; + } + + data := fields[1][1:]; + count: int; + + if res.depth == 8 { + if len(data) % 6 != 0 { + return {}, false; + } + count = len(data) / 6; + if count > 256 { + return {}, false; + } + + res.entries = mem.slice_data_cast([][4]u8, data); + } else { // res.depth == 16 + if len(data) % 10 != 0 { + return {}, false; + } + count = len(data) / 10; + if count > 256 { + return {}, false; + } + + res.entries = mem.slice_data_cast([][4]u16, data); + } + + res.name = strings.clone(string(fields[0])); + res.used = u16(count); + + return; +} + +splt_destroy :: proc(s: sPLT) { + delete(s.name); +} + +sbit :: proc(c: Chunk) -> (res: [4]u8, ok: bool) { + /* + Returns [4]u8 with the significant bits in each channel. + A channel will contain zero if not applicable to the PNG color type. + */ + + if len(c.data) < 1 || len(c.data) > 4 { + ok = false; return; + } + ok = true; + + for i := 0; i < len(c.data); i += 1 { + res[i] = c.data[i]; + } + return; + +} + +hist :: proc(c: Chunk) -> (res: hIST, ok: bool) { + if c.header.type != .hIST { + return {}, false; + } + if c.header.length & 1 == 1 || c.header.length > 512 { + // The entries are u16be, so the length must be even. + // At most 256 entries must be present + return {}, false; + } + + ok = true; + data := mem.slice_data_cast([]u16be, c.data); + i := 0; + for len(data) > 0 { + // HIST entries are u16be, we unpack them to machine format + res.entries[i] = u16(data[0]); + i += 1; data = data[1:]; + } + res.used = u16(i); + return; +} + +chrm :: proc(c: Chunk) -> (res: cHRM, ok: bool) { + ok = true; + if c.header.length != size_of(cHRM_Raw) { + return {}, false; + } + chrm := (^cHRM_Raw)(raw_data(c.data))^; + + res.w.x = f32(chrm.w.x) / 100_000.0; + res.w.y = f32(chrm.w.y) / 100_000.0; + res.r.x = f32(chrm.r.x) / 100_000.0; + res.r.y = f32(chrm.r.y) / 100_000.0; + res.g.x = f32(chrm.g.x) / 100_000.0; + res.g.y = f32(chrm.g.y) / 100_000.0; + res.b.x = f32(chrm.b.x) / 100_000.0; + res.b.y = f32(chrm.b.y) / 100_000.0; + return; +} + +exif :: proc(c: Chunk) -> (res: Exif, ok: bool) { + + ok = true; + + if len(c.data) < 4 { + ok = false; return; + } + + if c.data[0] == 'M' && c.data[1] == 'M' { + res.byte_order = .big_endian; + if c.data[2] != 0 || c.data[3] != 42 { + ok = false; return; + } + } else if c.data[0] == 'I' && c.data[1] == 'I' { + res.byte_order = .little_endian; + if c.data[2] != 42 || c.data[3] != 0 { + ok = false; return; + } + } else { + ok = false; return; + } + + res.data = c.data; + return; +} + +/* + General helper functions +*/ + +compute_buffer_size :: image.compute_buffer_size; + +/* + PNG save helpers +*/ + +when false { + + make_chunk :: proc(c: any, t: Chunk_Type) -> (res: Chunk) { + + data: []u8; + if v, ok := c.([]u8); ok { + data = v; + } else { + data = mem.any_to_bytes(c); + } + + res.header.length = u32be(len(data)); + res.header.type = t; + res.data = data; + + // CRC the type + crc := hash.crc32(mem.any_to_bytes(res.header.type)); + // Extend the CRC with the data + res.crc = u32be(hash.crc32(data, crc)); + return; + } + + write_chunk :: proc(fd: os.Handle, chunk: Chunk) { + c := chunk; + // Write length + type + os.write_ptr(fd, &c.header, 8); + // Write data + os.write_ptr(fd, mem.raw_data(c.data), int(c.header.length)); + // Write CRC32 + os.write_ptr(fd, &c.crc, 4); + } + + write_image_as_png :: proc(filename: string, image: Image) -> (err: Error) { + profiler.timed_proc(); + using image; + using os; + flags: int = O_WRONLY|O_CREATE|O_TRUNC; + + if len(image.pixels) == 0 || len(image.pixels) < image.width * image.height * int(image.channels) { + return E_PNG.Invalid_Image_Dimensions; + } + + 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, fderr := open(filename, flags, mode); + if fderr != 0 { + return E_General.Cannot_Open_File; + } + defer close(fd); + + magic := Signature; + + write_ptr(fd, &magic, 8); + + ihdr := IHDR{ + width = u32be(width), + height = u32be(height), + bit_depth = depth, + compression_method = 0, + filter_method = 0, + interlace_method = .None, + }; + + if channels == 1 { + ihdr.color_type = Color_Type{}; + } else if channels == 2 { + ihdr.color_type = Color_Type{.Alpha}; + } else if channels == 3 { + ihdr.color_type = Color_Type{.Color}; + } else if channels == 4 { + ihdr.color_type = Color_Type{.Color, .Alpha}; + } else { + // Unhandled + return E_PNG.Unknown_Color_Type; + } + + h := make_chunk(ihdr, .IHDR); + write_chunk(fd, h); + + bytes_needed := width * height * int(channels) + height; + filter_bytes := mem.make_dynamic_array_len_cap([dynamic]u8, bytes_needed, bytes_needed, context.allocator); + defer delete(filter_bytes); + + i := 0; j := 0; + // Add a filter byte 0 per pixel row + for y := 0; y < height; y += 1 { + filter_bytes[j] = 0; j += 1; + for x := 0; x < width; x += 1 { + for z := 0; z < channels; z += 1 { + filter_bytes[j+z] = image.pixels[i+z]; + } + i += channels; j += channels; + } + } + assert(j == bytes_needed); + + a: []u8 = filter_bytes[:]; + + out_buf: ^[dynamic]u8; + defer free(out_buf); + + ctx := zlib.ZLIB_Context{ + in_buf = &a, + out_buf = out_buf, + }; + err = zlib.write_zlib_stream_from_memory(&ctx); + + b: []u8; + if is_kind(err, E_General, E_General.OK) { + b = ctx.out_buf[:]; + } else { + return err; + } + + idat := make_chunk(b, .IDAT); + + write_chunk(fd, idat); + + iend := make_chunk([]u8{}, .IEND); + write_chunk(fd, iend); + + return E_General.OK; + } +} \ No newline at end of file diff --git a/core/image/png/png.odin b/core/image/png/png.odin new file mode 100644 index 000000000..a25fed1ec --- /dev/null +++ b/core/image/png/png.odin @@ -0,0 +1,1590 @@ +package png + +import "core:compress" +import "core:compress/zlib" +import "core:image" + +import "core:os" +import "core:strings" +import "core:hash" +import "core:bytes" +import "core:io" +import "core:mem" +import "core:intrinsics" + +Error :: compress.Error; +E_General :: compress.General_Error; +E_PNG :: image.PNG_Error; +E_Deflate :: compress.Deflate_Error; +is_kind :: compress.is_kind; + +Image :: image.Image; +Options :: image.Options; + +Signature :: enum u64be { + // 0x89504e470d0a1a0a + PNG = 0x89 << 56 | 'P' << 48 | 'N' << 40 | 'G' << 32 | '\r' << 24 | '\n' << 16 | 0x1a << 8 | '\n', +} + +Info :: struct { + header: IHDR, + chunks: [dynamic]Chunk, +} + +Chunk_Header :: struct #packed { + length: u32be, + type: Chunk_Type, +} + +Chunk :: struct #packed { + header: Chunk_Header, + data: []byte, + crc: u32be, +} + +Chunk_Type :: enum u32be { + // IHDR must come first in a file + IHDR = 'I' << 24 | 'H' << 16 | 'D' << 8 | 'R', + // PLTE must precede the first IDAT chunk + PLTE = 'P' << 24 | 'L' << 16 | 'T' << 8 | 'E', + bKGD = 'b' << 24 | 'K' << 16 | 'G' << 8 | 'D', + tRNS = 't' << 24 | 'R' << 16 | 'N' << 8 | 'S', + IDAT = 'I' << 24 | 'D' << 16 | 'A' << 8 | 'T', + + iTXt = 'i' << 24 | 'T' << 16 | 'X' << 8 | 't', + tEXt = 't' << 24 | 'E' << 16 | 'X' << 8 | 't', + zTXt = 'z' << 24 | 'T' << 16 | 'X' << 8 | 't', + + iCCP = 'i' << 24 | 'C' << 16 | 'C' << 8 | 'P', + pHYs = 'p' << 24 | 'H' << 16 | 'Y' << 8 | 's', + gAMA = 'g' << 24 | 'A' << 16 | 'M' << 8 | 'A', + tIME = 't' << 24 | 'I' << 16 | 'M' << 8 | 'E', + + sPLT = 's' << 24 | 'P' << 16 | 'L' << 8 | 'T', + sRGB = 's' << 24 | 'R' << 16 | 'G' << 8 | 'B', + hIST = 'h' << 24 | 'I' << 16 | 'S' << 8 | 'T', + cHRM = 'c' << 24 | 'H' << 16 | 'R' << 8 | 'M', + sBIT = 's' << 24 | 'B' << 16 | 'I' << 8 | 'T', + + /* + eXIf tags are not part of the core spec, but have been ratified + in v1.5.0 of the PNG Ext register. + + We will provide unprocessed chunks to the caller if `.return_metadata` is set. + Applications are free to implement an Exif decoder. + */ + eXIf = 'e' << 24 | 'X' << 16 | 'I' << 8 | 'f', + + // PNG files must end with IEND + IEND = 'I' << 24 | 'E' << 16 | 'N' << 8 | 'D', + + /* + XCode sometimes produces "PNG" files that don't adhere to the PNG spec. + We recognize them only in order to avoid doing further work on them. + + Some tools like PNG Defry may be able to repair them, but we're not + going to reward Apple for producing proprietary broken files purporting + to be PNGs by supporting them. + + */ + iDOT = 'i' << 24 | 'D' << 16 | 'O' << 8 | 'T', + CbGI = 'C' << 24 | 'b' << 16 | 'H' << 8 | 'I', +} + +IHDR :: struct #packed { + width: u32be, + height: u32be, + bit_depth: u8, + color_type: Color_Type, + compression_method: u8, + filter_method: u8, + interlace_method: Interlace_Method, +} +IHDR_SIZE :: size_of(IHDR); +#assert (IHDR_SIZE == 13); + +Color_Value :: enum u8 { + Paletted = 0, // 1 << 0 = 1 + Color = 1, // 1 << 1 = 2 + Alpha = 2, // 1 << 2 = 4 +} +Color_Type :: distinct bit_set[Color_Value; u8]; + +Interlace_Method :: enum u8 { + None = 0, + Adam7 = 1, +} + +Row_Filter :: enum u8 { + None = 0, + Sub = 1, + Up = 2, + Average = 3, + Paeth = 4, +}; + +PLTE_Entry :: [3]u8; + +PLTE :: struct #packed { + entries: [256]PLTE_Entry, + used: u16, +} + +hIST :: struct #packed { + entries: [256]u16, + used: u16, +} + +sPLT :: struct #packed { + name: string, + depth: u8, + entries: union { + [][4]u8, + [][4]u16, + }, + used: u16, +} + +// Other chunks +tIME :: struct #packed { + year: u16be, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, +}; +#assert(size_of(tIME) == 7); + +CIE_1931_Raw :: struct #packed { + x: u32be, + y: u32be, +} + +CIE_1931 :: struct #packed { + x: f32, + y: f32, +} + +cHRM_Raw :: struct #packed { + w: CIE_1931_Raw, + r: CIE_1931_Raw, + g: CIE_1931_Raw, + b: CIE_1931_Raw, +} +#assert(size_of(cHRM_Raw) == 32); + +cHRM :: struct #packed { + w: CIE_1931, + r: CIE_1931, + g: CIE_1931, + b: CIE_1931, +} +#assert(size_of(cHRM) == 32); + +gAMA :: struct { + gamma_100k: u32be, // Gamma * 100k +}; +#assert(size_of(gAMA) == 4); + +pHYs :: struct #packed { + ppu_x: u32be, + ppu_y: u32be, + unit: pHYs_Unit, +}; +#assert(size_of(pHYs) == 9); + +pHYs_Unit :: enum u8 { + Unknown = 0, + Meter = 1, +}; + +Text :: struct { + keyword: string, + keyword_localized: string, + language: string, + text: string, +}; + +Exif :: struct { + byte_order: enum { + little_endian, + big_endian, + }, + data: []u8, +} + +iCCP :: struct { + name: string, + profile: []u8, +} + +sRGB_Rendering_Intent :: enum u8 { + Perceptual = 0, + Relative_colorimetric = 1, + Saturation = 2, + Absolute_colorimetric = 3, +} + +sRGB :: struct #packed { + intent: sRGB_Rendering_Intent, +} + +ADAM7_X_ORIG := []int{ 0,4,0,2,0,1,0 }; +ADAM7_Y_ORIG := []int{ 0,0,4,0,2,0,1 }; +ADAM7_X_SPACING := []int{ 8,8,4,4,2,2,1 }; +ADAM7_Y_SPACING := []int{ 8,8,8,4,4,2,2 }; + +// Implementation starts here + +read_chunk :: proc(ctx: ^compress.Context) -> (Chunk, Error) { + + chunk := Chunk{}; + + ch, e := compress.read_data(ctx, Chunk_Header); + if e != .None { + return {}, E_General.Stream_Too_Short; + } + chunk.header = ch; + + data := make([]u8, ch.length, context.temp_allocator); + _, e2 := ctx.input->impl_read(data); + if e2 != .None { + return {}, E_General.Stream_Too_Short; + } + chunk.data = data; + + // Compute CRC over chunk type + data + type := (^[4]byte)(&ch.type)^; + computed_crc := hash.crc32(type[:]); + computed_crc = hash.crc32(data, computed_crc); + + crc, e3 := compress.read_data(ctx, u32be); + if e3 != .None { + return {}, E_General.Stream_Too_Short; + } + chunk.crc = crc; + + if chunk.crc != u32be(computed_crc) { + return {}, E_General.Checksum_Failed; + } + return chunk, E_General.OK; +} + +read_header :: proc(ctx: ^compress.Context) -> (IHDR, Error) { + + c, e := read_chunk(ctx); + if !is_kind(e, E_General.OK) { + return {}, e; + } + + header := (^IHDR)(raw_data(c.data))^; + // Validate IHDR + using header; + if width == 0 || height == 0 { + return {}, E_PNG.Invalid_Image_Dimensions; + } + + if compression_method != 0 { + return {}, E_General.Unknown_Compression_Method; + } + + if filter_method != 0 { + return {}, E_PNG.Unknown_Filter_Method; + } + + if interlace_method != .None && interlace_method != .Adam7 { + return {}, E_PNG.Unknown_Interlace_Method; + + } + + switch (transmute(u8)color_type) { + case 0: + /* + Grayscale. + Allowed bit depths: 1, 2, 4, 8 and 16. + */ + allowed := false; + for i in ([]u8{1, 2, 4, 8, 16}) { + if bit_depth == i { + allowed = true; + break; + } + } + if !allowed { + return {}, E_PNG.Invalid_Color_Bit_Depth_Combo; + } + case 2, 4, 6: + /* + RGB, Grayscale+Alpha, RGBA. + Allowed bit depths: 8 and 16 + */ + if bit_depth != 8 && bit_depth != 16 { + return {}, E_PNG.Invalid_Color_Bit_Depth_Combo; + } + case 3: + /* + Paletted. PLTE chunk must appear. + Allowed bit depths: 1, 2, 4 and 8. + */ + allowed := false; + for i in ([]u8{1, 2, 4, 8}) { + if bit_depth == i { + allowed = true; + break; + } + } + if !allowed { + return {}, E_PNG.Invalid_Color_Bit_Depth_Combo; + } + + case: + return {}, E_PNG.Unknown_Color_Type; + } + + return header, E_General.OK; +} + +chunk_type_to_name :: proc(type: ^Chunk_Type) -> string { + t := transmute(^u8)type; + return strings.string_from_ptr(t, 4); +} + +load_from_slice :: proc(slice: ^[]u8, options: Options = {}, allocator := context.allocator) -> (img: ^Image, err: Error) { + r := bytes.Reader{}; + bytes.reader_init(&r, slice^); + stream := bytes.reader_to_stream(&r); + + /* + TODO: Add a flag to tell the PNG loader that the stream is backed by a slice. + This way the stream reader could avoid the copy into the temp memory returned by it, + and instead return a slice into the original memory that's already owned by the caller. + */ + img, err = load_from_stream(&stream, options, allocator); + + return img, err; +} + +load_from_file :: proc(filename: string, options: Options = {}, allocator := context.allocator) -> (img: ^Image, err: Error) { + load_file :: proc(filename: string) -> (res: []u8, ok: bool) { + return os.read_entire_file(filename, context.temp_allocator); + } + + data, ok := load_file(filename); + if ok { + img, err = load_from_slice(&data, options, allocator); + return; + } else { + img = new(Image); + return img, E_General.File_Not_Found; + } +} + +load_from_stream :: proc(stream: ^io.Stream, options: Options = {}, allocator := context.allocator) -> (img: ^Image, err: Error) { + options := options; + if .info in options { + options |= {.return_metadata, .do_not_decompress_image}; + options ~= {.info}; + } + + if .alpha_drop_if_present in options && .alpha_add_if_missing in options { + return {}, E_General.Incompatible_Options; + } + + if img == nil { + img = new(Image); + } + + img.sidecar = nil; + + ctx := compress.Context{ + input = stream^, + }; + + signature, io_error := compress.read_data(&ctx, Signature); + if io_error != .None || signature != .PNG { + return img, E_PNG.Invalid_PNG_Signature; + } + + idat: []u8; + idat_b: bytes.Buffer; + idat_length := u32be(0); + defer bytes.buffer_destroy(&idat_b); + + c: Chunk; + ch: Chunk_Header; + e: io.Error; + + header: IHDR; + info: Info; + info.chunks.allocator = context.temp_allocator; + + // State to ensure correct chunk ordering. + seen_ihdr := false; first := true; + seen_plte := false; + seen_bkgd := false; + seen_trns := false; + seen_idat := false; + seen_iend := false; + + _plte := PLTE{}; + trns := Chunk{}; + + final_image_channels := 0; + + read_error: io.Error; + // 12 bytes is the size of a chunk with a zero-length payload. + for (read_error == .None && !seen_iend) { + // Peek at next chunk's length and type. + // TODO: Some streams may not provide seek/read_at + + ch, e = compress.peek_data(&ctx, Chunk_Header); + if e != .None { + return img, E_General.Stream_Too_Short; + } + // name := chunk_type_to_name(&ch.type); // Only used for debug prints during development. + + #partial switch(ch.type) { + case .IHDR: + if seen_ihdr || !first { + return {}, E_PNG.IHDR_Not_First_Chunk; + } + seen_ihdr = true; + + header, err = read_header(&ctx); + if !is_kind(err, E_General.OK) { + return img, err; + } + + if .Paletted in header.color_type { + // Color type 3 + img.channels = 1; + final_image_channels = 3; + img.depth = 8; + } else if .Color in header.color_type { + // Color image without a palette + img.channels = 3; + final_image_channels = 3; + img.depth = header.bit_depth; + } else { + // Grayscale + img.channels = 1; + final_image_channels = 1; + img.depth = header.bit_depth; + } + + if .Alpha in header.color_type { + img.channels += 1; + final_image_channels += 1; + } + + if img.channels == 0 || img.depth == 0 { + return {}, E_PNG.IHDR_Corrupt; + } + + img.width = int(header.width); + img.height = int(header.height); + + using header; + h := IHDR{ + width = width, + height = height, + bit_depth = bit_depth, + color_type = color_type, + compression_method = compression_method, + filter_method = filter_method, + interlace_method = interlace_method, + }; + info.header = h; + case .PLTE: + seen_plte = true; + // PLTE must appear before IDAT and can't appear for color types 0, 4. + ct := transmute(u8)info.header.color_type; + if seen_idat || ct == 0 || ct == 4 { + return img, E_PNG.PLTE_Encountered_Unexpectedly; + } + + c, err = read_chunk(&ctx); + if !is_kind(err, E_General.OK) { + return img, err; + } + + if c.header.length % 3 != 0 || c.header.length > 768 { + return img, E_PNG.PLTE_Invalid_Length; + } + plte_ok: bool; + _plte, plte_ok = plte(c); + if !plte_ok { + return img, E_PNG.PLTE_Invalid_Length; + } + + if .return_metadata in options { + append(&info.chunks, c); + } + case .IDAT: + // If we only want image metadata and don't want the pixel data, we can early out. + if .return_metadata not_in options && .do_not_decompress_image in options { + img.channels = final_image_channels; + img.sidecar = info; + return img, E_General.OK; + } + // There must be at least 1 IDAT, contiguous if more. + if seen_idat { + return img, E_PNG.IDAT_Must_Be_Contiguous; + } + + if idat_length > 0 { + return img, E_PNG.IDAT_Must_Be_Contiguous; + } + + next := ch.type; + for next == .IDAT { + c, err = read_chunk(&ctx); + if !is_kind(err, E_General.OK) { + return img, err; + } + + bytes.buffer_write(&idat_b, c.data); + idat_length += c.header.length; + + ch, e = compress.peek_data(&ctx, Chunk_Header); + if e != .None { + return img, E_General.Stream_Too_Short; + } + next = ch.type; + } + idat = bytes.buffer_to_bytes(&idat_b); + if int(idat_length) != len(idat) { + return {}, E_PNG.IDAT_Corrupt; + } + seen_idat = true; + case .IEND: + c, err = read_chunk(&ctx); + if !is_kind(err, E_General.OK) { + return img, err; + } + seen_iend = true; + case .bKGD: + + // TODO: Make sure that 16-bit bKGD + tRNS chunks return u16 instead of u16be + + c, err = read_chunk(&ctx); + if !is_kind(err, E_General.OK) { + return img, err; + } + seen_bkgd = true; + if .return_metadata in options { + append(&info.chunks, c); + } + + ct := transmute(u8)info.header.color_type; + switch(ct) { + case 3: // Indexed color + if c.header.length != 1 { + return {}, E_PNG.BKGD_Invalid_Length; + } + col := _plte.entries[c.data[0]]; + img.background = [3]u16{ + u16(col[0]) << 8 | u16(col[0]), + u16(col[1]) << 8 | u16(col[1]), + u16(col[2]) << 8 | u16(col[2]), + }; + case 0, 4: // Grayscale, with and without Alpha + if c.header.length != 2 { + return {}, E_PNG.BKGD_Invalid_Length; + } + col := u16(mem.slice_data_cast([]u16be, c.data[:])[0]); + img.background = [3]u16{col, col, col}; + case 2, 6: // Color, with and without Alpha + if c.header.length != 6 { + return {}, E_PNG.BKGD_Invalid_Length; + } + col := mem.slice_data_cast([]u16be, c.data[:]); + img.background = [3]u16{u16(col[0]), u16(col[1]), u16(col[2])}; + } + case .tRNS: + c, err = read_chunk(&ctx); + if !is_kind(err, E_General.OK) { + return img, err; + } + + if .Alpha in info.header.color_type { + return img, E_PNG.TRNS_Encountered_Unexpectedly; + } + + if .return_metadata in options { + append(&info.chunks, c); + } + + /* + This makes the image one with transparency, so set it to +1 here, + even if we need we leave img.channels alone for the defilterer's + sake. If we early because the user just cares about metadata, + we'll set it to 'final_image_channels'. + */ + + final_image_channels += 1; + + seen_trns = true; + if info.header.bit_depth < 8 && .Paletted not_in info.header.color_type { + // Rescale tRNS data so key matches intensity + dsc := depth_scale_table; + scale := dsc[info.header.bit_depth]; + if scale != 1 { + key := mem.slice_data_cast([]u16be, c.data)[0] * u16be(scale); + c.data = []u8{0, u8(key & 255)}; + } + } + trns = c; + case .iDOT, .CbGI: + /* + iPhone PNG bastardization that doesn't adhere to spec with broken IDAT chunk. + We're not going to add support for it. If you have the misfortunte of coming + across one of these files, use a utility to defry it.s + */ + return img, E_PNG.PNG_Does_Not_Adhere_to_Spec; + case: + // Unhandled type + c, err = read_chunk(&ctx); + if !is_kind(err, E_General.OK) { + return img, err; + } + if .return_metadata in options { + // NOTE: Chunk cata is currently allocated on the temp allocator. + append(&info.chunks, c); + } + + first = false; + } + } + + if .return_header in options || .return_metadata in options { + img.sidecar = info; + } + if .do_not_decompress_image in options { + img.channels = final_image_channels; + return img, E_General.OK; + } + + if !seen_idat { + return img, E_PNG.IDAT_Missing; + } + + buf: bytes.Buffer; + zlib_error := zlib.inflate(&idat, &buf); + defer bytes.buffer_destroy(&buf); + + if !is_kind(zlib_error, E_General.OK) { + return {}, zlib_error; + } else { + /* + Let's calcalate the expected size of the IDAT based on its dimensions, + and whether or not it's interlaced + */ + expected_size: int; + buf_len := len(buf.buf); + + if header.interlace_method != .Adam7 { + expected_size = compute_buffer_size(int(header.width), int(header.height), int(img.channels), int(header.bit_depth), 1); + } else { + /* + Because Adam7 divides the image up into sub-images, and each scanline must start + with a filter byte, Adam7 interlaced images can have a larger raw size. + */ + for p := 0; p < 7; p += 1 { + x := (int(header.width) - ADAM7_X_ORIG[p] + ADAM7_X_SPACING[p] - 1) / ADAM7_X_SPACING[p]; + y := (int(header.height) - ADAM7_Y_ORIG[p] + ADAM7_Y_SPACING[p] - 1) / ADAM7_Y_SPACING[p]; + if (x > 0 && y > 0) { + expected_size += compute_buffer_size(int(x), int(y), int(img.channels), int(header.bit_depth), 1); + } + } + } + + if expected_size != buf_len { + return {}, E_PNG.IDAT_Corrupt; + } + } + + /* + Defilter just cares about the raw number of image channels present. + So, we'll save the old value of img.channels we return to the user + as metadata, and set it instead to the raw number of channels. + */ + defilter_error := defilter(img, &buf, &header, options); + if !is_kind(defilter_error, E_General.OK) { + bytes.buffer_destroy(&img.pixels); + return {}, defilter_error; + } + + /* + Now we'll handle the relocoring of paletted images, handling of tRNS chunks, + and we'll expand grayscale images to RGB(A). + + For the sake of convenience we return only RGB(A) images. In the future we + may supply an option to return Gray/Gray+Alpha as-is, in which case RGB(A) + will become the default. + */ + + raw_image_channels := img.channels; + out_image_channels := 3; + + /* + To give ourselves less options to test, we'll knock out + `.blend_background` and `seen_bkgd` if we haven't seen both. + */ + if !(seen_bkgd && .blend_background in options) { + options ~= {.blend_background}; + seen_bkgd = false; + } + + if seen_trns || .Alpha in info.header.color_type || .alpha_add_if_missing in options { + out_image_channels = 4; + } + + if .alpha_drop_if_present in options { + out_image_channels = 3; + } + + if seen_bkgd && .blend_background in options && .alpha_add_if_missing not_in options { + out_image_channels = 3; + } + + add_alpha := (seen_trns && .alpha_drop_if_present not_in options) || (.alpha_add_if_missing in options); + premultiply := .alpha_premultiply in options || .blend_background in options; + + img.channels = out_image_channels; + + if .Paletted in header.color_type { + temp := img.pixels; + defer bytes.buffer_destroy(&temp); + + // We need to create a new image buffer + dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8); + t := bytes.Buffer{}; + resize(&t.buf, dest_raw_size); + + i := 0; j := 0; + + // If we don't have transparency or drop it without applying it, we can do this: + if (!seen_trns || (seen_trns && .alpha_drop_if_present in options && .alpha_premultiply not_in options)) && .alpha_add_if_missing not_in options { + for h := 0; h < int(img.height); h += 1 { + for w := 0; w < int(img.width); w += 1 { + c := _plte.entries[temp.buf[i]]; + t.buf[j ] = c.r; + t.buf[j+1] = c.g; + t.buf[j+2] = c.b; + i += 1; j += 3; + } + } + } else if add_alpha || .alpha_drop_if_present in options { + bg := [3]f32{0, 0, 0}; + if premultiply && seen_bkgd { + c16 := img.background.([3]u16); + bg = [3]f32{f32(c16.r), f32(c16.g), f32(c16.b)}; + } + + no_alpha := (.alpha_drop_if_present in options || premultiply) && .alpha_add_if_missing not_in options; + blend_background := seen_bkgd && .blend_background in options; + + for h := 0; h < int(img.height); h += 1 { + for w := 0; w < int(img.width); w += 1 { + index := temp.buf[i]; + + c := _plte.entries[index]; + a := int(index) < len(trns.data) ? trns.data[index] : 255; + alpha := f32(a) / 255.0; + + if blend_background { + c.r = u8((1.0 - alpha) * bg[0] + f32(c.r) * alpha); + c.g = u8((1.0 - alpha) * bg[1] + f32(c.g) * alpha); + c.b = u8((1.0 - alpha) * bg[2] + f32(c.b) * alpha); + a = 255; + } else if premultiply { + c.r = u8(f32(c.r) * alpha); + c.g = u8(f32(c.g) * alpha); + c.b = u8(f32(c.b) * alpha); + } + + t.buf[j ] = c.r; + t.buf[j+1] = c.g; + t.buf[j+2] = c.b; + i += 1; + + if no_alpha { + j += 3; + } else { + t.buf[j+3] = u8(a); + j += 4; + } + } + } + } else { + // This should be impossible. + assert(false); + } + + img.pixels = t; + + } else if img.depth == 16 { + // Check if we need to do something. + if raw_image_channels == out_image_channels { + // If we have 3 in and 3 out, or 4 in and 4 out without premultiplication... + if raw_image_channels == 4 && .alpha_premultiply not_in options && !seen_bkgd { + // Then we're done. + return img, E_General.OK; + } + } + + temp := img.pixels; + defer bytes.buffer_destroy(&temp); + + // We need to create a new image buffer + dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 16); + t := bytes.Buffer{}; + resize(&t.buf, dest_raw_size); + + p16 := mem.slice_data_cast([]u16, temp.buf[:]); + o16 := mem.slice_data_cast([]u16, t.buf[:]); + + switch (raw_image_channels) { + case 1: + // Gray without Alpha. Might have tRNS alpha. + key := u16(0); + if seen_trns { + key = mem.slice_data_cast([]u16, trns.data)[0]; + } + + for len(p16) > 0 { + r := p16[0]; + + alpha := u16(1); // Default to full opaque + + if seen_trns { + if r == key { + if seen_bkgd { + c := img.background.([3]u16); + r = c[0]; + } else { + alpha = 0; // Keyed transparency + } + } + } + + if premultiply { + o16[0] = r * alpha; + o16[1] = r * alpha; + o16[2] = r * alpha; + } else { + o16[0] = r; + o16[1] = r; + o16[2] = r; + } + + if out_image_channels == 4 { + o16[3] = alpha * 65535; + } + + p16 = p16[1:]; + o16 = o16[out_image_channels:]; + } + case 2: + // Gray with alpha, we shouldn't have a tRNS chunk. + for len(p16) > 0 { + r := p16[0]; + if premultiply { + alpha := p16[1]; + c := u16(f32(r) * f32(alpha) / f32(65535)); + o16[0] = c; + o16[1] = c; + o16[2] = c; + } else { + o16[0] = r; + o16[1] = r; + o16[2] = r; + } + + if .alpha_drop_if_present not_in options { + o16[3] = p16[1]; + } + + p16 = p16[2:]; + o16 = o16[out_image_channels:]; + } + case 3: + /* + Color without Alpha. + We may still have a tRNS chunk or `.alpha_add_if_missing`. + */ + + key: []u16; + if seen_trns { + key = mem.slice_data_cast([]u16, trns.data); + } + + for len(p16) > 0 { + r := p16[0]; + g := p16[1]; + b := p16[2]; + + alpha := u16(1); // Default to full opaque + + if seen_trns { + if r == key[0] && g == key[1] && b == key[2] { + if seen_bkgd { + c := img.background.([3]u16); + r = c[0]; + g = c[1]; + b = c[2]; + } else { + alpha = 0; // Keyed transparency + } + } + } + + if premultiply { + o16[0] = r * alpha; + o16[1] = g * alpha; + o16[2] = b * alpha; + } else { + o16[0] = r; + o16[1] = g; + o16[2] = b; + } + + if out_image_channels == 4 { + o16[3] = alpha * 65535; + } + + p16 = p16[3:]; + o16 = o16[out_image_channels:]; + } + case 4: + // Color with Alpha, can't have tRNS. + for len(p16) > 0 { + r := p16[0]; + g := p16[1]; + b := p16[2]; + a := p16[3]; + + if premultiply { + alpha := f32(a) / 65535.0; + o16[0] = u16(f32(r) * alpha); + o16[1] = u16(f32(g) * alpha); + o16[2] = u16(f32(b) * alpha); + } else { + o16[0] = r; + o16[1] = g; + o16[2] = b; + } + + if .alpha_drop_if_present not_in options { + o16[3] = a; + } + + p16 = p16[4:]; + o16 = o16[out_image_channels:]; + } + case: + unreachable("We should never seen # channels other than 1-4 inclusive."); + } + + img.pixels = t; + img.channels = out_image_channels; + + } else if img.depth == 8 { + // Check if we need to do something. + if raw_image_channels == out_image_channels { + // If we have 3 in and 3 out, or 4 in and 4 out without premultiplication... + if raw_image_channels == 4 && .alpha_premultiply not_in options { + // Then we're done. + return img, E_General.OK; + } + } + + temp := img.pixels; + defer bytes.buffer_destroy(&temp); + + // We need to create a new image buffer + dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8); + t := bytes.Buffer{}; + resize(&t.buf, dest_raw_size); + + p := mem.slice_data_cast([]u8, temp.buf[:]); + o := mem.slice_data_cast([]u8, t.buf[:]); + + switch (raw_image_channels) { + case 1: + // Gray without Alpha. Might have tRNS alpha. + key := u8(0); + if seen_trns { + key = u8(mem.slice_data_cast([]u16be, trns.data)[0]); + } + + for len(p) > 0 { + r := p[0]; + alpha := u8(1); + + if seen_trns { + if r == key { + if seen_bkgd { + c := img.background.([3]u16); + r = u8(c[0]); + } else { + alpha = 0; // Keyed transparency + } + } + if premultiply { + o[0] = r * alpha; + o[1] = r * alpha; + o[2] = r * alpha; + } + } else { + o[0] = r; + o[1] = r; + o[2] = r; + } + + if out_image_channels == 4 { + o[3] = alpha * 255; + } + + p = p[1:]; + o = o[out_image_channels:]; + } + case 2: + // Gray with alpha, we shouldn't have a tRNS chunk. + for len(p) > 0 { + r := p[0]; + if .alpha_premultiply in options { + alpha := p[1]; + c := u8(f32(r) * f32(alpha) / f32(255)); + o[0] = c; + o[1] = c; + o[2] = c; + } else { + o[0] = r; + o[1] = r; + o[2] = r; + } + + if .alpha_drop_if_present not_in options { + o[3] = p[1]; + } + + p = p[2:]; + o = o[out_image_channels:]; + } + case 3: + // Color without Alpha. We may still have a tRNS chunk + key: []u8; + if seen_trns { + /* + For 8-bit images, the tRNS chunk still contains a triple in u16be. + We use only the low byte in this case. + */ + key = []u8{trns.data[1], trns.data[3], trns.data[5]}; + } + for len(p) > 0 { + r := p[0]; + g := p[1]; + b := p[2]; + + alpha := u8(1); // Default to full opaque + + // TODO: Combine the seen_trns cases. + if seen_trns { + if r == key[0] && g == key[1] && b == key[2] { + if seen_bkgd { + c := img.background.([3]u16); + r = u8(c[0]); + g = u8(c[1]); + b = u8(c[2]); + } else { + alpha = 0; // Keyed transparency + } + } + + if .alpha_premultiply in options || .blend_background in options { + o[0] = r * alpha; + o[1] = g * alpha; + o[2] = b * alpha; + } + } else { + o[0] = r; + o[1] = g; + o[2] = b; + } + + if out_image_channels == 4 { + o[3] = alpha * 255; + } + + p = p[3:]; + o = o[out_image_channels:]; + } + case 4: + // Color with Alpha, can't have tRNS. + for len(p) > 0 { + r := p[0]; + g := p[1]; + b := p[2]; + a := p[3]; + + if .alpha_premultiply in options { + alpha := f32(a) / 255.0; + o[0] = u8(f32(r) * alpha); + o[1] = u8(f32(g) * alpha); + o[2] = u8(f32(b) * alpha); + } else { + o[0] = r; + o[1] = g; + o[2] = b; + } + + if .alpha_drop_if_present not_in options { + o[3] = a; + } + + p = p[4:]; + o = o[out_image_channels:]; + } + case: + unreachable("We should never seen # channels other than 1-4 inclusive."); + } + + img.pixels = t; + img.channels = out_image_channels; + + } else { + /* + This may change if we ever don't expand 1, 2 and 4 bit images. But, those raw + returns will likely bypass this processing pipeline. + */ + unreachable("We should never see bit depths other than 8, 16 and 'Paletted' here."); + } + + return img, E_General.OK; +} + + +filter_paeth :: #force_inline proc(left, up, up_left: u8) -> u8 { + aa, bb, cc := i16(left), i16(up), i16(up_left); + p := aa + bb - cc; + pa := abs(p - aa); + pb := abs(p - bb); + pc := abs(p - cc); + if pa <= pb && pa <= pc { + return left; + } + if pb <= pc { + return up; + } + return up_left; +} + +Filter_Params :: struct #packed { + src : []u8, + dest : []u8, + width : int, + height : int, + depth : int, + channels: int, + rescale : bool, +} + +depth_scale_table :: []u8{0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01}; + +// @(optimization_mode="speed") +defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) { + + using params; + row_stride := channels * width; + + // TODO: See about doing a Duff's #unroll where practicable + + // Apron so we don't need to special case first rows. + up := make([]u8, row_stride, context.temp_allocator); + ok = true; + + for _ in 0..> 1; + dest[i] = (src[i] + avg) & 255; + } + for k := 0; k < nk; k += 1 { + avg := u8((u16(up[channels+k]) + u16(dest[k])) >> 1); + dest[channels+k] = (src[channels+k] + avg) & 255; + } + case .Paeth: + for i := 0; i < channels; i += 1 { + paeth := filter_paeth(0, up[i], 0); + dest[i] = (src[i] + paeth) & 255; + } + for k := 0; k < nk; k += 1 { + paeth := filter_paeth(dest[k], up[channels+k], up[k]); + dest[channels+k] = (src[channels+k] + paeth) & 255; + } + case: + return false; + } + + src = src[row_stride:]; + up = dest; + dest = dest[row_stride:]; + } + return; +} + +// @(optimization_mode="speed") +defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_check { + + using params; + ok = true; + + row_stride_in := ((channels * width * depth) + 7) >> 3; + row_stride_out := channels * width; + + // Store defiltered bytes rightmost so we can widen in-place. + row_offset := row_stride_out - row_stride_in; + // Save original dest because we'll need it for the bit widening. + orig_dest := dest; + + // TODO: See about doing a Duff's #unroll where practicable + + // Apron so we don't need to special case first rows. + up := make([]u8, row_stride_out, context.temp_allocator); + + #no_bounds_check for _ in 0..> 1; + dest[i] = (src[i] + avg) & 255; + } + for k in 0..nk { + avg := u8((u16(up[channels+k]) + u16(dest[k])) >> 1); + dest[channels+k] = (src[channels+k] + avg) & 255; + } + case .Paeth: + for i in 0..channels { + paeth := filter_paeth(0, up[i], 0); + dest[i] = (src[i] + paeth) & 255; + } + for k in 0..nk { + paeth := filter_paeth(dest[k], up[channels], up[k]); + dest[channels+k] = (src[channels+k] + paeth) & 255; + } + case: + return false; + } + + src = src [row_stride_in:]; + up = dest; + dest = dest[row_stride_in:]; + } + + // Let's expand the bits + dest = orig_dest; + + // Don't rescale the bits if we're a paletted image. + dsc := depth_scale_table; + scale := rescale ? dsc[depth] : 1; + + /* + For sBIT support we should probably set scale to 1 and mask the significant bits. + Seperately, do we want to support packed pixels? i.e defiltering only, no expansion? + If so, all we have to do is call defilter_8 for that case and not set img.depth to 8. + */ + + for j := 0; j < height; j += 1 { + src = dest[row_offset:]; + + if depth == 4 { + k := row_stride_out; + for ; k >= 2; k -= 2 { + c := src[0]; + dest[0] = scale * (c >> 4); + dest[1] = scale * (c & 15); + dest = dest[2:]; src = src[1:]; + } + if k > 0 { + c := src[0]; + dest[0] = scale * (c >> 4); + dest = dest[1:]; + } + } else if depth == 2 { + k := row_stride_out; + for ; k >= 4; k -= 4 { + c := src[0]; + dest[0] = scale * ((c >> 6) ); + dest[1] = scale * ((c >> 4) & 3); + dest[2] = scale * ((c >> 2) & 3); + dest[3] = scale * ((c ) & 3); + dest = dest[4:]; src = src[1:]; + } + if k > 0 { + c := src[0]; + dest[0] = scale * ((c >> 6) ); + if k > 1 { + dest[1] = scale * ((c >> 4) & 3); + } + if k > 2 { + dest[2] = scale * ((c >> 2) & 3); + } + dest = dest[k:]; + } + } else if depth == 1 { + k := row_stride_out; + for ; k >= 8; k -= 8 { + c := src[0]; + dest[0] = scale * ((c >> 7) ); + dest[1] = scale * ((c >> 6) & 1); + dest[2] = scale * ((c >> 5) & 1); + dest[3] = scale * ((c >> 4) & 1); + dest[4] = scale * ((c >> 3) & 1); + dest[5] = scale * ((c >> 2) & 1); + dest[6] = scale * ((c >> 1) & 1); + dest[7] = scale * ((c ) & 1); + dest = dest[8:]; src = src[1:]; + } + if k > 0 { + c := src[0]; + dest[0] = scale * ((c >> 7) ); + if k > 1 { + dest[1] = scale * ((c >> 6) & 1); + } + if k > 2 { + dest[2] = scale * ((c >> 5) & 1); + } + if k > 3 { + dest[3] = scale * ((c >> 4) & 1); + } + if k > 4 { + dest[4] = scale * ((c >> 3) & 1); + } + if k > 5 { + dest[5] = scale * ((c >> 2) & 1); + } + if k > 6 { + dest[6] = scale * ((c >> 1) & 1); + } + dest = dest[k:]; + + } + } + } + + return; +} + +// @(optimization_mode="speed") +defilter_16 :: proc(params: ^Filter_Params) -> (ok: bool) { + + using params; + ok = true; + + stride := channels * 2; + row_stride := width * stride; + + // TODO: See about doing a Duff's #unroll where practicable + // Apron so we don't need to special case first rows. + up := make([]u8, row_stride, context.temp_allocator); + + for y := 0; y < height; y += 1 { + nk := row_stride - stride; + + filter := Row_Filter(src[0]); src = src[1:]; + switch(filter) { + case .None: + copy(dest, src[:row_stride]); + case .Sub: + for i := 0; i < stride; i += 1 { + dest[i] = src[i]; + } + for k := 0; k < nk; k += 1 { + dest[stride+k] = (src[stride+k] + dest[k]) & 255; + } + case .Up: + for k := 0; k < row_stride; k += 1 { + dest[k] = (src[k] + up[k]) & 255; + } + case .Average: + for i := 0; i < stride; i += 1 { + avg := up[i] >> 1; + dest[i] = (src[i] + avg) & 255; + } + for k := 0; k < nk; k += 1 { + avg := u8((u16(up[stride+k]) + u16(dest[k])) >> 1); + dest[stride+k] = (src[stride+k] + avg) & 255; + } + case .Paeth: + for i := 0; i < stride; i += 1 { + paeth := filter_paeth(0, up[i], 0); + dest[i] = (src[i] + paeth) & 255; + } + for k := 0; k < nk; k += 1 { + paeth := filter_paeth(dest[k], up[stride+k], up[k]); + dest[stride+k] = (src[stride+k] + paeth) & 255; + } + case: + return false; + } + + src = src[row_stride:]; + up = dest; + dest = dest[row_stride:]; + } + + return; +} + +defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, options: Options) -> (err: compress.Error) { + input := bytes.buffer_to_bytes(filter_bytes); + width := int(header.width); + height := int(header.height); + channels := int(img.channels); + depth := int(header.bit_depth); + rescale := .Color not_in header.color_type; + + bytes_per_channel := depth == 16 ? 2 : 1; + + num_bytes := compute_buffer_size(width, height, channels, depth == 16 ? 16 : 8); + resize(&img.pixels.buf, num_bytes); + + filter_ok: bool; + + if header.interlace_method != .Adam7 { + params := Filter_Params{ + src = input, + width = width, + height = height, + channels = channels, + depth = depth, + rescale = rescale, + dest = img.pixels.buf[:], + }; + + if depth == 8 { + filter_ok = defilter_8(¶ms); + } else if depth < 8 { + filter_ok = defilter_less_than_8(¶ms); + img.depth = 8; + } else { + filter_ok = defilter_16(¶ms); + } + if !filter_ok { + // Caller will destroy buffer for us. + return E_PNG.Unknown_Filter_Method; + } + } else { + /* + For deinterlacing we need to make a temporary buffer, defiilter part of the image, + and copy that back into the actual output buffer. + */ + + for p := 0; p < 7; p += 1 { + i,j,x,y: int; + x = (width - ADAM7_X_ORIG[p] + ADAM7_X_SPACING[p] - 1) / ADAM7_X_SPACING[p]; + y = (height - ADAM7_Y_ORIG[p] + ADAM7_Y_SPACING[p] - 1) / ADAM7_Y_SPACING[p]; + if (x > 0 && y > 0) { + temp: bytes.Buffer; + temp_len := compute_buffer_size(x, y, channels, depth == 16 ? 16 : 8); + resize(&temp.buf, temp_len); + + params := Filter_Params{ + src = input, + width = x, + height = y, + channels = channels, + depth = depth, + rescale = rescale, + dest = temp.buf[:], + }; + + if depth == 8 { + filter_ok = defilter_8(¶ms); + } else if depth < 8 { + filter_ok = defilter_less_than_8(¶ms); + img.depth = 8; + } else { + filter_ok = defilter_16(¶ms); + } + + if !filter_ok { + // Caller will destroy buffer for us. + return E_PNG.Unknown_Filter_Method; + } + + t := temp.buf[:]; + for j = 0; j < y; j += 1 { + for i = 0; i < x; i += 1 { + out_y := j * ADAM7_Y_SPACING[p] + ADAM7_Y_ORIG[p]; + out_x := i * ADAM7_X_SPACING[p] + ADAM7_X_ORIG[p]; + + out_off := out_y * width * channels * bytes_per_channel; + out_off += out_x * channels * bytes_per_channel; + + for z := 0; z < channels * bytes_per_channel; z += 1 { + img.pixels.buf[out_off + z] = t[z]; + } + t = t[channels * bytes_per_channel:]; + } + } + bytes.buffer_destroy(&temp); + input_stride := compute_buffer_size(x, y, channels, depth, 1); + input = input[input_stride:]; + } + } + } + when ODIN_ENDIAN == "little" { + if img.depth == 16 { + // The pixel components are in Big Endian. Let's byteswap. + input := mem.slice_data_cast([]u16be, img.pixels.buf[:]); + output := mem.slice_data_cast([]u16 , img.pixels.buf[:]); + #no_bounds_check for v, i in input { + output[i] = u16(v); + } + } + } + + return E_General.OK; +} + +load :: proc{load_from_file, load_from_slice, load_from_stream}; \ No newline at end of file -- cgit v1.2.3 From 06f1eaa1538279a82b6a24ed7ee17c1dea1e1293 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 30 Apr 2021 09:35:43 +0200 Subject: Use regular allocator from png+gzip's `load_from_file`. I would've used `os.stream_from_handle`, but: - Parts of it seem to be implemented for Windows only at the moment. - PNG's `peek_data` using that stream didn't manage to rewind and thus tried to parse the data after the header as the header. Two things must happen: - The `os.stream_from_handle` implementation needs to be fixed. - PNG and GZIP's parsers need to be able to handle streams that can't rewind or seek (backward). Those fixes are on my TODO list but are exceed the scope of this patch. --- core/compress/gzip/gzip.odin | 4 +++- core/image/png/png.odin | 6 ++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'core') diff --git a/core/compress/gzip/gzip.odin b/core/compress/gzip/gzip.odin index 3278d7c1d..457b32bd3 100644 --- a/core/compress/gzip/gzip.odin +++ b/core/compress/gzip/gzip.odin @@ -108,7 +108,9 @@ load_from_slice :: proc(slice: ^[]u8, buf: ^bytes.Buffer, allocator := context.a } load_from_file :: proc(filename: string, buf: ^bytes.Buffer, allocator := context.allocator) -> (err: Error) { - data, ok := os.read_entire_file(filename, context.temp_allocator); + data, ok := os.read_entire_file(filename, allocator); + defer delete(data); + if ok { err = load_from_slice(&data, buf, allocator); return; diff --git a/core/image/png/png.odin b/core/image/png/png.odin index a25fed1ec..5ea9979a8 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -366,11 +366,9 @@ load_from_slice :: proc(slice: ^[]u8, options: Options = {}, allocator := contex } load_from_file :: proc(filename: string, options: Options = {}, allocator := context.allocator) -> (img: ^Image, err: Error) { - load_file :: proc(filename: string) -> (res: []u8, ok: bool) { - return os.read_entire_file(filename, context.temp_allocator); - } + data, ok := os.read_entire_file(filename, allocator); + defer delete(data); - data, ok := load_file(filename); if ok { img, err = load_from_slice(&data, options, allocator); return; -- cgit v1.2.3 From 5f617c56e17ce33ca9be5cfd051deb640fb8200b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 30 Apr 2021 10:58:29 +0100 Subject: Minor stylistic code changes to compress and image packages --- core/compress/common.odin | 2 +- core/compress/gzip/example.odin | 6 +- core/compress/gzip/gzip.odin | 78 +++-- core/compress/zlib/example.odin | 22 +- core/compress/zlib/zlib.odin | 133 ++++---- core/image/png/example.odin | 4 +- core/image/png/helpers.odin | 187 +++++------ core/image/png/png.odin | 723 ++++++++++++++++++++-------------------- 8 files changed, 575 insertions(+), 580 deletions(-) (limited to 'core') diff --git a/core/compress/common.odin b/core/compress/common.odin index 99b054903..11b5b74b3 100644 --- a/core/compress/common.odin +++ b/core/compress/common.odin @@ -200,4 +200,4 @@ read_bits_no_refill_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 { discard_to_next_byte_lsb :: proc(z: ^Context) { discard := u8(z.num_bits & 7); consume_bits_lsb(z, discard); -} \ No newline at end of file +} diff --git a/core/compress/gzip/example.odin b/core/compress/gzip/example.odin index fa0cba9db..8e0f53cf8 100644 --- a/core/compress/gzip/example.odin +++ b/core/compress/gzip/example.odin @@ -34,7 +34,7 @@ main :: proc() { if len(args) < 2 { stderr("No input file specified.\n"); - err := gzip.load(&TEST, &buf); + err := gzip.load(TEST, &buf); if gzip.is_kind(err, gzip.E_General.OK) { stdout("Displaying test vector: "); stdout(bytes.buffer_to_string(&buf)); @@ -50,7 +50,7 @@ main :: proc() { if file == "-" { // Read from stdin s := os.stream_from_handle(os.stdin); - err = gzip.load(&s, &buf); + err = gzip.load(s, &buf); } else { err = gzip.load(file, &buf); } @@ -67,4 +67,4 @@ main :: proc() { stdout(bytes.buffer_to_string(&buf)); } os.exit(0); -} \ No newline at end of file +} diff --git a/core/compress/gzip/gzip.odin b/core/compress/gzip/gzip.odin index 457b32bd3..f86d3ddce 100644 --- a/core/compress/gzip/gzip.odin +++ b/core/compress/gzip/gzip.odin @@ -45,39 +45,39 @@ Header_Flag :: enum u8 { Header_Flags :: distinct bit_set[Header_Flag; u8]; OS :: enum u8 { - FAT = 0, - Amiga = 1, - VMS = 2, - Unix = 3, - VM_CMS = 4, - Atari_TOS = 5, - HPFS = 6, - Macintosh = 7, - Z_System = 8, - CP_M = 9, - TOPS_20 = 10, - NTFS = 11, - QDOS = 12, + FAT = 0, + Amiga = 1, + VMS = 2, + Unix = 3, + VM_CMS = 4, + Atari_TOS = 5, + HPFS = 6, + Macintosh = 7, + Z_System = 8, + CP_M = 9, + TOPS_20 = 10, + NTFS = 11, + QDOS = 12, Acorn_RISCOS = 13, - _Unknown = 14, - Unknown = 255, + _Unknown = 14, + Unknown = 255, } OS_Name :: #partial [OS]string{ - .FAT = "FAT", - .Amiga = "Amiga", - .VMS = "VMS/OpenVMS", - .Unix = "Unix", - .VM_CMS = "VM/CMS", - .Atari_TOS = "Atari TOS", - .HPFS = "HPFS", - .Macintosh = "Macintosh", - .Z_System = "Z-System", - .CP_M = "CP/M", - .TOPS_20 = "TOPS-20", - .NTFS = "NTFS", - .QDOS = "QDOS", + .FAT = "FAT", + .Amiga = "Amiga", + .VMS = "VMS/OpenVMS", + .Unix = "Unix", + .VM_CMS = "VM/CMS", + .Atari_TOS = "Atari TOS", + .HPFS = "HPFS", + .Macintosh = "Macintosh", + .Z_System = "Z-System", + .CP_M = "CP/M", + .TOPS_20 = "TOPS-20", + .NTFS = "NTFS", + .QDOS = "QDOS", .Acorn_RISCOS = "Acorn RISCOS", - .Unknown = "Unknown", + .Unknown = "Unknown", }; Compression :: enum u8 { @@ -96,13 +96,13 @@ E_ZLIB :: compress.ZLIB_Error; E_Deflate :: compress.Deflate_Error; is_kind :: compress.is_kind; -load_from_slice :: proc(slice: ^[]u8, buf: ^bytes.Buffer, allocator := context.allocator) -> (err: Error) { +load_from_slice :: proc(slice: []u8, buf: ^bytes.Buffer, allocator := context.allocator) -> (err: Error) { r := bytes.Reader{}; - bytes.reader_init(&r, slice^); + bytes.reader_init(&r, slice); stream := bytes.reader_to_stream(&r); - err = load_from_stream(&stream, buf, allocator); + err = load_from_stream(stream, buf, allocator); return err; } @@ -111,18 +111,16 @@ load_from_file :: proc(filename: string, buf: ^bytes.Buffer, allocator := contex data, ok := os.read_entire_file(filename, allocator); defer delete(data); + err = E_General.File_Not_Found; if ok { - err = load_from_slice(&data, buf, allocator); - return; - } else { - return E_General.File_Not_Found; + err = load_from_slice(data, buf, allocator); } + return; } -load_from_stream :: proc(stream: ^io.Stream, buf: ^bytes.Buffer, allocator := context.allocator) -> (err: Error) { - +load_from_stream :: proc(stream: io.Stream, buf: ^bytes.Buffer, allocator := context.allocator) -> (err: Error) { ctx := compress.Context{ - input = stream^, + input = stream, }; buf := buf; ws := bytes.buffer_to_stream(buf); @@ -313,4 +311,4 @@ load_from_stream :: proc(stream: ^io.Stream, buf: ^bytes.Buffer, allocator := co return E_General.OK; } -load :: proc{load_from_file, load_from_slice, load_from_stream}; \ No newline at end of file +load :: proc{load_from_file, load_from_slice, load_from_stream}; diff --git a/core/compress/zlib/example.odin b/core/compress/zlib/example.odin index a7fa76d62..0906d7eef 100644 --- a/core/compress/zlib/example.odin +++ b/core/compress/zlib/example.odin @@ -7,30 +7,30 @@ import "core:fmt" main :: proc() { - ODIN_DEMO: []u8 = { + ODIN_DEMO := []u8{ 120, 156, 101, 144, 77, 110, 131, 48, 16, 133, 215, 204, 41, 158, 44, - 69, 73, 32, 148, 182, 75, 35, 14, 208, 125, 47, 96, 185, 195, 143, + 69, 73, 32, 148, 182, 75, 35, 14, 208, 125, 47, 96, 185, 195, 143, 130, 13, 50, 38, 81, 84, 101, 213, 75, 116, 215, 43, 246, 8, 53, - 82, 126, 8, 181, 188, 152, 153, 111, 222, 147, 159, 123, 165, 247, 170, - 98, 24, 213, 88, 162, 198, 244, 157, 243, 16, 186, 115, 44, 75, 227, - 5, 77, 115, 72, 137, 222, 117, 122, 179, 197, 39, 69, 161, 170, 156, - 50, 144, 5, 68, 130, 4, 49, 126, 127, 190, 191, 144, 34, 19, 57, - 69, 74, 235, 209, 140, 173, 242, 157, 155, 54, 158, 115, 162, 168, 12, + 82, 126, 8, 181, 188, 152, 153, 111, 222, 147, 159, 123, 165, 247, 170, + 98, 24, 213, 88, 162, 198, 244, 157, 243, 16, 186, 115, 44, 75, 227, + 5, 77, 115, 72, 137, 222, 117, 122, 179, 197, 39, 69, 161, 170, 156, + 50, 144, 5, 68, 130, 4, 49, 126, 127, 190, 191, 144, 34, 19, 57, + 69, 74, 235, 209, 140, 173, 242, 157, 155, 54, 158, 115, 162, 168, 12, 181, 239, 246, 108, 17, 188, 174, 242, 224, 20, 13, 199, 198, 235, 250, 194, 166, 129, 86, 3, 99, 157, 172, 37, 230, 62, 73, 129, 151, 252, 70, 211, 5, 77, 31, 104, 188, 160, 113, 129, 215, 59, 205, 22, 52, 123, 160, 83, 142, 255, 242, 89, 123, 93, 149, 200, 50, 188, 85, 54, 252, 18, 248, 192, 238, 228, 235, 198, 86, 224, 118, 224, 176, 113, 166, 112, 67, 106, 227, 159, 122, 215, 88, 95, 110, 196, 123, 205, 183, 224, - 98, 53, 8, 104, 213, 234, 201, 147, 7, 248, 192, 14, 170, 29, 25, + 98, 53, 8, 104, 213, 234, 201, 147, 7, 248, 192, 14, 170, 29, 25, 171, 15, 18, 59, 138, 112, 63, 23, 205, 110, 254, 136, 109, 78, 231, - 63, 234, 138, 133, 204, + 63, 234, 138, 133, 204, }; buf: bytes.Buffer; // We can pass ", true" to inflate a raw DEFLATE stream instead of a ZLIB wrapped one. - err := zlib.inflate(&ODIN_DEMO, &buf); + err := zlib.inflate(ODIN_DEMO, &buf); defer bytes.buffer_destroy(&buf); if !zlib.is_kind(err, zlib.E_General.OK) { @@ -39,4 +39,4 @@ main :: proc() { 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) == 438); -} \ No newline at end of file +} diff --git a/core/compress/zlib/zlib.odin b/core/compress/zlib/zlib.odin index 34a7984a7..5ce1f26ad 100644 --- a/core/compress/zlib/zlib.odin +++ b/core/compress/zlib/zlib.odin @@ -21,8 +21,8 @@ Compression_Method :: enum u8 { Compression_Level :: enum u8 { Fastest = 0, Fast = 1, - Default = 2, - Maximum = 3, + Default = 2, + Maximum = 3, } Options :: struct { @@ -68,19 +68,19 @@ Z_LENGTH_DEZIGZAG := []u8{ }; Z_FIXED_LENGTH := [288]u8{ - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8, }; Z_FIXED_DIST := [32]u8{ - 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, }; /* @@ -94,12 +94,12 @@ ZFAST_MASK :: ((1 << ZFAST_BITS) - 1); JPEG packs from left, ZLIB from right. We can't share code. */ Huffman_Table :: struct { - fast: [1 << ZFAST_BITS]u16, - firstcode: [16]u16, - maxcode: [17]int, - firstsymbol: [16]u16, - size: [288]u8, - value: [288]u16, + fast: [1 << ZFAST_BITS]u16, + firstcode: [16]u16, + maxcode: [17]int, + firstsymbol: [16]u16, + size: [288]u8, + value: [288]u16, }; // Implementation starts here @@ -218,19 +218,19 @@ decode_huffman_slowpath :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err: if (s >= 16) { return 0, E_Deflate.Bad_Huffman_Code; } - // code size is s, so: - b := (k >> (16-s)) - int(t.firstcode[s]) + int(t.firstsymbol[s]); - if b >= size_of(t.size) { - return 0, E_Deflate.Bad_Huffman_Code; - } - if t.size[b] != s { - return 0, E_Deflate.Bad_Huffman_Code; - } - - compress.consume_bits_lsb(z, s); - - r = t.value[b]; - return r, E_General.OK; + // code size is s, so: + b := (k >> (16-s)) - int(t.firstcode[s]) + int(t.firstsymbol[s]); + if b >= size_of(t.size) { + return 0, E_Deflate.Bad_Huffman_Code; + } + if t.size[b] != s { + return 0, E_Deflate.Bad_Huffman_Code; + } + + compress.consume_bits_lsb(z, s); + + r = t.value[b]; + return r, E_General.OK; } decode_huffman :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check { @@ -254,7 +254,6 @@ decode_huffman :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err: Error) # } parse_huffman_block :: proc(z: ^Context, z_repeat, z_offset: ^Huffman_Table) -> (err: Error) #no_bounds_check { - #no_bounds_check for { value, e := decode_huffman(z, z_repeat); if !is_kind(e, E_General.OK) { @@ -267,8 +266,8 @@ parse_huffman_block :: proc(z: ^Context, z_repeat, z_offset: ^Huffman_Table) -> } } else { if value == 256 { - // End of block - return E_General.OK; + // End of block + return E_General.OK; } value -= 257; @@ -370,7 +369,7 @@ inflate_from_stream :: proc(using ctx: ^Context, raw := false, allocator := cont } fdict := (flg >> 5) & 1; - /* + /* We don't handle built-in dictionaries for now. They're application specific and PNG doesn't use them. */ @@ -449,7 +448,8 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> // log.debugf("Final: %v | Type: %v\n", final, type); - if type == 0 { + switch type { + case 0: // Uncompressed block // Discard bits until next byte boundary @@ -471,9 +471,9 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> write_byte(z, u8(lit)); uncompressed_len -= 1; } - } else if type == 3 { + case 3: return E_Deflate.BType_3; - } else { + case: // log.debugf("Err: %v | Final: %v | Type: %v\n", err, final, type); if type == 1 { // Use fixed code lengths. @@ -487,12 +487,12 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> } } else { lencodes: [286+32+137]u8; - codelength_sizes: [19]u8; + codelength_sizes: [19]u8; - //i: u32; - n: u32; + //i: u32; + n: u32; - compress.refill_lsb(z, 14); + compress.refill_lsb(z, 14); hlit := compress.read_bits_no_refill_lsb(z, 5) + 257; hdist := compress.read_bits_no_refill_lsb(z, 5) + 1; hclen := compress.read_bits_no_refill_lsb(z, 4) + 4; @@ -525,34 +525,35 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> } else { fill := u8(0); compress.refill_lsb(z, 7); - if c == 16 { + switch c { + case 16: c = u16(compress.read_bits_no_refill_lsb(z, 2) + 3); - if n == 0 { - return E_Deflate.Huffman_Bad_Code_Lengths; - } - fill = lencodes[n - 1]; - } else if c == 17 { - c = u16(compress.read_bits_no_refill_lsb(z, 3) + 3); - } else if c == 18 { - c = u16(compress.read_bits_no_refill_lsb(z, 7) + 11); - } else { - return E_Deflate.Huffman_Bad_Code_Lengths; - } + if n == 0 { + return E_Deflate.Huffman_Bad_Code_Lengths; + } + fill = lencodes[n - 1]; + case 17: + c = u16(compress.read_bits_no_refill_lsb(z, 3) + 3); + case 18: + c = u16(compress.read_bits_no_refill_lsb(z, 7) + 11); + case: + return E_Deflate.Huffman_Bad_Code_Lengths; + } if ntot - n < u32(c) { - return E_Deflate.Huffman_Bad_Code_Lengths; - } + return E_Deflate.Huffman_Bad_Code_Lengths; + } - nc := n + u32(c); - #no_bounds_check for ; n < nc; n += 1 { - lencodes[n] = fill; - } + nc := n + u32(c); + #no_bounds_check for ; n < nc; n += 1 { + lencodes[n] = fill; + } } } if n != ntot { - return E_Deflate.Huffman_Bad_Code_Lengths; - } + return E_Deflate.Huffman_Bad_Code_Lengths; + } err = build_huffman(z_repeat, lencodes[:hlit]); if !is_kind(err, E_General.OK) { @@ -577,11 +578,11 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> return E_General.OK; } -inflate_from_byte_array :: proc(input: ^[]u8, buf: ^bytes.Buffer, raw := false) -> (err: Error) { +inflate_from_byte_array :: proc(input: []u8, buf: ^bytes.Buffer, raw := false) -> (err: Error) { ctx := Context{}; r := bytes.Reader{}; - bytes.reader_init(&r, input^); + bytes.reader_init(&r, input); rs := bytes.reader_to_stream(&r); ctx.input = rs; @@ -594,9 +595,9 @@ inflate_from_byte_array :: proc(input: ^[]u8, buf: ^bytes.Buffer, raw := false) return err; } -inflate_from_byte_array_raw :: proc(input: ^[]u8, buf: ^bytes.Buffer, raw := false) -> (err: Error) { +inflate_from_byte_array_raw :: proc(input: []u8, buf: ^bytes.Buffer, raw := false) -> (err: Error) { return inflate_from_byte_array(input, buf, true); } inflate :: proc{inflate_from_stream, inflate_from_byte_array}; -inflate_raw :: proc{inflate_from_stream_raw, inflate_from_byte_array_raw}; \ No newline at end of file +inflate_raw :: proc{inflate_from_stream_raw, inflate_from_byte_array_raw}; diff --git a/core/image/png/example.odin b/core/image/png/example.odin index 8cac5a505..be6cdd7d7 100644 --- a/core/image/png/example.odin +++ b/core/image/png/example.odin @@ -194,7 +194,7 @@ write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: b } defer close(fd); - write_string(fd, + write_string(fd, fmt.tprintf("P6\n%v %v\n%v\n", width, height, (1 << depth -1)), ); @@ -324,4 +324,4 @@ write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: b } } return true; -} \ No newline at end of file +} diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index a179cd23b..9bd5f55b2 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -76,102 +76,102 @@ core_time :: proc(c: Chunk) -> (t: coretime.Time, ok: bool) { using png_time; return coretime.datetime_to_time( int(year), int(month), int(day), - int(hour), int(minute), int(second)); + int(hour), int(minute), int(second), + ); } text :: proc(c: Chunk) -> (res: Text, ok: bool) { #partial switch c.header.type { - case .tEXt: - ok = true; - - fields := bytes.split(s=c.data, sep=[]u8{0}, allocator=context.temp_allocator); - if len(fields) == 2 { - res.keyword = strings.clone(string(fields[0])); - res.text = strings.clone(string(fields[1])); - } else { - ok = false; - } - return; - case .zTXt: - ok = true; - - fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator); - if len(fields) != 3 || len(fields[1]) != 0 { - // Compression method must be 0=Deflate, which thanks to the split above turns - // into an empty slice - ok = false; return; - } - - // Set up ZLIB context and decompress text payload. - buf: bytes.Buffer; - zlib_error := zlib.inflate_from_byte_array(&fields[2], &buf); - defer bytes.buffer_destroy(&buf); - if !is_kind(zlib_error, E_General.OK) { - ok = false; return; - } + case .tEXt: + ok = true; + fields := bytes.split(s=c.data, sep=[]u8{0}, allocator=context.temp_allocator); + if len(fields) == 2 { res.keyword = strings.clone(string(fields[0])); - res.text = strings.clone(bytes.buffer_to_string(&buf)); - return; - case .iTXt: - ok = true; + res.text = strings.clone(string(fields[1])); + } else { + ok = false; + } + return; + case .zTXt: + ok = true; - s := string(c.data); - null := strings.index_byte(s, 0); - if null == -1 { - ok = false; return; - } - if len(c.data) < null + 4 { - // At a minimum, including the \0 following the keyword, we require 5 more bytes. - ok = false; return; - } - res.keyword = strings.clone(string(c.data[:null])); - rest := c.data[null+1:]; + fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator); + if len(fields) != 3 || len(fields[1]) != 0 { + // Compression method must be 0=Deflate, which thanks to the split above turns + // into an empty slice + ok = false; return; + } - compression_flag := rest[:1][0]; - if compression_flag > 1 { - ok = false; return; - } - compression_method := rest[1:2][0]; - if compression_flag == 1 && compression_method > 0 { - // Only Deflate is supported - ok = false; return; - } - rest = rest[2:]; + // Set up ZLIB context and decompress text payload. + buf: bytes.Buffer; + zlib_error := zlib.inflate_from_byte_array(fields[2], &buf); + defer bytes.buffer_destroy(&buf); + if !is_kind(zlib_error, E_General.OK) { + ok = false; return; + } - // We now expect an optional language keyword and translated keyword, both followed by a \0 - null = strings.index_byte(string(rest), 0); - if null == -1 { - ok = false; return; - } - res.language = strings.clone(string(rest[:null])); - rest = rest[null+1:]; + res.keyword = strings.clone(string(fields[0])); + res.text = strings.clone(bytes.buffer_to_string(&buf)); + return; + case .iTXt: + ok = true; - null = strings.index_byte(string(rest), 0); - if null == -1 { - ok = false; return; - } - res.keyword_localized = strings.clone(string(rest[:null])); - rest = rest[null+1:]; - if compression_flag == 0 { - res.text = strings.clone(string(rest)); - } else { - // Set up ZLIB context and decompress text payload. - buf: bytes.Buffer; - zlib_error := zlib.inflate_from_byte_array(&rest, &buf); - defer bytes.buffer_destroy(&buf); - if !is_kind(zlib_error, E_General.OK) { - - ok = false; return; - } + s := string(c.data); + null := strings.index_byte(s, 0); + if null == -1 { + ok = false; return; + } + if len(c.data) < null + 4 { + // At a minimum, including the \0 following the keyword, we require 5 more bytes. + ok = false; return; + } + res.keyword = strings.clone(string(c.data[:null])); + rest := c.data[null+1:]; - res.text = strings.clone(bytes.buffer_to_string(&buf)); - } - return; - case: - // PNG text helper called with an unrecognized chunk type. + compression_flag := rest[:1][0]; + if compression_flag > 1 { + ok = false; return; + } + compression_method := rest[1:2][0]; + if compression_flag == 1 && compression_method > 0 { + // Only Deflate is supported ok = false; return; + } + rest = rest[2:]; + // We now expect an optional language keyword and translated keyword, both followed by a \0 + null = strings.index_byte(string(rest), 0); + if null == -1 { + ok = false; return; + } + res.language = strings.clone(string(rest[:null])); + rest = rest[null+1:]; + + null = strings.index_byte(string(rest), 0); + if null == -1 { + ok = false; return; + } + res.keyword_localized = strings.clone(string(rest[:null])); + rest = rest[null+1:]; + if compression_flag == 0 { + res.text = strings.clone(string(rest)); + } else { + // Set up ZLIB context and decompress text payload. + buf: bytes.Buffer; + zlib_error := zlib.inflate_from_byte_array(rest, &buf); + defer bytes.buffer_destroy(&buf); + if !is_kind(zlib_error, E_General.OK) { + + ok = false; return; + } + + res.text = strings.clone(bytes.buffer_to_string(&buf)); + } + return; + case: + // PNG text helper called with an unrecognized chunk type. + ok = false; return; } } @@ -199,7 +199,7 @@ iccp :: proc(c: Chunk) -> (res: iCCP, ok: bool) { // Set up ZLIB context and decompress iCCP payload buf: bytes.Buffer; - zlib_error := zlib.inflate_from_byte_array(&fields[2], &buf); + zlib_error := zlib.inflate_from_byte_array(fields[2], &buf); if !is_kind(zlib_error, E_General.OK) { bytes.buffer_destroy(&buf); ok = false; return; @@ -458,19 +458,14 @@ when false { interlace_method = .None, }; - if channels == 1 { - ihdr.color_type = Color_Type{}; - } else if channels == 2 { - ihdr.color_type = Color_Type{.Alpha}; - } else if channels == 3 { - ihdr.color_type = Color_Type{.Color}; - } else if channels == 4 { - ihdr.color_type = Color_Type{.Color, .Alpha}; - } else { - // Unhandled + switch channels { + case 1: ihdr.color_type = Color_Type{}; + case 2: ihdr.color_type = Color_Type{.Alpha}; + case 3: ihdr.color_type = Color_Type{.Color}; + case 4: ihdr.color_type = Color_Type{.Color, .Alpha}; + case:// Unhandled return E_PNG.Unknown_Color_Type; } - h := make_chunk(ihdr, .IHDR); write_chunk(fd, h); @@ -518,4 +513,4 @@ when false { return E_General.OK; } -} \ No newline at end of file +} diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 5ea9979a8..c8dcd24a3 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -350,9 +350,9 @@ chunk_type_to_name :: proc(type: ^Chunk_Type) -> string { return strings.string_from_ptr(t, 4); } -load_from_slice :: proc(slice: ^[]u8, options: Options = {}, allocator := context.allocator) -> (img: ^Image, err: Error) { +load_from_slice :: proc(slice: []u8, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { r := bytes.Reader{}; - bytes.reader_init(&r, slice^); + bytes.reader_init(&r, slice); stream := bytes.reader_to_stream(&r); /* @@ -360,17 +360,17 @@ load_from_slice :: proc(slice: ^[]u8, options: Options = {}, allocator := contex This way the stream reader could avoid the copy into the temp memory returned by it, and instead return a slice into the original memory that's already owned by the caller. */ - img, err = load_from_stream(&stream, options, allocator); + img, err = load_from_stream(stream, options, allocator); return img, err; } -load_from_file :: proc(filename: string, options: Options = {}, allocator := context.allocator) -> (img: ^Image, err: Error) { +load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { data, ok := os.read_entire_file(filename, allocator); defer delete(data); if ok { - img, err = load_from_slice(&data, options, allocator); + img, err = load_from_slice(data, options, allocator); return; } else { img = new(Image); @@ -378,7 +378,7 @@ load_from_file :: proc(filename: string, options: Options = {}, allocator := con } } -load_from_stream :: proc(stream: ^io.Stream, options: Options = {}, allocator := context.allocator) -> (img: ^Image, err: Error) { +load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { options := options; if .info in options { options |= {.return_metadata, .do_not_decompress_image}; @@ -396,7 +396,7 @@ load_from_stream :: proc(stream: ^io.Stream, options: Options = {}, allocator := img.sidecar = nil; ctx := compress.Context{ - input = stream^, + input = stream, }; signature, io_error := compress.read_data(&ctx, Signature); @@ -669,7 +669,7 @@ load_from_stream :: proc(stream: ^io.Stream, options: Options = {}, allocator := } buf: bytes.Buffer; - zlib_error := zlib.inflate(&idat, &buf); + zlib_error := zlib.inflate(idat, &buf); defer bytes.buffer_destroy(&buf); if !is_kind(zlib_error, E_General.OK) { @@ -817,8 +817,7 @@ load_from_stream :: proc(stream: ^io.Stream, options: Options = {}, allocator := } } } else { - // This should be impossible. - assert(false); + unreachable(); } img.pixels = t; @@ -845,145 +844,145 @@ load_from_stream :: proc(stream: ^io.Stream, options: Options = {}, allocator := o16 := mem.slice_data_cast([]u16, t.buf[:]); switch (raw_image_channels) { - case 1: - // Gray without Alpha. Might have tRNS alpha. - key := u16(0); - if seen_trns { - key = mem.slice_data_cast([]u16, trns.data)[0]; - } + case 1: + // Gray without Alpha. Might have tRNS alpha. + key := u16(0); + if seen_trns { + key = mem.slice_data_cast([]u16, trns.data)[0]; + } - for len(p16) > 0 { - r := p16[0]; + for len(p16) > 0 { + r := p16[0]; - alpha := u16(1); // Default to full opaque + alpha := u16(1); // Default to full opaque - if seen_trns { - if r == key { - if seen_bkgd { - c := img.background.([3]u16); - r = c[0]; - } else { - alpha = 0; // Keyed transparency - } + if seen_trns { + if r == key { + if seen_bkgd { + c := img.background.([3]u16); + r = c[0]; + } else { + alpha = 0; // Keyed transparency } } + } - if premultiply { - o16[0] = r * alpha; - o16[1] = r * alpha; - o16[2] = r * alpha; - } else { - o16[0] = r; - o16[1] = r; - o16[2] = r; - } - - if out_image_channels == 4 { - o16[3] = alpha * 65535; - } - - p16 = p16[1:]; - o16 = o16[out_image_channels:]; + if premultiply { + o16[0] = r * alpha; + o16[1] = r * alpha; + o16[2] = r * alpha; + } else { + o16[0] = r; + o16[1] = r; + o16[2] = r; } - case 2: - // Gray with alpha, we shouldn't have a tRNS chunk. - for len(p16) > 0 { - r := p16[0]; - if premultiply { - alpha := p16[1]; - c := u16(f32(r) * f32(alpha) / f32(65535)); - o16[0] = c; - o16[1] = c; - o16[2] = c; - } else { - o16[0] = r; - o16[1] = r; - o16[2] = r; - } - if .alpha_drop_if_present not_in options { - o16[3] = p16[1]; - } + if out_image_channels == 4 { + o16[3] = alpha * 65535; + } - p16 = p16[2:]; - o16 = o16[out_image_channels:]; + p16 = p16[1:]; + o16 = o16[out_image_channels:]; + } + case 2: + // Gray with alpha, we shouldn't have a tRNS chunk. + for len(p16) > 0 { + r := p16[0]; + if premultiply { + alpha := p16[1]; + c := u16(f32(r) * f32(alpha) / f32(65535)); + o16[0] = c; + o16[1] = c; + o16[2] = c; + } else { + o16[0] = r; + o16[1] = r; + o16[2] = r; } - case 3: - /* - Color without Alpha. - We may still have a tRNS chunk or `.alpha_add_if_missing`. - */ - key: []u16; - if seen_trns { - key = mem.slice_data_cast([]u16, trns.data); + if .alpha_drop_if_present not_in options { + o16[3] = p16[1]; } - for len(p16) > 0 { - r := p16[0]; - g := p16[1]; - b := p16[2]; - - alpha := u16(1); // Default to full opaque - - if seen_trns { - if r == key[0] && g == key[1] && b == key[2] { - if seen_bkgd { - c := img.background.([3]u16); - r = c[0]; - g = c[1]; - b = c[2]; - } else { - alpha = 0; // Keyed transparency - } - } - } + p16 = p16[2:]; + o16 = o16[out_image_channels:]; + } + case 3: + /* + Color without Alpha. + We may still have a tRNS chunk or `.alpha_add_if_missing`. + */ - if premultiply { - o16[0] = r * alpha; - o16[1] = g * alpha; - o16[2] = b * alpha; - } else { - o16[0] = r; - o16[1] = g; - o16[2] = b; - } + key: []u16; + if seen_trns { + key = mem.slice_data_cast([]u16, trns.data); + } + + for len(p16) > 0 { + r := p16[0]; + g := p16[1]; + b := p16[2]; - if out_image_channels == 4 { - o16[3] = alpha * 65535; + alpha := u16(1); // Default to full opaque + + if seen_trns { + if r == key[0] && g == key[1] && b == key[2] { + if seen_bkgd { + c := img.background.([3]u16); + r = c[0]; + g = c[1]; + b = c[2]; + } else { + alpha = 0; // Keyed transparency + } } + } - p16 = p16[3:]; - o16 = o16[out_image_channels:]; + if premultiply { + o16[0] = r * alpha; + o16[1] = g * alpha; + o16[2] = b * alpha; + } else { + o16[0] = r; + o16[1] = g; + o16[2] = b; } - case 4: - // Color with Alpha, can't have tRNS. - for len(p16) > 0 { - r := p16[0]; - g := p16[1]; - b := p16[2]; - a := p16[3]; - if premultiply { - alpha := f32(a) / 65535.0; - o16[0] = u16(f32(r) * alpha); - o16[1] = u16(f32(g) * alpha); - o16[2] = u16(f32(b) * alpha); - } else { - o16[0] = r; - o16[1] = g; - o16[2] = b; - } + if out_image_channels == 4 { + o16[3] = alpha * 65535; + } - if .alpha_drop_if_present not_in options { - o16[3] = a; - } + p16 = p16[3:]; + o16 = o16[out_image_channels:]; + } + case 4: + // Color with Alpha, can't have tRNS. + for len(p16) > 0 { + r := p16[0]; + g := p16[1]; + b := p16[2]; + a := p16[3]; + + if premultiply { + alpha := f32(a) / 65535.0; + o16[0] = u16(f32(r) * alpha); + o16[1] = u16(f32(g) * alpha); + o16[2] = u16(f32(b) * alpha); + } else { + o16[0] = r; + o16[1] = g; + o16[2] = b; + } - p16 = p16[4:]; - o16 = o16[out_image_channels:]; + if .alpha_drop_if_present not_in options { + o16[3] = a; } - case: - unreachable("We should never seen # channels other than 1-4 inclusive."); + + p16 = p16[4:]; + o16 = o16[out_image_channels:]; + } + case: + unreachable("We should never seen # channels other than 1-4 inclusive."); } img.pixels = t; @@ -1011,143 +1010,143 @@ load_from_stream :: proc(stream: ^io.Stream, options: Options = {}, allocator := o := mem.slice_data_cast([]u8, t.buf[:]); switch (raw_image_channels) { - case 1: - // Gray without Alpha. Might have tRNS alpha. - key := u8(0); - if seen_trns { - key = u8(mem.slice_data_cast([]u16be, trns.data)[0]); - } + case 1: + // Gray without Alpha. Might have tRNS alpha. + key := u8(0); + if seen_trns { + key = u8(mem.slice_data_cast([]u16be, trns.data)[0]); + } - for len(p) > 0 { - r := p[0]; - alpha := u8(1); - - if seen_trns { - if r == key { - if seen_bkgd { - c := img.background.([3]u16); - r = u8(c[0]); - } else { - alpha = 0; // Keyed transparency - } - } - if premultiply { - o[0] = r * alpha; - o[1] = r * alpha; - o[2] = r * alpha; + for len(p) > 0 { + r := p[0]; + alpha := u8(1); + + if seen_trns { + if r == key { + if seen_bkgd { + c := img.background.([3]u16); + r = u8(c[0]); + } else { + alpha = 0; // Keyed transparency } - } else { - o[0] = r; - o[1] = r; - o[2] = r; } - - if out_image_channels == 4 { - o[3] = alpha * 255; + if premultiply { + o[0] = r * alpha; + o[1] = r * alpha; + o[2] = r * alpha; } - - p = p[1:]; - o = o[out_image_channels:]; + } else { + o[0] = r; + o[1] = r; + o[2] = r; } - case 2: - // Gray with alpha, we shouldn't have a tRNS chunk. - for len(p) > 0 { - r := p[0]; - if .alpha_premultiply in options { - alpha := p[1]; - c := u8(f32(r) * f32(alpha) / f32(255)); - o[0] = c; - o[1] = c; - o[2] = c; - } else { - o[0] = r; - o[1] = r; - o[2] = r; - } - if .alpha_drop_if_present not_in options { - o[3] = p[1]; - } + if out_image_channels == 4 { + o[3] = alpha * 255; + } - p = p[2:]; - o = o[out_image_channels:]; + p = p[1:]; + o = o[out_image_channels:]; + } + case 2: + // Gray with alpha, we shouldn't have a tRNS chunk. + for len(p) > 0 { + r := p[0]; + if .alpha_premultiply in options { + alpha := p[1]; + c := u8(f32(r) * f32(alpha) / f32(255)); + o[0] = c; + o[1] = c; + o[2] = c; + } else { + o[0] = r; + o[1] = r; + o[2] = r; } - case 3: - // Color without Alpha. We may still have a tRNS chunk - key: []u8; - if seen_trns { - /* - For 8-bit images, the tRNS chunk still contains a triple in u16be. - We use only the low byte in this case. - */ - key = []u8{trns.data[1], trns.data[3], trns.data[5]}; + + if .alpha_drop_if_present not_in options { + o[3] = p[1]; } - for len(p) > 0 { - r := p[0]; - g := p[1]; - b := p[2]; - - alpha := u8(1); // Default to full opaque - - // TODO: Combine the seen_trns cases. - if seen_trns { - if r == key[0] && g == key[1] && b == key[2] { - if seen_bkgd { - c := img.background.([3]u16); - r = u8(c[0]); - g = u8(c[1]); - b = u8(c[2]); - } else { - alpha = 0; // Keyed transparency - } - } - if .alpha_premultiply in options || .blend_background in options { - o[0] = r * alpha; - o[1] = g * alpha; - o[2] = b * alpha; + p = p[2:]; + o = o[out_image_channels:]; + } + case 3: + // Color without Alpha. We may still have a tRNS chunk + key: []u8; + if seen_trns { + /* + For 8-bit images, the tRNS chunk still contains a triple in u16be. + We use only the low byte in this case. + */ + key = []u8{trns.data[1], trns.data[3], trns.data[5]}; + } + for len(p) > 0 { + r := p[0]; + g := p[1]; + b := p[2]; + + alpha := u8(1); // Default to full opaque + + // TODO: Combine the seen_trns cases. + if seen_trns { + if r == key[0] && g == key[1] && b == key[2] { + if seen_bkgd { + c := img.background.([3]u16); + r = u8(c[0]); + g = u8(c[1]); + b = u8(c[2]); + } else { + alpha = 0; // Keyed transparency } - } else { - o[0] = r; - o[1] = g; - o[2] = b; } - if out_image_channels == 4 { - o[3] = alpha * 255; + if .alpha_premultiply in options || .blend_background in options { + o[0] = r * alpha; + o[1] = g * alpha; + o[2] = b * alpha; } + } else { + o[0] = r; + o[1] = g; + o[2] = b; + } - p = p[3:]; - o = o[out_image_channels:]; + if out_image_channels == 4 { + o[3] = alpha * 255; } - case 4: - // Color with Alpha, can't have tRNS. - for len(p) > 0 { - r := p[0]; - g := p[1]; - b := p[2]; - a := p[3]; - - if .alpha_premultiply in options { - alpha := f32(a) / 255.0; - o[0] = u8(f32(r) * alpha); - o[1] = u8(f32(g) * alpha); - o[2] = u8(f32(b) * alpha); - } else { - o[0] = r; - o[1] = g; - o[2] = b; - } - if .alpha_drop_if_present not_in options { - o[3] = a; - } + p = p[3:]; + o = o[out_image_channels:]; + } + case 4: + // Color with Alpha, can't have tRNS. + for len(p) > 0 { + r := p[0]; + g := p[1]; + b := p[2]; + a := p[3]; + + if .alpha_premultiply in options { + alpha := f32(a) / 255.0; + o[0] = u8(f32(r) * alpha); + o[1] = u8(f32(g) * alpha); + o[2] = u8(f32(b) * alpha); + } else { + o[0] = r; + o[1] = g; + o[2] = b; + } - p = p[4:]; - o = o[out_image_channels:]; + if .alpha_drop_if_present not_in options { + o[3] = a; } - case: - unreachable("We should never seen # channels other than 1-4 inclusive."); + + p = p[4:]; + o = o[out_image_channels:]; + } + case: + unreachable("We should never seen # channels other than 1-4 inclusive."); } img.pixels = t; @@ -1181,13 +1180,13 @@ filter_paeth :: #force_inline proc(left, up, up_left: u8) -> u8 { } Filter_Params :: struct #packed { - src : []u8, - dest : []u8, - width : int, - height : int, - depth : int, + src: []u8, + dest: []u8, + width: int, + height: int, + depth: int, channels: int, - rescale : bool, + rescale: bool, } depth_scale_table :: []u8{0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01}; @@ -1210,39 +1209,39 @@ defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) { filter := Row_Filter(src[0]); src = src[1:]; // fmt.printf("Row: %v | Filter: %v\n", y, filter); switch(filter) { - case .None: - copy(dest, src[:row_stride]); - case .Sub: - for i := 0; i < channels; i += 1 { - dest[i] = src[i]; - } - for k := 0; k < nk; k += 1 { - dest[channels+k] = (src[channels+k] + dest[k]) & 255; - } - case .Up: - for k := 0; k < row_stride; k += 1 { - dest[k] = (src[k] + up[k]) & 255; - } - case .Average: - for i := 0; i < channels; i += 1 { - avg := up[i] >> 1; - dest[i] = (src[i] + avg) & 255; - } - for k := 0; k < nk; k += 1 { - avg := u8((u16(up[channels+k]) + u16(dest[k])) >> 1); - dest[channels+k] = (src[channels+k] + avg) & 255; - } - case .Paeth: - for i := 0; i < channels; i += 1 { - paeth := filter_paeth(0, up[i], 0); - dest[i] = (src[i] + paeth) & 255; - } - for k := 0; k < nk; k += 1 { - paeth := filter_paeth(dest[k], up[channels+k], up[k]); - dest[channels+k] = (src[channels+k] + paeth) & 255; - } - case: - return false; + case .None: + copy(dest, src[:row_stride]); + case .Sub: + for i := 0; i < channels; i += 1 { + dest[i] = src[i]; + } + for k := 0; k < nk; k += 1 { + dest[channels+k] = (src[channels+k] + dest[k]) & 255; + } + case .Up: + for k := 0; k < row_stride; k += 1 { + dest[k] = (src[k] + up[k]) & 255; + } + case .Average: + for i := 0; i < channels; i += 1 { + avg := up[i] >> 1; + dest[i] = (src[i] + avg) & 255; + } + for k := 0; k < nk; k += 1 { + avg := u8((u16(up[channels+k]) + u16(dest[k])) >> 1); + dest[channels+k] = (src[channels+k] + avg) & 255; + } + case .Paeth: + for i := 0; i < channels; i += 1 { + paeth := filter_paeth(0, up[i], 0); + dest[i] = (src[i] + paeth) & 255; + } + for k := 0; k < nk; k += 1 { + paeth := filter_paeth(dest[k], up[channels+k], up[k]); + dest[channels+k] = (src[channels+k] + paeth) & 255; + } + case: + return false; } src = src[row_stride:]; @@ -1277,45 +1276,45 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_ch dest = dest[row_offset:]; filter := Row_Filter(src[0]); src = src[1:]; - switch(filter) { - case .None: - copy(dest, src[:row_stride_in]); - case .Sub: - for i in 0..channels { - dest[i] = src[i]; - } - for k in 0..nk { - dest[channels+k] = (src[channels+k] + dest[k]) & 255; - } - case .Up: - for k in 0..row_stride_in { - dest[k] = (src[k] + up[k]) & 255; - } - case .Average: - for i in 0..channels { - avg := up[i] >> 1; - dest[i] = (src[i] + avg) & 255; - } - for k in 0..nk { - avg := u8((u16(up[channels+k]) + u16(dest[k])) >> 1); - dest[channels+k] = (src[channels+k] + avg) & 255; - } - case .Paeth: - for i in 0..channels { - paeth := filter_paeth(0, up[i], 0); - dest[i] = (src[i] + paeth) & 255; - } - for k in 0..nk { - paeth := filter_paeth(dest[k], up[channels], up[k]); - dest[channels+k] = (src[channels+k] + paeth) & 255; - } - case: - return false; + switch filter { + case .None: + copy(dest, src[:row_stride_in]); + case .Sub: + for i in 0..channels { + dest[i] = src[i]; + } + for k in 0..nk { + dest[channels+k] = (src[channels+k] + dest[k]) & 255; + } + case .Up: + for k in 0..row_stride_in { + dest[k] = (src[k] + up[k]) & 255; + } + case .Average: + for i in 0..channels { + avg := up[i] >> 1; + dest[i] = (src[i] + avg) & 255; + } + for k in 0..nk { + avg := u8((u16(up[channels+k]) + u16(dest[k])) >> 1); + dest[channels+k] = (src[channels+k] + avg) & 255; + } + case .Paeth: + for i in 0..channels { + paeth := filter_paeth(0, up[i], 0); + dest[i] = (src[i] + paeth) & 255; + } + for k in 0..nk { + paeth := filter_paeth(dest[k], up[channels], up[k]); + dest[channels+k] = (src[channels+k] + paeth) & 255; + } + case: + return false; } - src = src [row_stride_in:]; - up = dest; - dest = dest[row_stride_in:]; + src = src [row_stride_in:]; + up = dest; + dest = dest[row_stride_in:]; } // Let's expand the bits @@ -1334,7 +1333,8 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_ch for j := 0; j < height; j += 1 { src = dest[row_offset:]; - if depth == 4 { + switch depth { + case 4: k := row_stride_out; for ; k >= 2; k -= 2 { c := src[0]; @@ -1347,7 +1347,7 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_ch dest[0] = scale * (c >> 4); dest = dest[1:]; } - } else if depth == 2 { + case 2: k := row_stride_out; for ; k >= 4; k -= 4 { c := src[0]; @@ -1368,7 +1368,7 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_ch } dest = dest[k:]; } - } else if depth == 1 { + case 1: k := row_stride_out; for ; k >= 8; k -= 8 { c := src[0]; @@ -1406,6 +1406,7 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_ch dest = dest[k:]; } + } } @@ -1429,40 +1430,40 @@ defilter_16 :: proc(params: ^Filter_Params) -> (ok: bool) { nk := row_stride - stride; filter := Row_Filter(src[0]); src = src[1:]; - switch(filter) { - case .None: - copy(dest, src[:row_stride]); - case .Sub: - for i := 0; i < stride; i += 1 { - dest[i] = src[i]; - } - for k := 0; k < nk; k += 1 { - dest[stride+k] = (src[stride+k] + dest[k]) & 255; - } - case .Up: - for k := 0; k < row_stride; k += 1 { - dest[k] = (src[k] + up[k]) & 255; - } - case .Average: - for i := 0; i < stride; i += 1 { - avg := up[i] >> 1; - dest[i] = (src[i] + avg) & 255; - } - for k := 0; k < nk; k += 1 { - avg := u8((u16(up[stride+k]) + u16(dest[k])) >> 1); - dest[stride+k] = (src[stride+k] + avg) & 255; - } - case .Paeth: - for i := 0; i < stride; i += 1 { - paeth := filter_paeth(0, up[i], 0); - dest[i] = (src[i] + paeth) & 255; - } - for k := 0; k < nk; k += 1 { - paeth := filter_paeth(dest[k], up[stride+k], up[k]); - dest[stride+k] = (src[stride+k] + paeth) & 255; - } - case: - return false; + switch filter { + case .None: + copy(dest, src[:row_stride]); + case .Sub: + for i := 0; i < stride; i += 1 { + dest[i] = src[i]; + } + for k := 0; k < nk; k += 1 { + dest[stride+k] = (src[stride+k] + dest[k]) & 255; + } + case .Up: + for k := 0; k < row_stride; k += 1 { + dest[k] = (src[k] + up[k]) & 255; + } + case .Average: + for i := 0; i < stride; i += 1 { + avg := up[i] >> 1; + dest[i] = (src[i] + avg) & 255; + } + for k := 0; k < nk; k += 1 { + avg := u8((u16(up[stride+k]) + u16(dest[k])) >> 1); + dest[stride+k] = (src[stride+k] + avg) & 255; + } + case .Paeth: + for i := 0; i < stride; i += 1 { + paeth := filter_paeth(0, up[i], 0); + dest[i] = (src[i] + paeth) & 255; + } + for k := 0; k < nk; k += 1 { + paeth := filter_paeth(dest[k], up[stride+k], up[k]); + dest[stride+k] = (src[stride+k] + paeth) & 255; + } + case: + return false; } src = src[row_stride:]; @@ -1510,7 +1511,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, option if !filter_ok { // Caller will destroy buffer for us. return E_PNG.Unknown_Filter_Method; - } + } } else { /* For deinterlacing we need to make a temporary buffer, defiilter part of the image, @@ -1582,7 +1583,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, option } } - return E_General.OK; + return E_General.OK; } -load :: proc{load_from_file, load_from_slice, load_from_stream}; \ No newline at end of file +load :: proc{load_from_file, load_from_slice, load_from_stream}; -- cgit v1.2.3 From a02bcd3bfdb0a12a4aa19bf45764f6b011e551de Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 May 2021 13:16:47 +0200 Subject: PNG: Fix test for when premultiplication is needed. --- core/image/png/png.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'core') diff --git a/core/image/png/png.odin b/core/image/png/png.odin index c8dcd24a3..8a6729484 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -731,7 +731,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c `.blend_background` and `seen_bkgd` if we haven't seen both. */ if !(seen_bkgd && .blend_background in options) { - options ~= {.blend_background}; + options -= {.blend_background}; seen_bkgd = false; } @@ -748,7 +748,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c } add_alpha := (seen_trns && .alpha_drop_if_present not_in options) || (.alpha_add_if_missing in options); - premultiply := .alpha_premultiply in options || .blend_background in options; + premultiply := .alpha_premultiply in options || seen_bkgd; img.channels = out_image_channels; -- cgit v1.2.3 From db1ef078ff1d7cb00d090c87a8d8b6981fadd530 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 May 2021 16:05:13 +0200 Subject: Fix a few more cases in which bKGD wasn't properly applied. --- core/image/png/png.odin | 83 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 12 deletions(-) (limited to 'core') diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 8a6729484..5b18e6619 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -886,9 +886,25 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c } case 2: // Gray with alpha, we shouldn't have a tRNS chunk. + bg := f32(0.0); + if seen_bkgd { + bg = f32(img.background.([3]u16)[0]); + } + for len(p16) > 0 { r := p16[0]; - if premultiply { + if seen_bkgd { + alpha := f32(p16[1]) / f32(65535); + c := u16(f32(r) * alpha + (1.0 - alpha) * bg); + o16[0] = c; + o16[1] = c; + o16[2] = c; + /* + After BG blending, the pixel is now fully opaque. + Update the value we'll write to the output alpha. + */ + p16[1] = 65535; + } else if premultiply { alpha := p16[1]; c := u16(f32(r) * f32(alpha) / f32(65535)); o16[0] = c; @@ -900,7 +916,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c o16[2] = r; } - if .alpha_drop_if_present not_in options { + if out_image_channels == 4 { o16[3] = p16[1]; } @@ -963,7 +979,22 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c b := p16[2]; a := p16[3]; - if premultiply { + if seen_bkgd { + alpha := f32(a) / 65535.0; + c := img.background.([3]u16); + rb := f32(c[0]) * (1.0 - alpha); + gb := f32(c[1]) * (1.0 - alpha); + bb := f32(c[2]) * (1.0 - alpha); + + o16[0] = u16(f32(r) * alpha + rb); + o16[1] = u16(f32(g) * alpha + gb); + o16[2] = u16(f32(b) * alpha + bb); + /* + After BG blending, the pixel is now fully opaque. + Update the value we'll write to the output alpha. + */ + a = 65535; + } else if premultiply { alpha := f32(a) / 65535.0; o16[0] = u16(f32(r) * alpha); o16[1] = u16(f32(g) * alpha); @@ -974,7 +1005,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c o16[2] = b; } - if .alpha_drop_if_present not_in options { + if out_image_channels == 4 { o16[3] = a; } @@ -992,7 +1023,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c // Check if we need to do something. if raw_image_channels == out_image_channels { // If we have 3 in and 3 out, or 4 in and 4 out without premultiplication... - if raw_image_channels == 4 && .alpha_premultiply not_in options { + if !premultiply { // Then we're done. return img, E_General.OK; } @@ -1050,9 +1081,25 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c } case 2: // Gray with alpha, we shouldn't have a tRNS chunk. + bg := f32(0.0); + if seen_bkgd { + bg = f32(img.background.([3]u16)[0]); + } + for len(p) > 0 { r := p[0]; - if .alpha_premultiply in options { + if seen_bkgd { + alpha := f32(p[1]) / f32(255); + c := u8(f32(r) * alpha + (1.0 - alpha) * bg); + o[0] = c; + o[1] = c; + o[2] = c; + /* + After BG blending, the pixel is now fully opaque. + Update the value we'll write to the output alpha. + */ + p[1] = 255; + } else if .alpha_premultiply in options { alpha := p[1]; c := u8(f32(r) * f32(alpha) / f32(255)); o[0] = c; @@ -1064,7 +1111,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c o[2] = r; } - if .alpha_drop_if_present not_in options { + if out_image_channels == 4 { o[3] = p[1]; } @@ -1088,7 +1135,6 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c alpha := u8(1); // Default to full opaque - // TODO: Combine the seen_trns cases. if seen_trns { if r == key[0] && g == key[1] && b == key[2] { if seen_bkgd { @@ -1126,8 +1172,22 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c g := p[1]; b := p[2]; a := p[3]; - - if .alpha_premultiply in options { + if seen_bkgd { + alpha := f32(a) / 255.0; + c := img.background.([3]u16); + rb := f32(c[0]) * (1.0 - alpha); + gb := f32(c[1]) * (1.0 - alpha); + bb := f32(c[2]) * (1.0 - alpha); + + o[0] = u8(f32(r) * alpha + rb); + o[1] = u8(f32(g) * alpha + gb); + o[2] = u8(f32(b) * alpha + bb); + /* + After BG blending, the pixel is now fully opaque. + Update the value we'll write to the output alpha. + */ + a = 255; + } else if premultiply { alpha := f32(a) / 255.0; o[0] = u8(f32(r) * alpha); o[1] = u8(f32(g) * alpha); @@ -1138,7 +1198,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c o[2] = b; } - if .alpha_drop_if_present not_in options { + if out_image_channels == 4 { o[3] = a; } @@ -1207,7 +1267,6 @@ defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) { nk := row_stride - channels; filter := Row_Filter(src[0]); src = src[1:]; - // fmt.printf("Row: %v | Filter: %v\n", y, filter); switch(filter) { case .None: copy(dest, src[:row_stride]); -- cgit v1.2.3 From 97b537f80029724e2a9b9f9240532aeb95e1143d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 1 May 2021 16:23:50 +0100 Subject: Update intrinsics.odin for documentation --- core/intrinsics/intrinsics.odin | 76 +++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 33 deletions(-) (limited to 'core') diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index ac916a693..84d5b06b5 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -12,7 +12,28 @@ volatile_store :: proc(dst: ^$T, val: T) -> T --- // Trapping debug_trap :: proc() --- -trap :: proc() -> ! --- +trap :: proc() -> ! --- + +// Instructions + +alloca :: proc(size, align: int) -> ^u8 --- +cpu_relax :: proc() --- +read_cycle_counter :: proc() -> i64 --- + +count_ones :: proc(x: $T) -> T where type_is_integer(T) --- +count_zeros :: proc(x: $T) -> T where type_is_integer(T) --- +count_trailing_zeros :: proc(x: $T) -> T where type_is_integer(T) --- +count_leading_zeros :: proc(x: $T) -> T where type_is_integer(T) --- +reverse_bits :: proc(x: $T) -> T where type_is_integer(T) --- +byte_swap :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) --- + +overflow_add :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok --- +overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok --- +overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok --- + +// Compiler Hints +expect :: proc(val, expected_val: T) -> T --- + // Atomics atomic_fence :: proc() --- @@ -67,36 +88,25 @@ atomic_xchg_rel :: proc(dst; ^$T, val: T) -> T --- atomic_xchg_acqrel :: proc(dst; ^$T, val: T) -> T --- atomic_xchg_relaxed :: proc(dst; ^$T, val: T) -> T --- -atomic_cxchg :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- -atomic_cxchg_acq :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- -atomic_cxchg_rel :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- -atomic_cxchg_acqrel :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- -atomic_cxchg_relaxed :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- -atomic_cxchg_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- -atomic_cxchg_failacq :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- -atomic_cxchg_acq_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- -atomic_cxchg_acqrel_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- - -atomic_cxchgweak :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- -atomic_cxchgweak_acq :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- -atomic_cxchgweak_rel :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- -atomic_cxchgweak_acqrel :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- -atomic_cxchgweak_relaxed :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- -atomic_cxchgweak_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- -atomic_cxchgweak_failacq :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- -atomic_cxchgweak_acq_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- -atomic_cxchgweak_acqrel_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, /*option*/bool) --- - -// Instructions - -alloca :: proc(size, align: int) -> ^u8 --- -cpu_relax :: proc() --- -read_cycle_counter :: proc() -> i64 --- - - -// Compiler Hints -expect :: proc(val, expected_val: T) -> T --- - +atomic_cxchg :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_cxchg_acq :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_cxchg_rel :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_cxchg_acqrel :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_cxchg_relaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_cxchg_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_cxchg_failacq :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_cxchg_acq_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_cxchg_acqrel_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- + +atomic_cxchgweak :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_cxchgweak_acq :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_cxchgweak_rel :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_cxchgweak_acqrel :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_cxchgweak_relaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_cxchgweak_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_cxchgweak_failacq :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_cxchgweak_acq_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_cxchgweak_acqrel_failrelaxed :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- // Constant type tests @@ -159,5 +169,5 @@ type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V -- type_field_index_of :: proc($T: typeid, $name: string) -> uintptr --- -type_equal_proc :: proc($T: typeid) -> (equal: proc "contextless" (rawptr, rawptr) -> bool) --- -type_hasher_proc :: proc($T: typeid) -> (hasher: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr) --- +type_equal_proc :: proc($T: typeid) -> (equal: proc "contextless" (rawptr, rawptr) -> bool) where type_is_comparable(T) --- +type_hasher_proc :: proc($T: typeid) -> (hasher: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr) where type_is_comparable(T) --- -- cgit v1.2.3 From 0659a11a1a42679af56e988bb63166b483a99c60 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 May 2021 18:24:31 +0200 Subject: PNG: Fix tRNS handling. --- core/image/png/png.odin | 34 ++++++++++++++++------------------ core/testing/runner.odin | 1 - 2 files changed, 16 insertions(+), 19 deletions(-) (limited to 'core') diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 5b18e6619..cfbeb43f5 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -1049,28 +1049,25 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c } for len(p) > 0 { - r := p[0]; + r := p[0]; alpha := u8(1); if seen_trns { if r == key { if seen_bkgd { - c := img.background.([3]u16); - r = u8(c[0]); + bc := img.background.([3]u16); + r = u8(bc[0]); } else { alpha = 0; // Keyed transparency } } if premultiply { - o[0] = r * alpha; - o[1] = r * alpha; - o[2] = r * alpha; + r *= alpha; } - } else { - o[0] = r; - o[1] = r; - o[2] = r; } + o[0] = r; + o[1] = r; + o[2] = r; if out_image_channels == 4 { o[3] = alpha * 255; @@ -1128,6 +1125,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c */ key = []u8{trns.data[1], trns.data[3], trns.data[5]}; } + for len(p) > 0 { r := p[0]; g := p[1]; @@ -1147,17 +1145,17 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c } } - if .alpha_premultiply in options || .blend_background in options { - o[0] = r * alpha; - o[1] = g * alpha; - o[2] = b * alpha; + if premultiply { + r *= alpha; + g *= alpha; + b *= alpha; } - } else { - o[0] = r; - o[1] = g; - o[2] = b; } + o[0] = r; + o[1] = g; + o[2] = b; + if out_image_channels == 4 { o[3] = alpha * 255; } diff --git a/core/testing/runner.odin b/core/testing/runner.odin index efeaa04f6..7691ef11e 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -3,7 +3,6 @@ package testing import "core:io" import "core:os" -import "core:strings" import "core:slice" reset_t :: proc(t: ^T) { -- cgit v1.2.3 From b845db16187dc8d609932a0e473e11f53ae31ef7 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 1 May 2021 18:26:51 +0100 Subject: Add prototypes for `intrinsics.fixed_point_*` --- core/intrinsics/intrinsics.odin | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'core') diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index 84d5b06b5..d767f5e47 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -31,6 +31,11 @@ overflow_add :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok --- overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok --- overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok --- +fixed_point_mul :: proc(lhs, rhs: $T, #const scale: uint) -> T where type_is_integer(T) --- +fixed_point_div :: proc(lhs, rhs: $T, #const scale: uint) -> T where type_is_integer(T) --- +fixed_point_mul_sat :: proc(lhs, rhs: $T, #const scale: uint) -> T where type_is_integer(T) --- +fixed_point_div_sat :: proc(lhs, rhs: $T, #const scale: uint) -> T where type_is_integer(T) --- + // Compiler Hints expect :: proc(val, expected_val: T) -> T --- -- cgit v1.2.3 From 433d742183eb4e4a9290c0b889bad6dc747dc06c Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 May 2021 20:39:00 +0200 Subject: Fix Paeth for bit depth < 8. --- core/image/png/png.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'core') diff --git a/core/image/png/png.odin b/core/image/png/png.odin index cfbeb43f5..41ea8e625 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -1362,7 +1362,7 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_ch dest[i] = (src[i] + paeth) & 255; } for k in 0..nk { - paeth := filter_paeth(dest[k], up[channels], up[k]); + paeth := filter_paeth(dest[k], up[channels+k], up[k]); dest[channels+k] = (src[channels+k] + paeth) & 255; } case: -- cgit v1.2.3 From 2ad8f99790dda7d021202b9d7830d24d80d4e331 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 May 2021 21:56:45 +0200 Subject: ZLIB level 0: LEN/NLEN = i16. --- core/compress/zlib/zlib.odin | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'core') diff --git a/core/compress/zlib/zlib.odin b/core/compress/zlib/zlib.odin index 5ce1f26ad..252470f7a 100644 --- a/core/compress/zlib/zlib.odin +++ b/core/compress/zlib/zlib.odin @@ -446,7 +446,7 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> final = compress.read_bits_lsb(z, 1); type = compress.read_bits_lsb(z, 2); - // log.debugf("Final: %v | Type: %v\n", final, type); + // fmt.printf("Final: %v | Type: %v\n", final, type); switch type { case 0: @@ -455,9 +455,13 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> // Discard bits until next byte boundary compress.discard_to_next_byte_lsb(z); - uncompressed_len := int(compress.read_bits_lsb(z, 16)); - length_check := int(compress.read_bits_lsb(z, 16)); - if uncompressed_len != ~length_check { + uncompressed_len := i16(compress.read_bits_lsb(z, 16)); + length_check := i16(compress.read_bits_lsb(z, 16)); + + // fmt.printf("LEN: %v, ~LEN: %v, NLEN: %v, ~NLEN: %v\n", uncompressed_len, ~uncompressed_len, length_check, ~length_check); + + + if ~uncompressed_len != length_check { return E_Deflate.Len_Nlen_Mismatch; } -- cgit v1.2.3 From 52d38ae42b15311102719d25b6f285c43af701bb Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 1 May 2021 22:54:27 +0100 Subject: Make the core:testing runner on windows run in a separate thread to handle crashes in more safe manner --- core/testing/runner.odin | 6 +- core/testing/runner_other.odin | 7 ++ core/testing/runner_windows.odin | 188 +++++++++++++++++++++++++++++++++++++++ core/testing/testing.odin | 7 +- src/main.cpp | 7 +- 5 files changed, 208 insertions(+), 7 deletions(-) create mode 100644 core/testing/runner_other.odin create mode 100644 core/testing/runner_windows.odin (limited to 'core') diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 7691ef11e..1552cbc12 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -55,11 +55,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { logf(t, "[Test: %s]", it.name); // TODO(bill): Catch panics - { - it.p(t); - } + run_internal_test(t, it); - if t.error_count != 0 { + if failed(t) { logf(t, "[%s : FAILURE]", it.name); } else { logf(t, "[%s : SUCCESS]", it.name); diff --git a/core/testing/runner_other.odin b/core/testing/runner_other.odin new file mode 100644 index 000000000..84d830512 --- /dev/null +++ b/core/testing/runner_other.odin @@ -0,0 +1,7 @@ +//+private +//+build !windows +package testing + +run_internal_test :: proc(t: ^T, it: Internal_Test) { + it.p(t); +} diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin new file mode 100644 index 000000000..4ab24f39c --- /dev/null +++ b/core/testing/runner_windows.odin @@ -0,0 +1,188 @@ +//+private +//+build windows +package testing + +import win32 "core:sys/windows" +import "core:runtime" +import "core:fmt" +import "intrinsics" + + +Sema :: struct { + count: i32, +} + +sema_reset :: proc "contextless" (s: ^Sema) { + intrinsics.atomic_store(&s.count, 0); +} +sema_wait :: proc "contextless" (s: ^Sema) { + for { + original_count := s.count; + for original_count == 0 { + win32.WaitOnAddress( + &s.count, + &original_count, + size_of(original_count), + win32.INFINITE, + ); + original_count = s.count; + } + if original_count == intrinsics.atomic_cxchg(&s.count, original_count-1, original_count) { + return; + } + } +} + +sema_post :: proc "contextless" (s: ^Sema, count := 1) { + intrinsics.atomic_add(&s.count, i32(count)); + if count == 1 { + win32.WakeByAddressSingle(&s.count); + } else { + win32.WakeByAddressAll(&s.count); + } +} + + +Thread_Proc :: #type proc(^Thread); + +MAX_USER_ARGUMENTS :: 8; + +Thread :: struct { + using specific: Thread_Os_Specific, + procedure: Thread_Proc, + + t: ^T, + it: Internal_Test, + success: bool, + + init_context: Maybe(runtime.Context), + + creation_allocator: runtime.Allocator, +} + +Thread_Os_Specific :: struct { + win32_thread: win32.HANDLE, + win32_thread_id: win32.DWORD, + done: bool, // see note in `is_done` +} + +thread_create :: proc(procedure: Thread_Proc) -> ^Thread { + __windows_thread_entry_proc :: proc "stdcall" (t_: rawptr) -> win32.DWORD { + t := (^Thread)(t_); + context = runtime.default_context(); + c := context; + if ic, ok := t.init_context.?; ok { + c = ic; + } + context = c; + + t.procedure(t); + + if t.init_context == nil { + if context.temp_allocator.data == &runtime.global_default_temp_allocator_data { + runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data); + } + } + + intrinsics.atomic_store(&t.done, true); + return 0; + } + + + thread := new(Thread); + if thread == nil { + return nil; + } + thread.creation_allocator = context.allocator; + + win32_thread_id: win32.DWORD; + win32_thread := win32.CreateThread(nil, 0, __windows_thread_entry_proc, thread, win32.CREATE_SUSPENDED, &win32_thread_id); + if win32_thread == nil { + free(thread, thread.creation_allocator); + return nil; + } + thread.procedure = procedure; + thread.win32_thread = win32_thread; + thread.win32_thread_id = win32_thread_id; + thread.init_context = context; + + return thread; +} + +thread_start :: proc "contextless" (thread: ^Thread) { + win32.ResumeThread(thread.win32_thread); +} + +thread_join_and_destroy :: proc(thread: ^Thread) { + if thread.win32_thread != win32.INVALID_HANDLE { + win32.WaitForSingleObject(thread.win32_thread, win32.INFINITE); + win32.CloseHandle(thread.win32_thread); + thread.win32_thread = win32.INVALID_HANDLE; + } + free(thread, thread.creation_allocator); +} + +thread_terminate :: proc "contextless" (thread: ^Thread, exit_code: int) { + win32.TerminateThread(thread.win32_thread, u32(exit_code)); +} + + + + +global_threaded_runner_semaphore: Sema; +global_exception_handler: rawptr; +global_current_thread: ^Thread; +global_current_t: ^T; + +run_internal_test :: proc(t: ^T, it: Internal_Test) { + thread := thread_create(proc(thread: ^Thread) { + exception_handler_proc :: proc "stdcall" (ExceptionInfo: ^win32.EXCEPTION_POINTERS) -> win32.LONG { + switch ExceptionInfo.ExceptionRecord.ExceptionCode { + case + win32.EXCEPTION_DATATYPE_MISALIGNMENT, + win32.EXCEPTION_BREAKPOINT, + win32.EXCEPTION_ACCESS_VIOLATION, + win32.EXCEPTION_ILLEGAL_INSTRUCTION, + win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED, + win32.EXCEPTION_STACK_OVERFLOW: + + sema_post(&global_threaded_runner_semaphore); + return win32.EXCEPTION_EXECUTE_HANDLER; + } + + return win32.EXCEPTION_CONTINUE_SEARCH; + } + global_exception_handler = win32.AddVectoredExceptionHandler(0, exception_handler_proc); + + context.assertion_failure_proc = proc(prefix, message: string, loc: runtime.Source_Code_Location) { + errorf(t=global_current_t, format="%s %s", args={prefix, message}, loc=loc); + intrinsics.debug_trap(); + }; + + thread.it.p(thread.t); + + thread.success = true; + sema_post(&global_threaded_runner_semaphore); + }); + + sema_reset(&global_threaded_runner_semaphore); + global_current_t = t; + + thread.t = t; + thread.it = it; + thread.success = false; + + thread_start(thread); + + sema_wait(&global_threaded_runner_semaphore); + thread_terminate(thread, int(!thread.success)); + thread_join_and_destroy(thread); + + win32.RemoveVectoredExceptionHandler(global_exception_handler); + + if !thread.success && t.error_count == 0 { + t.error_count += 1; + } + + return; +} diff --git a/core/testing/testing.odin b/core/testing/testing.odin index a431d8575..8a32ce7c8 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -29,12 +29,15 @@ T :: struct { error :: proc(t: ^T, args: ..any, loc := #caller_location) { - log(t=t, args=args, loc=loc); + fmt.wprintf(t.w, "%v: ", loc); + fmt.wprintln(t.w, ..args); t.error_count += 1; } errorf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) { - logf(t=t, format=format, args=args, loc=loc); + fmt.wprintf(t.w, "%v: ", loc); + fmt.wprintf(t.w, format, ..args); + fmt.wprintln(t.w); t.error_count += 1; } diff --git a/src/main.cpp b/src/main.cpp index 117c4e549..abc90d627 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1554,7 +1554,7 @@ void print_show_help(String const arg0, String const &command) { } else if (command == "check") { print_usage_line(1, "check parse and type check .odin file"); } else if (command == "test") { - print_usage_line(1, "test build ands runs 'test_*' procedures in the initial package"); + print_usage_line(1, "test build ands runs procedures with the attribute @(test) in the initial package"); } else if (command == "query") { print_usage_line(1, "query [experimental] parse, type check, and output a .json file containing information about the program"); } else if (command == "doc") { @@ -1662,6 +1662,11 @@ void print_show_help(String const arg0, String const &command) { print_usage_line(3, "-build-mode:shared Build as a dynamically linked library"); print_usage_line(3, "-build-mode:obj Build as an object file"); print_usage_line(3, "-build-mode:object Build as an object file"); + print_usage_line(3, "-build-mode:assembly Build as an object file"); + print_usage_line(3, "-build-mode:assembler Build as an assembly file"); + print_usage_line(3, "-build-mode:asm Build as an assembly file"); + print_usage_line(3, "-build-mode:llvm-ir Build as an LLVM IR file"); + print_usage_line(3, "-build-mode:llvm Build as an LLVM IR file"); print_usage_line(0, ""); } -- cgit v1.2.3 From 364e6c9573f35cb32be50cabb898ceb2afa57b88 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 1 May 2021 22:58:13 +0100 Subject: Move comment --- core/testing/runner.odin | 1 - core/testing/runner_other.odin | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) (limited to 'core') diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 1552cbc12..e3286988c 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -54,7 +54,6 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { logf(t, "[Test: %s]", it.name); - // TODO(bill): Catch panics run_internal_test(t, it); if failed(t) { diff --git a/core/testing/runner_other.odin b/core/testing/runner_other.odin index 84d830512..0bd95e10a 100644 --- a/core/testing/runner_other.odin +++ b/core/testing/runner_other.odin @@ -3,5 +3,6 @@ package testing run_internal_test :: proc(t: ^T, it: Internal_Test) { + // TODO(bill): Catch panics on other platforms it.p(t); } -- cgit v1.2.3 From cf0bf1a7cb395cb334cc456d30af3224fa607ca0 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 1 May 2021 23:06:14 +0100 Subject: Add `testing.fail_now` --- core/testing/runner_windows.odin | 6 +++++- core/testing/testing.odin | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'core') diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin index 4ab24f39c..4b600218a 100644 --- a/core/testing/runner_windows.odin +++ b/core/testing/runner_windows.odin @@ -156,7 +156,7 @@ run_internal_test :: proc(t: ^T, it: Internal_Test) { context.assertion_failure_proc = proc(prefix, message: string, loc: runtime.Source_Code_Location) { errorf(t=global_current_t, format="%s %s", args={prefix, message}, loc=loc); - intrinsics.debug_trap(); + intrinsics.trap(); }; thread.it.p(thread.t); @@ -168,6 +168,10 @@ run_internal_test :: proc(t: ^T, it: Internal_Test) { sema_reset(&global_threaded_runner_semaphore); global_current_t = t; + t._fail_now = proc() -> ! { + intrinsics.trap(); + }; + thread.t = t; thread.it = it; thread.success = false; diff --git a/core/testing/testing.odin b/core/testing/testing.odin index 8a32ce7c8..ec47ca4d4 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -25,6 +25,8 @@ T :: struct { w: io.Writer, cleanups: [dynamic]Internal_Cleanup, + + _fail_now: proc() -> !, } @@ -46,6 +48,13 @@ fail :: proc(t: ^T) { t.error_count += 1; } +fail_now :: proc(t: ^T) { + fail(t); + if t._fail_now != nil { + t._fail_now(); + } +} + failed :: proc(t: ^T) -> bool { return t.error_count != 0; } -- cgit v1.2.3 From 9854dbe8897b3722e571a23b699c234cfc0baa83 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 1 May 2021 23:14:14 +0100 Subject: Remove unused import --- core/testing/runner_windows.odin | 1 - 1 file changed, 1 deletion(-) (limited to 'core') diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin index 4b600218a..d8633f703 100644 --- a/core/testing/runner_windows.odin +++ b/core/testing/runner_windows.odin @@ -4,7 +4,6 @@ package testing import win32 "core:sys/windows" import "core:runtime" -import "core:fmt" import "intrinsics" -- cgit v1.2.3 From 2451014b6e1758355449b78970bf5f84ec9b96d4 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 May 2021 13:23:57 +0200 Subject: datetime_to_time's ok should default to true. --- core/time/time.odin | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'core') diff --git a/core/time/time.odin b/core/time/time.odin index 00d7e529a..c75549b17 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -262,19 +262,18 @@ datetime_to_time :: proc(year, month, day, hour, minute, second: int, nsec := in return; } + ok = true; + _y := year - 1970; _m := month - 1; _d := day - 1; - if _m < 0 || _m > 11 { + if month < 1 || month > 12 { _m %= 12; ok = false; } - if _d < 0 || _m > 30 { + if day < 1 || day > 31 { _d %= 31; ok = false; } - if _m < 0 || _m > 11 { - _m %= 12; ok = false; - } s := i64(0); div, mod := divmod(_y, 400); -- cgit v1.2.3 From 7d534769d6437cb63eeb4718bc8ed36ffef937f9 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 May 2021 20:38:30 +0200 Subject: Add new PNG post processing options. --- core/compress/common.odin | 2 +- core/image/common.odin | 129 +++++++++++++++++++++++++++++++++++++++++----- core/image/png/png.odin | 29 ++++++++++- 3 files changed, 143 insertions(+), 17 deletions(-) (limited to 'core') diff --git a/core/compress/common.odin b/core/compress/common.odin index 11b5b74b3..ffdc2d208 100644 --- a/core/compress/common.odin +++ b/core/compress/common.odin @@ -19,7 +19,7 @@ Error :: union { This is here because png.load will return a this type of error union, as it may involve an I/O error, a Deflate error, etc. */ - image.PNG_Error, + image.Error, } General_Error :: enum { diff --git a/core/image/common.odin b/core/image/common.odin index d05feabaa..6630dfd5b 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -1,6 +1,9 @@ package image import "core:bytes" +import "core:mem" + +import "core:fmt" Image :: struct { width: int, @@ -17,49 +20,69 @@ Image :: struct { sidecar: any, } +/* + IMPORTANT: `.do_not_expand_*` options currently skip handling of the `alpha_*` options, + therefore Gray+Alpha will be returned as such even if you add `.alpha_drop_if_present`, + and `.alpha_add_if_missing` and keyed transparency will likewise be ignored. + + The same goes for indexed images. This will be remedied in a near future update. +*/ + /* Image_Option: `.info` - This option behaves as `return_ihdr` and `do_not_decompress_image` and can be used + This option behaves as `.return_ihdr` and `.do_not_decompress_image` and can be used to gather an image's dimensions and color information. `.return_header` Fill out img.sidecar.header with the image's format-specific header struct. - If we only care about the image specs, we can set `return_header` + - `do_not_decompress_image`, or `.info`, which works as if both of these were set. + If we only care about the image specs, we can set `.return_header` + + `.do_not_decompress_image`, or `.info`, which works as if both of these were set. `.return_metadata` Returns all chunks not needed to decode the data. - It also returns the header as if `.return_header` is set. + It also returns the header as if `.return_header` was set. - `do_not_decompress_image` + `.do_not_decompress_image` Skip decompressing IDAT chunk, defiltering and the rest. - `alpha_add_if_missing` + `.do_not_expand_grayscale` + Do not turn grayscale (+ Alpha) images into RGB(A). + Returns just the 1 or 2 channels present, although 1, 2 and 4 bit are still scaled to 8-bit. + + `.do_not_expand_indexed` + Do not turn indexed (+ Alpha) images into RGB(A). + Returns just the 1 or 2 (with `tRNS`) channels present. + Make sure to use `return_metadata` to also return the palette chunk so you can recolor it yourself. + + `.do_not_expand_channels` + Applies both `.do_not_expand_grayscale` and `.do_not_expand_indexed`. + + `.alpha_add_if_missing` If the image has no alpha channel, it'll add one set to max(type). Turns RGB into RGBA and Gray into Gray+Alpha - `alpha_drop_if_present` + `.alpha_drop_if_present` If the image has an alpha channel, drop it. - You may want to use `alpha_premultiply` in this case. + You may want to use `.alpha_premultiply` in this case. NOTE: For PNG, this also skips handling of the tRNS chunk, if present, unless you select `alpha_premultiply`. In this case it'll premultiply the specified pixels in question only, as the others are implicitly fully opaque. - `alpha_premultiply` + `.alpha_premultiply` If the image has an alpha channel, returns image data as follows: RGB *= A, Gray = Gray *= A - `blend_background` + `.blend_background` If a bKGD chunk is present in a PNG, we normally just set `img.background` with its value and leave it up to the application to decide how to display the image, as per the PNG specification. - With `blend_background` selected, we blend the image against the background + With `.blend_background` selected, we blend the image against the background color. As this negates the use for an alpha channel, we'll drop it _unless_ - you also specify `alpha_add_if_missing`. + you also specify `.alpha_add_if_missing`. Options that don't apply to an image format will be ignored by their loader. */ @@ -73,10 +96,14 @@ Option :: enum { alpha_drop_if_present, alpha_premultiply, blend_background, + // Unimplemented + do_not_expand_grayscale, + do_not_expand_indexed, + do_not_expand_channels, } Options :: distinct bit_set[Option]; -PNG_Error :: enum { +Error :: enum { Invalid_PNG_Signature, IHDR_Not_First_Chunk, IHDR_Corrupt, @@ -93,9 +120,10 @@ PNG_Error :: enum { Invalid_Color_Bit_Depth_Combo, Unknown_Filter_Method, Unknown_Interlace_Method, + Requested_Channel_Not_Present, + Post_Processing_Error, } - /* Functions to help with image buffer calculations */ @@ -104,4 +132,77 @@ compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height; return; +} + +/* + For when you have an RGB(A) image, but want a particular channel. +*/ + +Channel :: enum u8 { + R = 1, + G = 2, + B = 3, + A = 4, +} + +return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok: bool) { + + ok = false; + t: bytes.Buffer; + + idx := int(channel); + + if idx > img.channels { + return {}, false; + } + + if img.channels == 2 && idx == 4 { + // Alpha requested, which in a two channel image is index 2: G. + idx = 2; + } + + switch(img.depth) { + case 8: + buffer_size := compute_buffer_size(img.width, img.height, 1, 8); + t = bytes.Buffer{}; + resize(&t.buf, buffer_size); + + i := bytes.buffer_to_bytes(&img.pixels); + o := bytes.buffer_to_bytes(&t); + + for len(i) > 0 { + o[0] = i[idx]; + i = i[img.channels:]; + o = o[1:]; + } + case 16: + buffer_size := compute_buffer_size(img.width, img.height, 2, 8); + t = bytes.Buffer{}; + resize(&t.buf, buffer_size); + + i := mem.slice_data_cast([]u16, img.pixels.buf[:]); + o := mem.slice_data_cast([]u16, t.buf[:]); + + for len(i) > 0 { + o[0] = i[idx]; + i = i[img.channels:]; + o = o[1:]; + } + case 1, 2, 4: + // We shouldn't see this case, as the loader already turns these into 8-bit. + return {}, false; + } + + res = new(Image); + res.width = img.width; + res.height = img.height; + res.channels = 1; + res.depth = img.depth; + res.pixels = t; + res.background = img.background; + res.sidecar = img.sidecar; + + fmt.println(t); + + return res, true; } \ No newline at end of file diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 41ea8e625..912b31300 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -14,7 +14,7 @@ import "core:intrinsics" Error :: compress.Error; E_General :: compress.General_Error; -E_PNG :: image.PNG_Error; +E_PNG :: image.Error; E_Deflate :: compress.Deflate_Error; is_kind :: compress.is_kind; @@ -382,13 +382,17 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c options := options; if .info in options { options |= {.return_metadata, .do_not_decompress_image}; - options ~= {.info}; + options -= {.info}; } if .alpha_drop_if_present in options && .alpha_add_if_missing in options { return {}, E_General.Incompatible_Options; } + if .do_not_expand_channels in options { + options |= {.do_not_expand_grayscale, .do_not_expand_indexed}; + } + if img == nil { img = new(Image); } @@ -723,6 +727,14 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c will become the default. */ + if .Paletted in header.color_type && .do_not_expand_indexed in options { + return img, E_General.OK; + } + if .Color not_in header.color_type && .do_not_expand_grayscale in options { + return img, E_General.OK; + } + + raw_image_channels := img.channels; out_image_channels := 3; @@ -1218,6 +1230,19 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c unreachable("We should never see bit depths other than 8, 16 and 'Paletted' here."); } + // TODO: Rather than first expanding to RGB(A) and then dropping channels, give these their own path. + if .do_not_expand_grayscale in options && .Color not_in info.header.color_type { + + single, single_ok := image.return_single_channel(img, .R); + if single_ok { + destroy(img); + img = single; + } else { + destroy(single); + return img, E_PNG.Post_Processing_Error; + } + } + return img, E_General.OK; } -- cgit v1.2.3 From 3160a6a12c5437413afcae70a893ec7debb91893 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 May 2021 21:11:06 +0200 Subject: Don't need other path for grayscale output. --- core/image/png/png.odin | 13 ------------- 1 file changed, 13 deletions(-) (limited to 'core') diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 912b31300..716185c5f 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -1230,19 +1230,6 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c unreachable("We should never see bit depths other than 8, 16 and 'Paletted' here."); } - // TODO: Rather than first expanding to RGB(A) and then dropping channels, give these their own path. - if .do_not_expand_grayscale in options && .Color not_in info.header.color_type { - - single, single_ok := image.return_single_channel(img, .R); - if single_ok { - destroy(img); - img = single; - } else { - destroy(single); - return img, E_PNG.Post_Processing_Error; - } - } - return img, E_General.OK; } -- cgit v1.2.3 From 448f834b28afa4bb73e82f76c4cfec9c0b45d42e Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 3 May 2021 01:23:03 +0200 Subject: Remove debug print in image helper. --- core/image/common.odin | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'core') diff --git a/core/image/common.odin b/core/image/common.odin index 6630dfd5b..e92060ed2 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -3,8 +3,6 @@ package image import "core:bytes" import "core:mem" -import "core:fmt" - Image :: struct { width: int, height: int, @@ -202,7 +200,5 @@ return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok res.background = img.background; res.sidecar = img.sidecar; - fmt.println(t); - return res, true; -} \ No newline at end of file +} -- cgit v1.2.3 From 518ecaf9c909bf2fd372fd758a5075f603b07d75 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 May 2021 13:17:16 +0100 Subject: Allow `union`s to be comparable if all their variants are comparable --- core/runtime/core.odin | 3 +++ src/llvm_backend.cpp | 71 +++++++++++++++++++++++++++++++++++++++++++++----- src/types.cpp | 18 ++++++++++++- 3 files changed, 85 insertions(+), 7 deletions(-) (limited to 'core') diff --git a/core/runtime/core.odin b/core/runtime/core.odin index 5f016579f..cb526ed2d 100644 --- a/core/runtime/core.odin +++ b/core/runtime/core.odin @@ -121,6 +121,9 @@ Type_Info_Union :: struct { variants: []^Type_Info, tag_offset: uintptr, tag_type: ^Type_Info, + + equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set + custom_align: bool, no_nil: bool, maybe: bool, diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index de202658a..29a181f94 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -10259,7 +10259,7 @@ lbValue lb_get_equal_proc_for_type(lbModule *m, Type *type) { lb_start_block(p, block_diff_ptr); - if (type->kind == Type_Struct) { + if (type->kind == Type_Struct) { type_set_offsets(type); lbBlock *block_false = lb_create_block(p, "bfalse"); @@ -10285,6 +10285,56 @@ lbValue lb_get_equal_proc_for_type(lbModule *m, Type *type) { lb_start_block(p, block_false); LLVMBuildRet(p->builder, LLVMConstInt(lb_type(m, t_bool), 0, false)); + } else if (type->kind == Type_Union) { + if (is_type_union_maybe_pointer(type)) { + Type *v = type->Union.variants[0]; + Type *pv = alloc_type_pointer(v); + + lbValue left = lb_emit_load(p, lb_emit_conv(p, lhs, pv)); + lbValue right = lb_emit_load(p, lb_emit_conv(p, rhs, pv)); + + lbValue ok = lb_emit_comp(p, Token_CmpEq, left, right); + ok = lb_emit_conv(p, ok, t_bool); + LLVMBuildRet(p->builder, ok.value); + } else { + lbBlock *block_false = lb_create_block(p, "bfalse"); + lbBlock *block_switch = lb_create_block(p, "bswitch"); + + lbValue left_tag = lb_emit_load(p, lb_emit_union_tag_ptr(p, lhs)); + lbValue right_tag = lb_emit_load(p, lb_emit_union_tag_ptr(p, rhs)); + + lbValue tag_eq = lb_emit_comp(p, Token_CmpEq, left_tag, right_tag); + lb_emit_if(p, tag_eq, block_switch, block_false); + + lb_start_block(p, block_switch); + LLVMValueRef v_switch = LLVMBuildSwitch(p->builder, left_tag.value, block_false->block, cast(unsigned)type->Union.variants.count); + + + for_array(i, type->Union.variants) { + lbBlock *case_block = lb_create_block(p, "bcase"); + lb_start_block(p, case_block); + + Type *v = type->Union.variants[i]; + lbValue tag = lb_const_union_tag(p->module, type, v); + + Type *vp = alloc_type_pointer(v); + + lbValue left = lb_emit_load(p, lb_emit_conv(p, lhs, vp)); + lbValue right = lb_emit_load(p, lb_emit_conv(p, rhs, vp)); + lbValue ok = lb_emit_comp(p, Token_CmpEq, left, right); + ok = lb_emit_conv(p, ok, t_bool); + + LLVMBuildRet(p->builder, ok.value); + + + LLVMAddCase(v_switch, tag.value, case_block->block); + } + + lb_start_block(p, block_false); + + LLVMBuildRet(p->builder, LLVMConstInt(lb_type(m, t_bool), 0, false)); + } + } else { lbValue left = lb_emit_load(p, lhs); lbValue right = lb_emit_load(p, rhs); @@ -10565,7 +10615,7 @@ lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left, lbValue ri } - if (is_type_struct(a) && is_type_comparable(a)) { + if ((is_type_struct(a) || is_type_union(b)) && is_type_comparable(a)) { lbValue left_ptr = lb_address_from_load_or_generate_local(p, left); lbValue right_ptr = lb_address_from_load_or_generate_local(p, right); lbValue res = {}; @@ -13467,7 +13517,7 @@ void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup type_info da tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_union_ptr); { - LLVMValueRef vals[6] = {}; + LLVMValueRef vals[7] = {}; isize variant_count = gb_max(0, t->Union.variants.count); lbValue memory_types = lb_type_info_member_types_offset(p, variant_count); @@ -13496,10 +13546,19 @@ void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup type_info da vals[2] = LLVMConstNull(lb_type(m, t_type_info_ptr)); } - vals[3] = lb_const_bool(m, t_bool, t->Union.custom_align != 0).value; - vals[4] = lb_const_bool(m, t_bool, t->Union.no_nil).value; - vals[5] = lb_const_bool(m, t_bool, t->Union.maybe).value; + if (is_type_comparable(t) && !is_type_simple_compare(t)) { + vals[3] = lb_get_equal_proc_for_type(m, t).value; + } + + vals[4] = lb_const_bool(m, t_bool, t->Union.custom_align != 0).value; + vals[5] = lb_const_bool(m, t_bool, t->Union.no_nil).value; + vals[6] = lb_const_bool(m, t_bool, t->Union.maybe).value; + for (isize i = 0; i < gb_count_of(vals); i++) { + if (vals[i] == nullptr) { + vals[i] = LLVMConstNull(lb_type(m, get_struct_field_type(tag.type, i))); + } + } lbValue res = {}; res.type = type_deref(tag.type); diff --git a/src/types.cpp b/src/types.cpp index 7b5b81c43..5e4370f8a 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1544,6 +1544,9 @@ bool is_type_valid_for_keys(Type *t) { if (is_type_untyped(t)) { return false; } + if (t->kind == Type_Union) { + return false; + } return is_type_comparable(t); } @@ -1915,6 +1918,18 @@ bool is_type_comparable(Type *t) { } } return true; + + case Type_Union: + if (type_size_of(t) == 0) { + return false; + } + for_array(i, t->Union.variants) { + Type *v = t->Union.variants[i]; + if (!is_type_comparable(v)) { + return false; + } + } + return true; } return false; } @@ -1959,7 +1974,8 @@ bool is_type_simple_compare(Type *t) { return false; } } - return true; + // make it dumb on purpose + return t->Union.variants.count == 1; case Type_SimdVector: return is_type_simple_compare(t->SimdVector.elem); -- cgit v1.2.3 From 59b3c472ca1ea4e56728eb81c3c8b595a4b1bcdd Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 3 May 2021 15:08:34 +0200 Subject: Convert `core:compress` and `core:image` error checks to new union comparison. No more need for `is_kind(err, Error_Value)`, just test err == Error_Value. --- core/compress/common.odin | 6 ------ core/compress/gzip/example.odin | 6 +++--- core/compress/gzip/gzip.odin | 3 +-- core/compress/zlib/example.odin | 2 +- core/compress/zlib/zlib.odin | 29 ++++++++++++++--------------- core/image/png/example.odin | 4 ++-- core/image/png/helpers.odin | 8 ++++---- core/image/png/png.odin | 21 ++++++++++----------- 8 files changed, 35 insertions(+), 44 deletions(-) (limited to 'core') diff --git a/core/compress/common.odin b/core/compress/common.odin index ffdc2d208..de1894f61 100644 --- a/core/compress/common.odin +++ b/core/compress/common.odin @@ -3,12 +3,6 @@ package compress import "core:io" import "core:image" -// Error helper, e.g. is_kind(err, General_Error.OK); -is_kind :: proc(u: $U, x: $V) -> bool { - v, ok := u.(V); - return ok && v == x; -} - Error :: union { General_Error, Deflate_Error, diff --git a/core/compress/gzip/example.odin b/core/compress/gzip/example.odin index 8e0f53cf8..344521489 100644 --- a/core/compress/gzip/example.odin +++ b/core/compress/gzip/example.odin @@ -35,7 +35,7 @@ main :: proc() { if len(args) < 2 { stderr("No input file specified.\n"); err := gzip.load(TEST, &buf); - if gzip.is_kind(err, gzip.E_General.OK) { + if err != E_General.OK { stdout("Displaying test vector: "); stdout(bytes.buffer_to_string(&buf)); stdout("\n"); @@ -54,8 +54,8 @@ main :: proc() { } else { err = gzip.load(file, &buf); } - if !gzip.is_kind(err, gzip.E_General.OK) { - if gzip.is_kind(err, gzip.E_General.File_Not_Found) { + if err != gzip.E_General.OK { + if err != E_General.File_Not_Found { stderr("File not found: "); stderr(file); stderr("\n"); diff --git a/core/compress/gzip/gzip.odin b/core/compress/gzip/gzip.odin index f86d3ddce..2284953d1 100644 --- a/core/compress/gzip/gzip.odin +++ b/core/compress/gzip/gzip.odin @@ -94,7 +94,6 @@ E_General :: compress.General_Error; E_GZIP :: compress.GZIP_Error; E_ZLIB :: compress.ZLIB_Error; E_Deflate :: compress.Deflate_Error; -is_kind :: compress.is_kind; load_from_slice :: proc(slice: []u8, buf: ^bytes.Buffer, allocator := context.allocator) -> (err: Error) { @@ -278,7 +277,7 @@ load_from_stream :: proc(stream: io.Stream, buf: ^bytes.Buffer, allocator := con // fmt.printf("ZLIB returned: %v\n", zlib_error); - if !is_kind(zlib_error, E_General.OK) || zlib_error == nil { + if zlib_error != E_General.OK || zlib_error == nil { return zlib_error; } diff --git a/core/compress/zlib/example.odin b/core/compress/zlib/example.odin index 0906d7eef..65b78c70c 100644 --- a/core/compress/zlib/example.odin +++ b/core/compress/zlib/example.odin @@ -33,7 +33,7 @@ main :: proc() { err := zlib.inflate(ODIN_DEMO, &buf); defer bytes.buffer_destroy(&buf); - if !zlib.is_kind(err, zlib.E_General.OK) { + if err != zlib.E_General.OK { fmt.printf("\nError: %v\n", err); } s := bytes.buffer_to_string(&buf); diff --git a/core/compress/zlib/zlib.odin b/core/compress/zlib/zlib.odin index 252470f7a..c5097a320 100644 --- a/core/compress/zlib/zlib.odin +++ b/core/compress/zlib/zlib.odin @@ -8,7 +8,7 @@ import "core:bytes" import "core:hash" /* zlib.inflate decompresses a ZLIB stream passed in as a []u8 or io.Stream. - Returns: Error. You can use zlib.is_kind or compress.is_kind to easily test for OK. + Returns: Error. */ Context :: compress.Context; @@ -34,7 +34,6 @@ Error :: compress.Error; E_General :: compress.General_Error; E_ZLIB :: compress.ZLIB_Error; E_Deflate :: compress.Deflate_Error; -is_kind :: compress.is_kind; DEFLATE_MAX_CHUNK_SIZE :: 65535; DEFLATE_MAX_LITERAL_SIZE :: 65535; @@ -256,7 +255,7 @@ decode_huffman :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err: Error) # parse_huffman_block :: proc(z: ^Context, z_repeat, z_offset: ^Huffman_Table) -> (err: Error) #no_bounds_check { #no_bounds_check for { value, e := decode_huffman(z, z_repeat); - if !is_kind(e, E_General.OK) { + if e != E_General.OK { return err; } if value < 256 { @@ -277,7 +276,7 @@ parse_huffman_block :: proc(z: ^Context, z_repeat, z_offset: ^Huffman_Table) -> } value, e = decode_huffman(z, z_offset); - if !is_kind(e, E_General.OK) { + if e != E_General.OK { return E_Deflate.Bad_Huffman_Code; } @@ -390,7 +389,7 @@ inflate_from_stream :: proc(using ctx: ^Context, raw := false, allocator := cont // Parse ZLIB stream without header. err = inflate_raw(ctx); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return err; } @@ -418,15 +417,15 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> codelength_ht: ^Huffman_Table; z_repeat, err = allocate_huffman_table(allocator=context.allocator); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return err; } z_offset, err = allocate_huffman_table(allocator=context.allocator); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return err; } codelength_ht, err = allocate_huffman_table(allocator=context.allocator); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return err; } defer free(z_repeat); @@ -482,11 +481,11 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> if type == 1 { // Use fixed code lengths. err = build_huffman(z_repeat, Z_FIXED_LENGTH[:]); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return err; } err = build_huffman(z_offset, Z_FIXED_DIST[:]); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return err; } } else { @@ -507,7 +506,7 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> codelength_sizes[Z_LENGTH_DEZIGZAG[i]] = u8(s); } err = build_huffman(codelength_ht, codelength_sizes[:]); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return err; } @@ -516,7 +515,7 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> for n < ntot { c, err = decode_huffman(z, codelength_ht); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return err; } @@ -560,18 +559,18 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> } err = build_huffman(z_repeat, lencodes[:hlit]); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return err; } err = build_huffman(z_offset, lencodes[hlit:ntot]); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return err; } } err = parse_huffman_block(z, z_repeat, z_offset); // log.debugf("Err: %v | Final: %v | Type: %v\n", err, final, type); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return err; } } diff --git a/core/image/png/example.odin b/core/image/png/example.odin index be6cdd7d7..072f88d6b 100644 --- a/core/image/png/example.odin +++ b/core/image/png/example.odin @@ -23,7 +23,7 @@ main :: proc() { img, err = png.load(file, options); defer png.destroy(img); - if !png.is_kind(err, png.E_General.OK) { + if err != png.E_General.OK { fmt.printf("Trying to read PNG file %v returned %v\n", file, err); } else { v: png.Info; @@ -120,7 +120,7 @@ main :: proc() { } } - if is_kind(err, E_General.OK) && .do_not_decompress_image not_in options && .info not_in options { + if err == E_General.OK && .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 { diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index 9bd5f55b2..eb0d2f827 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -107,7 +107,7 @@ text :: proc(c: Chunk) -> (res: Text, ok: bool) { buf: bytes.Buffer; zlib_error := zlib.inflate_from_byte_array(fields[2], &buf); defer bytes.buffer_destroy(&buf); - if !is_kind(zlib_error, E_General.OK) { + if zlib_error != E_General.OK { ok = false; return; } @@ -161,7 +161,7 @@ text :: proc(c: Chunk) -> (res: Text, ok: bool) { buf: bytes.Buffer; zlib_error := zlib.inflate_from_byte_array(rest, &buf); defer bytes.buffer_destroy(&buf); - if !is_kind(zlib_error, E_General.OK) { + if zlib_error != E_General.OK { ok = false; return; } @@ -200,7 +200,7 @@ iccp :: proc(c: Chunk) -> (res: iCCP, ok: bool) { // Set up ZLIB context and decompress iCCP payload buf: bytes.Buffer; zlib_error := zlib.inflate_from_byte_array(fields[2], &buf); - if !is_kind(zlib_error, E_General.OK) { + if zlib_error != E_General.OK { bytes.buffer_destroy(&buf); ok = false; return; } @@ -498,7 +498,7 @@ when false { err = zlib.write_zlib_stream_from_memory(&ctx); b: []u8; - if is_kind(err, E_General, E_General.OK) { + if err == E_General.OK { b = ctx.out_buf[:]; } else { return err; diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 716185c5f..74f287df8 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -16,7 +16,6 @@ Error :: compress.Error; E_General :: compress.General_Error; E_PNG :: image.Error; E_Deflate :: compress.Deflate_Error; -is_kind :: compress.is_kind; Image :: image.Image; Options :: image.Options; @@ -274,7 +273,7 @@ read_chunk :: proc(ctx: ^compress.Context) -> (Chunk, Error) { read_header :: proc(ctx: ^compress.Context) -> (IHDR, Error) { c, e := read_chunk(ctx); - if !is_kind(e, E_General.OK) { + if e != E_General.OK { return {}, e; } @@ -454,7 +453,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c seen_ihdr = true; header, err = read_header(&ctx); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return img, err; } @@ -507,7 +506,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c } c, err = read_chunk(&ctx); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return img, err; } @@ -542,7 +541,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c next := ch.type; for next == .IDAT { c, err = read_chunk(&ctx); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return img, err; } @@ -562,7 +561,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c seen_idat = true; case .IEND: c, err = read_chunk(&ctx); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return img, err; } seen_iend = true; @@ -571,7 +570,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c // TODO: Make sure that 16-bit bKGD + tRNS chunks return u16 instead of u16be c, err = read_chunk(&ctx); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return img, err; } seen_bkgd = true; @@ -606,7 +605,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c } case .tRNS: c, err = read_chunk(&ctx); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return img, err; } @@ -648,7 +647,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c case: // Unhandled type c, err = read_chunk(&ctx); - if !is_kind(err, E_General.OK) { + if err != E_General.OK { return img, err; } if .return_metadata in options { @@ -676,7 +675,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c zlib_error := zlib.inflate(idat, &buf); defer bytes.buffer_destroy(&buf); - if !is_kind(zlib_error, E_General.OK) { + if zlib_error != E_General.OK { return {}, zlib_error; } else { /* @@ -713,7 +712,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c as metadata, and set it instead to the raw number of channels. */ defilter_error := defilter(img, &buf, &header, options); - if !is_kind(defilter_error, E_General.OK) { + if defilter_error != E_General.OK { bytes.buffer_destroy(&img.pixels); return {}, defilter_error; } -- cgit v1.2.3 From 9a39ce6b75c4a459a1ee0df664cf48eb7b1d0567 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 3 May 2021 15:38:43 +0200 Subject: Change General_Error.OK to nil --- core/compress/common.odin | 1 - core/compress/gzip/example.odin | 4 ++-- core/compress/gzip/gzip.odin | 4 ++-- core/compress/zlib/example.odin | 2 +- core/compress/zlib/zlib.odin | 42 ++++++++++++++++++++--------------------- core/image/png/example.odin | 4 ++-- core/image/png/helpers.odin | 10 +++++----- core/image/png/png.odin | 40 +++++++++++++++++++-------------------- 8 files changed, 53 insertions(+), 54 deletions(-) (limited to 'core') diff --git a/core/compress/common.odin b/core/compress/common.odin index de1894f61..a0e092643 100644 --- a/core/compress/common.odin +++ b/core/compress/common.odin @@ -17,7 +17,6 @@ Error :: union { } General_Error :: enum { - OK = 0, File_Not_Found, Cannot_Open_File, File_Too_Short, diff --git a/core/compress/gzip/example.odin b/core/compress/gzip/example.odin index 344521489..54576c380 100644 --- a/core/compress/gzip/example.odin +++ b/core/compress/gzip/example.odin @@ -35,7 +35,7 @@ main :: proc() { if len(args) < 2 { stderr("No input file specified.\n"); err := gzip.load(TEST, &buf); - if err != E_General.OK { + if err != nil { stdout("Displaying test vector: "); stdout(bytes.buffer_to_string(&buf)); stdout("\n"); @@ -54,7 +54,7 @@ main :: proc() { } else { err = gzip.load(file, &buf); } - if err != gzip.E_General.OK { + if err != nil { if err != E_General.File_Not_Found { stderr("File not found: "); stderr(file); diff --git a/core/compress/gzip/gzip.odin b/core/compress/gzip/gzip.odin index 2284953d1..2b5e513c7 100644 --- a/core/compress/gzip/gzip.odin +++ b/core/compress/gzip/gzip.odin @@ -277,7 +277,7 @@ load_from_stream :: proc(stream: io.Stream, buf: ^bytes.Buffer, allocator := con // fmt.printf("ZLIB returned: %v\n", zlib_error); - if zlib_error != E_General.OK || zlib_error == nil { + if zlib_error != nil { return zlib_error; } @@ -307,7 +307,7 @@ load_from_stream :: proc(stream: io.Stream, buf: ^bytes.Buffer, allocator := con if len(payload) != payload_len { return E_GZIP.Payload_Length_Invalid; } - return E_General.OK; + return nil; } load :: proc{load_from_file, load_from_slice, load_from_stream}; diff --git a/core/compress/zlib/example.odin b/core/compress/zlib/example.odin index 65b78c70c..9af61e4b3 100644 --- a/core/compress/zlib/example.odin +++ b/core/compress/zlib/example.odin @@ -33,7 +33,7 @@ main :: proc() { err := zlib.inflate(ODIN_DEMO, &buf); defer bytes.buffer_destroy(&buf); - if err != zlib.E_General.OK { + if err != nil { fmt.printf("\nError: %v\n", err); } s := bytes.buffer_to_string(&buf); diff --git a/core/compress/zlib/zlib.odin b/core/compress/zlib/zlib.odin index c5097a320..bc19c37ef 100644 --- a/core/compress/zlib/zlib.odin +++ b/core/compress/zlib/zlib.odin @@ -135,7 +135,7 @@ write_byte :: #force_inline proc(z: ^Context, c: u8) -> (err: io.Error) #no_boun allocate_huffman_table :: proc(allocator := context.allocator) -> (z: ^Huffman_Table, err: Error) { z = new(Huffman_Table, allocator); - return z, E_General.OK; + return z, nil; } build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) { @@ -193,13 +193,13 @@ build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) { next_code[v] += 1; } } - return E_General.OK; + return nil; } decode_huffman_slowpath :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check { r = 0; - err = E_General.OK; + err = nil; k: int; s: u8; @@ -229,7 +229,7 @@ decode_huffman_slowpath :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err: compress.consume_bits_lsb(z, s); r = t.value[b]; - return r, E_General.OK; + return r, nil; } decode_huffman :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check { @@ -247,7 +247,7 @@ decode_huffman :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err: Error) # if b != 0 { s := u8(b >> ZFAST_BITS); compress.consume_bits_lsb(z, s); - return b & 511, E_General.OK; + return b & 511, nil; } return decode_huffman_slowpath(z, t); } @@ -255,7 +255,7 @@ decode_huffman :: proc(z: ^Context, t: ^Huffman_Table) -> (r: u16, err: Error) # parse_huffman_block :: proc(z: ^Context, z_repeat, z_offset: ^Huffman_Table) -> (err: Error) #no_bounds_check { #no_bounds_check for { value, e := decode_huffman(z, z_repeat); - if e != E_General.OK { + if e != nil { return err; } if value < 256 { @@ -266,7 +266,7 @@ parse_huffman_block :: proc(z: ^Context, z_repeat, z_offset: ^Huffman_Table) -> } else { if value == 256 { // End of block - return E_General.OK; + return nil; } value -= 257; @@ -276,7 +276,7 @@ parse_huffman_block :: proc(z: ^Context, z_repeat, z_offset: ^Huffman_Table) -> } value, e = decode_huffman(z, z_offset); - if e != E_General.OK { + if e != nil { return E_Deflate.Bad_Huffman_Code; } @@ -389,7 +389,7 @@ inflate_from_stream :: proc(using ctx: ^Context, raw := false, allocator := cont // Parse ZLIB stream without header. err = inflate_raw(ctx); - if err != E_General.OK { + if err != nil { return err; } @@ -401,7 +401,7 @@ inflate_from_stream :: proc(using ctx: ^Context, raw := false, allocator := cont return E_General.Checksum_Failed; } } - return E_General.OK; + return nil; } // @(optimization_mode="speed") @@ -417,15 +417,15 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> codelength_ht: ^Huffman_Table; z_repeat, err = allocate_huffman_table(allocator=context.allocator); - if err != E_General.OK { + if err != nil { return err; } z_offset, err = allocate_huffman_table(allocator=context.allocator); - if err != E_General.OK { + if err != nil { return err; } codelength_ht, err = allocate_huffman_table(allocator=context.allocator); - if err != E_General.OK { + if err != nil { return err; } defer free(z_repeat); @@ -481,11 +481,11 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> if type == 1 { // Use fixed code lengths. err = build_huffman(z_repeat, Z_FIXED_LENGTH[:]); - if err != E_General.OK { + if err != nil { return err; } err = build_huffman(z_offset, Z_FIXED_DIST[:]); - if err != E_General.OK { + if err != nil { return err; } } else { @@ -506,7 +506,7 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> codelength_sizes[Z_LENGTH_DEZIGZAG[i]] = u8(s); } err = build_huffman(codelength_ht, codelength_sizes[:]); - if err != E_General.OK { + if err != nil { return err; } @@ -515,7 +515,7 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> for n < ntot { c, err = decode_huffman(z, codelength_ht); - if err != E_General.OK { + if err != nil { return err; } @@ -559,18 +559,18 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> } err = build_huffman(z_repeat, lencodes[:hlit]); - if err != E_General.OK { + if err != nil { return err; } err = build_huffman(z_offset, lencodes[hlit:ntot]); - if err != E_General.OK { + if err != nil { return err; } } err = parse_huffman_block(z, z_repeat, z_offset); // log.debugf("Err: %v | Final: %v | Type: %v\n", err, final, type); - if err != E_General.OK { + if err != nil { return err; } } @@ -578,7 +578,7 @@ inflate_from_stream_raw :: proc(z: ^Context, allocator := context.allocator) -> break; } } - return E_General.OK; + return nil; } inflate_from_byte_array :: proc(input: []u8, buf: ^bytes.Buffer, raw := false) -> (err: Error) { diff --git a/core/image/png/example.odin b/core/image/png/example.odin index 072f88d6b..59a4cfd42 100644 --- a/core/image/png/example.odin +++ b/core/image/png/example.odin @@ -23,7 +23,7 @@ main :: proc() { img, err = png.load(file, options); defer png.destroy(img); - if err != png.E_General.OK { + if err != nil { fmt.printf("Trying to read PNG file %v returned %v\n", file, err); } else { v: png.Info; @@ -120,7 +120,7 @@ main :: proc() { } } - if err == E_General.OK && .do_not_decompress_image not_in options && .info not_in options { + 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 { diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index eb0d2f827..0975d1d87 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -107,7 +107,7 @@ text :: proc(c: Chunk) -> (res: Text, ok: bool) { buf: bytes.Buffer; zlib_error := zlib.inflate_from_byte_array(fields[2], &buf); defer bytes.buffer_destroy(&buf); - if zlib_error != E_General.OK { + if zlib_error != nil { ok = false; return; } @@ -161,7 +161,7 @@ text :: proc(c: Chunk) -> (res: Text, ok: bool) { buf: bytes.Buffer; zlib_error := zlib.inflate_from_byte_array(rest, &buf); defer bytes.buffer_destroy(&buf); - if zlib_error != E_General.OK { + if zlib_error != nil { ok = false; return; } @@ -200,7 +200,7 @@ iccp :: proc(c: Chunk) -> (res: iCCP, ok: bool) { // Set up ZLIB context and decompress iCCP payload buf: bytes.Buffer; zlib_error := zlib.inflate_from_byte_array(fields[2], &buf); - if zlib_error != E_General.OK { + if zlib_error != nil { bytes.buffer_destroy(&buf); ok = false; return; } @@ -498,7 +498,7 @@ when false { err = zlib.write_zlib_stream_from_memory(&ctx); b: []u8; - if err == E_General.OK { + if err == nil { b = ctx.out_buf[:]; } else { return err; @@ -511,6 +511,6 @@ when false { iend := make_chunk([]u8{}, .IEND); write_chunk(fd, iend); - return E_General.OK; + return nil; } } diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 74f287df8..7762f0106 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -267,13 +267,13 @@ read_chunk :: proc(ctx: ^compress.Context) -> (Chunk, Error) { if chunk.crc != u32be(computed_crc) { return {}, E_General.Checksum_Failed; } - return chunk, E_General.OK; + return chunk, nil; } read_header :: proc(ctx: ^compress.Context) -> (IHDR, Error) { c, e := read_chunk(ctx); - if e != E_General.OK { + if e != nil { return {}, e; } @@ -341,7 +341,7 @@ read_header :: proc(ctx: ^compress.Context) -> (IHDR, Error) { return {}, E_PNG.Unknown_Color_Type; } - return header, E_General.OK; + return header, nil; } chunk_type_to_name :: proc(type: ^Chunk_Type) -> string { @@ -453,7 +453,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c seen_ihdr = true; header, err = read_header(&ctx); - if err != E_General.OK { + if err != nil { return img, err; } @@ -506,7 +506,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c } c, err = read_chunk(&ctx); - if err != E_General.OK { + if err != nil { return img, err; } @@ -527,7 +527,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c if .return_metadata not_in options && .do_not_decompress_image in options { img.channels = final_image_channels; img.sidecar = info; - return img, E_General.OK; + return img, nil; } // There must be at least 1 IDAT, contiguous if more. if seen_idat { @@ -541,7 +541,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c next := ch.type; for next == .IDAT { c, err = read_chunk(&ctx); - if err != E_General.OK { + if err != nil { return img, err; } @@ -561,7 +561,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c seen_idat = true; case .IEND: c, err = read_chunk(&ctx); - if err != E_General.OK { + if err != nil { return img, err; } seen_iend = true; @@ -570,7 +570,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c // TODO: Make sure that 16-bit bKGD + tRNS chunks return u16 instead of u16be c, err = read_chunk(&ctx); - if err != E_General.OK { + if err != nil { return img, err; } seen_bkgd = true; @@ -605,7 +605,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c } case .tRNS: c, err = read_chunk(&ctx); - if err != E_General.OK { + if err != nil { return img, err; } @@ -647,7 +647,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c case: // Unhandled type c, err = read_chunk(&ctx); - if err != E_General.OK { + if err != nil { return img, err; } if .return_metadata in options { @@ -664,7 +664,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c } if .do_not_decompress_image in options { img.channels = final_image_channels; - return img, E_General.OK; + return img, nil; } if !seen_idat { @@ -675,7 +675,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c zlib_error := zlib.inflate(idat, &buf); defer bytes.buffer_destroy(&buf); - if zlib_error != E_General.OK { + if zlib_error != nil { return {}, zlib_error; } else { /* @@ -712,7 +712,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c as metadata, and set it instead to the raw number of channels. */ defilter_error := defilter(img, &buf, &header, options); - if defilter_error != E_General.OK { + if defilter_error != nil { bytes.buffer_destroy(&img.pixels); return {}, defilter_error; } @@ -727,10 +727,10 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c */ if .Paletted in header.color_type && .do_not_expand_indexed in options { - return img, E_General.OK; + return img, nil; } if .Color not_in header.color_type && .do_not_expand_grayscale in options { - return img, E_General.OK; + return img, nil; } @@ -839,7 +839,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c // If we have 3 in and 3 out, or 4 in and 4 out without premultiplication... if raw_image_channels == 4 && .alpha_premultiply not_in options && !seen_bkgd { // Then we're done. - return img, E_General.OK; + return img, nil; } } @@ -1036,7 +1036,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c // If we have 3 in and 3 out, or 4 in and 4 out without premultiplication... if !premultiply { // Then we're done. - return img, E_General.OK; + return img, nil; } } @@ -1229,7 +1229,7 @@ load_from_stream :: proc(stream: io.Stream, options := Options{}, allocator := c unreachable("We should never see bit depths other than 8, 16 and 'Paletted' here."); } - return img, E_General.OK; + return img, nil; } @@ -1651,7 +1651,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, option } } - return E_General.OK; + return nil; } load :: proc{load_from_file, load_from_slice, load_from_stream}; -- cgit v1.2.3 From afb6ebd21e8296f7f49a49161e38cf2414ffbf3e Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 4 May 2021 17:48:43 +0200 Subject: Fix gray+alpha alpha extract. --- core/image/common.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'core') diff --git a/core/image/common.odin b/core/image/common.odin index e92060ed2..9024ec769 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -150,15 +150,15 @@ return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok idx := int(channel); - if idx > img.channels { - return {}, false; - } - if img.channels == 2 && idx == 4 { // Alpha requested, which in a two channel image is index 2: G. idx = 2; } + if idx > img.channels { + return {}, false; + } + switch(img.depth) { case 8: buffer_size := compute_buffer_size(img.width, img.height, 1, 8); -- cgit v1.2.3 From 278de3a92f80e1c3f1abc3ece53db6d0bba1e393 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 5 May 2021 15:22:54 +0100 Subject: Unify `AstTernaryExpr` with `AstTernaryIfExpr` Allow for both syntaxes `x if cond else y` and `cond ? x : y` Removes the confusing semantics behind `?:` which could be `if` or `when` depending on the context. --- core/runtime/udivmod128.odin | 2 +- src/check_expr.cpp | 120 ++++++------------------------------------- src/check_type.cpp | 10 ---- src/llvm_backend.cpp | 41 --------------- src/parser.cpp | 15 +----- src/parser.hpp | 1 - 6 files changed, 17 insertions(+), 172 deletions(-) (limited to 'core') diff --git a/core/runtime/udivmod128.odin b/core/runtime/udivmod128.odin index e4b7380d3..fff856ab6 100644 --- a/core/runtime/udivmod128.odin +++ b/core/runtime/udivmod128.odin @@ -11,7 +11,7 @@ udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { q, r: [2]u64 = ---, ---; sr: u32 = 0; - low :: ODIN_ENDIAN == "big" ? 1 : 0; + low :: 1 when ODIN_ENDIAN == "big" else 0; high :: 1 - low; U64_BITS :: 8*size_of(u64); U128_BITS :: 8*size_of(u128); diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 158325af1..338679755 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2860,16 +2860,6 @@ void update_expr_type(CheckerContext *c, Ast *e, Type *type, bool final) { } case_end; - case_ast_node(te, TernaryExpr, e); - if (old.value.kind != ExactValue_Invalid) { - // See above note in UnaryExpr case - break; - } - - update_expr_type(c, te->x, type, final); - update_expr_type(c, te->y, type, final); - case_end; - case_ast_node(te, TernaryIfExpr, e); if (old.value.kind != ExactValue_Invalid) { // See above note in UnaryExpr case @@ -6104,88 +6094,6 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type o->type = type; case_end; - case_ast_node(te, TernaryExpr, node); - Operand cond = {Addressing_Invalid}; - check_expr(c, &cond, te->cond); - node->viral_state_flags |= te->cond->viral_state_flags; - - if (cond.mode != Addressing_Invalid && !is_type_boolean(cond.type)) { - error(te->cond, "Non-boolean condition in if expression"); - } - - Operand x = {Addressing_Invalid}; - Operand y = {Addressing_Invalid}; - check_expr_or_type(c, &x, te->x, type_hint); - node->viral_state_flags |= te->x->viral_state_flags; - - if (te->y != nullptr) { - check_expr_or_type(c, &y, te->y, type_hint); - node->viral_state_flags |= te->y->viral_state_flags; - } else { - error(node, "A ternary expression must have an else clause"); - return kind; - } - - if (x.type == nullptr || x.type == t_invalid || - y.type == nullptr || y.type == t_invalid) { - return kind; - } - - if (x.mode == Addressing_Type && y.mode == Addressing_Type && - cond.mode == Addressing_Constant && is_type_boolean(cond.type)) { - o->mode = Addressing_Type; - if (cond.value.value_bool) { - o->type = x.type; - o->expr = x.expr; - } else { - o->type = y.type; - o->expr = y.expr; - } - return Expr_Expr; - } - - convert_to_typed(c, &x, y.type); - if (x.mode == Addressing_Invalid) { - return kind; - } - convert_to_typed(c, &y, x.type); - if (y.mode == Addressing_Invalid) { - x.mode = Addressing_Invalid; - return kind; - } - - if (!ternary_compare_types(x.type, y.type)) { - gbString its = type_to_string(x.type); - gbString ets = type_to_string(y.type); - error(node, "Mismatched types in ternary expression, %s vs %s", its, ets); - gb_string_free(ets); - gb_string_free(its); - return kind; - } - - Type *type = x.type; - if (is_type_untyped_nil(type) || is_type_untyped_undef(type)) { - type = y.type; - } - - o->type = type; - o->mode = Addressing_Value; - - if (cond.mode == Addressing_Constant && is_type_boolean(cond.type) && - x.mode == Addressing_Constant && - y.mode == Addressing_Constant) { - - o->mode = Addressing_Constant; - - if (cond.value.value_bool) { - o->value = x.value; - } else { - o->value = y.value; - } - } - - case_end; - case_ast_node(te, TernaryIfExpr, node); Operand cond = {Addressing_Invalid}; check_expr(c, &cond, te->cond); @@ -8265,20 +8173,22 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) { str = write_expr_to_string(str, be->right, shorthand); case_end; - case_ast_node(te, TernaryExpr, node); - str = write_expr_to_string(str, te->cond, shorthand); - str = gb_string_appendc(str, " ? "); - str = write_expr_to_string(str, te->x, shorthand); - str = gb_string_appendc(str, " : "); - str = write_expr_to_string(str, te->y, shorthand); - case_end; - case_ast_node(te, TernaryIfExpr, node); - str = write_expr_to_string(str, te->x, shorthand); - str = gb_string_appendc(str, " if "); - str = write_expr_to_string(str, te->cond, shorthand); - str = gb_string_appendc(str, " else "); - str = write_expr_to_string(str, te->y, shorthand); + TokenPos x = ast_token(te->x).pos; + TokenPos cond = ast_token(te->cond).pos; + if (x < cond) { + str = write_expr_to_string(str, te->x, shorthand); + str = gb_string_appendc(str, " if "); + str = write_expr_to_string(str, te->cond, shorthand); + str = gb_string_appendc(str, " else "); + str = write_expr_to_string(str, te->y, shorthand); + } else { + str = write_expr_to_string(str, te->cond, shorthand); + str = gb_string_appendc(str, " ? "); + str = write_expr_to_string(str, te->x, shorthand); + str = gb_string_appendc(str, " : "); + str = write_expr_to_string(str, te->y, shorthand); + } case_end; case_ast_node(te, TernaryWhenExpr, node); diff --git a/src/check_type.cpp b/src/check_type.cpp index f70230682..b90732e00 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2889,16 +2889,6 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t } case_end; - case_ast_node(te, TernaryExpr, e); - Operand o = {}; - check_expr_or_type(ctx, &o, e); - if (o.mode == Addressing_Type) { - *type = o.type; - set_base_type(named_type, *type); - return true; - } - case_end; - case_ast_node(te, TernaryIfExpr, e); Operand o = {}; check_expr_or_type(ctx, &o, e); diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index c4d704cc6..baf768880 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -11351,47 +11351,6 @@ lbValue lb_build_expr(lbProcedure *p, Ast *expr) { return lb_build_expr(p, se->call); case_end; - case_ast_node(te, TernaryExpr, expr); - LLVMValueRef incoming_values[2] = {}; - LLVMBasicBlockRef incoming_blocks[2] = {}; - - GB_ASSERT(te->y != nullptr); - lbBlock *then = lb_create_block(p, "if.then"); - lbBlock *done = lb_create_block(p, "if.done"); // NOTE(bill): Append later - lbBlock *else_ = lb_create_block(p, "if.else"); - - lbValue cond = lb_build_cond(p, te->cond, then, else_); - lb_start_block(p, then); - - Type *type = default_type(type_of_expr(expr)); - - lb_open_scope(p, nullptr); - incoming_values[0] = lb_emit_conv(p, lb_build_expr(p, te->x), type).value; - lb_close_scope(p, lbDeferExit_Default, nullptr); - - lb_emit_jump(p, done); - lb_start_block(p, else_); - - lb_open_scope(p, nullptr); - incoming_values[1] = lb_emit_conv(p, lb_build_expr(p, te->y), type).value; - lb_close_scope(p, lbDeferExit_Default, nullptr); - - lb_emit_jump(p, done); - lb_start_block(p, done); - - lbValue res = {}; - res.value = LLVMBuildPhi(p->builder, lb_type(p->module, type), ""); - res.type = type; - - GB_ASSERT(p->curr_block->preds.count >= 2); - incoming_blocks[0] = p->curr_block->preds[0]->block; - incoming_blocks[1] = p->curr_block->preds[1]->block; - - LLVMAddIncoming(res.value, incoming_values, incoming_blocks, 2); - - return res; - case_end; - case_ast_node(te, TernaryIfExpr, expr); LLVMValueRef incoming_values[2] = {}; LLVMBasicBlockRef incoming_blocks[2] = {}; diff --git a/src/parser.cpp b/src/parser.cpp index d0a2c933c..376ac58dc 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -39,7 +39,6 @@ Token ast_token(Ast *node) { case Ast_Ellipsis: return node->Ellipsis.token; case Ast_FieldValue: return node->FieldValue.eq; case Ast_DerefExpr: return node->DerefExpr.op; - case Ast_TernaryExpr: return ast_token(node->TernaryExpr.cond); case Ast_TernaryIfExpr: return ast_token(node->TernaryIfExpr.x); case Ast_TernaryWhenExpr: return ast_token(node->TernaryWhenExpr.x); case Ast_TypeAssertion: return ast_token(node->TypeAssertion.expr); @@ -241,11 +240,6 @@ Ast *clone_ast(Ast *node) { n->FieldValue.value = clone_ast(n->FieldValue.value); break; - case Ast_TernaryExpr: - n->TernaryExpr.cond = clone_ast(n->TernaryExpr.cond); - n->TernaryExpr.x = clone_ast(n->TernaryExpr.x); - n->TernaryExpr.y = clone_ast(n->TernaryExpr.y); - break; case Ast_TernaryIfExpr: n->TernaryIfExpr.x = clone_ast(n->TernaryIfExpr.x); n->TernaryIfExpr.cond = clone_ast(n->TernaryIfExpr.cond); @@ -698,13 +692,6 @@ Ast *ast_compound_lit(AstFile *f, Ast *type, Array const &elems, Token op } -Ast *ast_ternary_expr(AstFile *f, Ast *cond, Ast *x, Ast *y) { - Ast *result = alloc_ast_node(f, Ast_TernaryExpr); - result->TernaryExpr.cond = cond; - result->TernaryExpr.x = x; - result->TernaryExpr.y = y; - return result; -} Ast *ast_ternary_if_expr(AstFile *f, Ast *x, Ast *cond, Ast *y) { Ast *result = alloc_ast_node(f, Ast_TernaryIfExpr); result->TernaryIfExpr.x = x; @@ -2871,7 +2858,7 @@ Ast *parse_binary_expr(AstFile *f, bool lhs, i32 prec_in) { Ast *x = parse_expr(f, lhs); Token token_c = expect_token(f, Token_Colon); Ast *y = parse_expr(f, lhs); - expr = ast_ternary_expr(f, cond, x, y); + expr = ast_ternary_if_expr(f, x, cond, y); } else if (op.kind == Token_if) { Ast *x = expr; // Token_if diff --git a/src/parser.hpp b/src/parser.hpp index 93c0363ab..2a0b0fa27 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -343,7 +343,6 @@ AST_KIND(_ExprBegin, "", bool) \ i32 builtin_id; \ }) \ AST_KIND(FieldValue, "field value", struct { Token eq; Ast *field, *value; }) \ - AST_KIND(TernaryExpr, "ternary expression", struct { Ast *cond, *x, *y; }) \ AST_KIND(TernaryIfExpr, "ternary if expression", struct { Ast *x, *cond, *y; }) \ AST_KIND(TernaryWhenExpr, "ternary when expression", struct { Ast *x, *cond, *y; }) \ AST_KIND(TypeAssertion, "type assertion", struct { Ast *expr; Token dot; Ast *type; Type *type_hint; }) \ -- cgit v1.2.3 From 4f51d74fc29a3cfa09f00dd598d3751901261902 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 6 May 2021 11:25:41 +0200 Subject: Fix typo in core:mem alloc() comment. --- core/mem/alloc.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'core') diff --git a/core/mem/alloc.odin b/core/mem/alloc.odin index 0df68255f..0da7a9708 100644 --- a/core/mem/alloc.odin +++ b/core/mem/alloc.odin @@ -22,7 +22,7 @@ Allocator_Mode_Set :: distinct bit_set[Allocator_Mode]; Allocator_Query_Info :: runtime.Allocator_Query_Info; /* Allocator_Query_Info :: struct { - pointer: Maybe(rawptr), + pointer: rawptr, size: Maybe(int), alignment: Maybe(int), } -- cgit v1.2.3 From 03862d1f48a62d1ab9445050a6f3a3b8d0323f88 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 6 May 2021 13:23:17 +0200 Subject: Mark mem.slice_ptr_to_bytes as deprecated. Use byte_slice instead. We can't make it an alias *and* mark it as deprecated, regrettably: ```odin byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte { return transmute([]u8)Raw_Slice{data=data, len=max(len, 0)}; } @(deprecated="use byte_slice") slice_ptr_to_bytes :: byte_slice; "mem.odin(145:1) Constant alias declarations cannot have attributes" ``` --- core/mem/mem.odin | 1 + 1 file changed, 1 insertion(+) (limited to 'core') diff --git a/core/mem/mem.odin b/core/mem/mem.odin index ddf9e9637..ecf232557 100644 --- a/core/mem/mem.odin +++ b/core/mem/mem.odin @@ -142,6 +142,7 @@ slice_ptr :: proc(ptr: ^$T, len: int) -> []T { byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte { return transmute([]u8)Raw_Slice{data=data, len=max(len, 0)}; } +@(deprecated="use byte_slice") slice_ptr_to_bytes :: proc(data: rawptr, len: int) -> []byte { return transmute([]u8)Raw_Slice{data=data, len=max(len, 0)}; } -- cgit v1.2.3 From 502ad0c10baa98f0dfeb55795b9555e15d4e4d13 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 6 May 2021 14:00:01 +0100 Subject: `sync2.Auto_Reset_Event`; Make atomic operations names clearer --- core/sync/sync2/atomic.odin | 84 ++++++++++++++++---------------- core/sync/sync2/extended.odin | 54 +++++++++++++++----- core/sync/sync2/primitives.odin | 49 +++++++++++++++++++ core/sync/sync2/primitives_pthreads.odin | 4 +- core/sync/sync2/primitives_windows.odin | 8 +-- 5 files changed, 138 insertions(+), 61 deletions(-) (limited to 'core') diff --git a/core/sync/sync2/atomic.odin b/core/sync/sync2/atomic.odin index 1f8e2f3a8..fa86ec352 100644 --- a/core/sync/sync2/atomic.odin +++ b/core/sync/sync2/atomic.odin @@ -2,78 +2,76 @@ package sync2 import "intrinsics" -// TODO(bill): Is this even a good design? The intrinsics seem to be more than good enough and just as clean - cpu_relax :: intrinsics.cpu_relax; -atomic_fence :: intrinsics.atomic_fence; -atomic_fence_acq :: intrinsics.atomic_fence_acq; -atomic_fence_rel :: intrinsics.atomic_fence_rel; -atomic_fence_acqrel :: intrinsics.atomic_fence_acqrel; +atomic_fence :: intrinsics.atomic_fence; +atomic_fence_acquire :: intrinsics.atomic_fence_acq; +atomic_fence_release :: intrinsics.atomic_fence_rel; +atomic_fence_acqrel :: intrinsics.atomic_fence_acqrel; atomic_store :: intrinsics.atomic_store; -atomic_store_rel :: intrinsics.atomic_store_rel; +atomic_store_release :: intrinsics.atomic_store_rel; atomic_store_relaxed :: intrinsics.atomic_store_relaxed; atomic_store_unordered :: intrinsics.atomic_store_unordered; atomic_load :: intrinsics.atomic_load; -atomic_load_acq :: intrinsics.atomic_load_acq; +atomic_load_acquire :: intrinsics.atomic_load_acq; atomic_load_relaxed :: intrinsics.atomic_load_relaxed; atomic_load_unordered :: intrinsics.atomic_load_unordered; atomic_add :: intrinsics.atomic_add; -atomic_add_acq :: intrinsics.atomic_add_acq; -atomic_add_rel :: intrinsics.atomic_add_rel; +atomic_add_acquire :: intrinsics.atomic_add_acq; +atomic_add_release :: intrinsics.atomic_add_rel; atomic_add_acqrel :: intrinsics.atomic_add_acqrel; atomic_add_relaxed :: intrinsics.atomic_add_relaxed; atomic_sub :: intrinsics.atomic_sub; -atomic_sub_acq :: intrinsics.atomic_sub_acq; -atomic_sub_rel :: intrinsics.atomic_sub_rel; +atomic_sub_acquire :: intrinsics.atomic_sub_acq; +atomic_sub_release :: intrinsics.atomic_sub_rel; atomic_sub_acqrel :: intrinsics.atomic_sub_acqrel; atomic_sub_relaxed :: intrinsics.atomic_sub_relaxed; atomic_and :: intrinsics.atomic_and; -atomic_and_acq :: intrinsics.atomic_and_acq; -atomic_and_rel :: intrinsics.atomic_and_rel; +atomic_and_acquire :: intrinsics.atomic_and_acq; +atomic_and_release :: intrinsics.atomic_and_rel; atomic_and_acqrel :: intrinsics.atomic_and_acqrel; atomic_and_relaxed :: intrinsics.atomic_and_relaxed; atomic_nand :: intrinsics.atomic_nand; -atomic_nand_acq :: intrinsics.atomic_nand_acq; -atomic_nand_rel :: intrinsics.atomic_nand_rel; +atomic_nand_acquire :: intrinsics.atomic_nand_acq; +atomic_nand_release :: intrinsics.atomic_nand_rel; atomic_nand_acqrel :: intrinsics.atomic_nand_acqrel; atomic_nand_relaxed :: intrinsics.atomic_nand_relaxed; atomic_or :: intrinsics.atomic_or; -atomic_or_acq :: intrinsics.atomic_or_acq; -atomic_or_rel :: intrinsics.atomic_or_rel; +atomic_or_acquire :: intrinsics.atomic_or_acq; +atomic_or_release :: intrinsics.atomic_or_rel; atomic_or_acqrel :: intrinsics.atomic_or_acqrel; atomic_or_relaxed :: intrinsics.atomic_or_relaxed; atomic_xor :: intrinsics.atomic_xor; -atomic_xor_acq :: intrinsics.atomic_xor_acq; -atomic_xor_rel :: intrinsics.atomic_xor_rel; +atomic_xor_acquire :: intrinsics.atomic_xor_acq; +atomic_xor_release :: intrinsics.atomic_xor_rel; atomic_xor_acqrel :: intrinsics.atomic_xor_acqrel; atomic_xor_relaxed :: intrinsics.atomic_xor_relaxed; -atomic_xchg :: intrinsics.atomic_xchg; -atomic_xchg_acq :: intrinsics.atomic_xchg_acq; -atomic_xchg_rel :: intrinsics.atomic_xchg_rel; -atomic_xchg_acqrel :: intrinsics.atomic_xchg_acqrel; -atomic_xchg_relaxed :: intrinsics.atomic_xchg_relaxed; +atomic_exchange :: intrinsics.atomic_xchg; +atomic_exchange_acquire :: intrinsics.atomic_xchg_acq; +atomic_exchange_release :: intrinsics.atomic_xchg_rel; +atomic_exchange_acqrel :: intrinsics.atomic_xchg_acqrel; +atomic_exchange_relaxed :: intrinsics.atomic_xchg_relaxed; -atomic_cxchg :: intrinsics.atomic_cxchg; -atomic_cxchg_acq :: intrinsics.atomic_cxchg_acq; -atomic_cxchg_rel :: intrinsics.atomic_cxchg_rel; -atomic_cxchg_acqrel :: intrinsics.atomic_cxchg_acqrel; -atomic_cxchg_relaxed :: intrinsics.atomic_cxchg_relaxed; -atomic_cxchg_failrelaxed :: intrinsics.atomic_cxchg_failrelaxed; -atomic_cxchg_failacq :: intrinsics.atomic_cxchg_failacq; -atomic_cxchg_acq_failrelaxed :: intrinsics.atomic_cxchg_acq_failrelaxed; -atomic_cxchg_acqrel_failrelaxed :: intrinsics.atomic_cxchg_acqrel_failrelaxed; +atomic_compare_exchange_strong :: intrinsics.atomic_cxchg; +atomic_compare_exchange_strong_acquire :: intrinsics.atomic_cxchg_acq; +atomic_compare_exchange_strong_release :: intrinsics.atomic_cxchg_rel; +atomic_compare_exchange_strong_acqrel :: intrinsics.atomic_cxchg_acqrel; +atomic_compare_exchange_strong_relaxed :: intrinsics.atomic_cxchg_relaxed; +atomic_compare_exchange_strong_failrelaxed :: intrinsics.atomic_cxchg_failrelaxed; +atomic_compare_exchange_strong_failacquire :: intrinsics.atomic_cxchg_failacq; +atomic_compare_exchange_strong_acquire_failrelaxed :: intrinsics.atomic_cxchg_acq_failrelaxed; +atomic_compare_exchange_strong_acqrel_failrelaxed :: intrinsics.atomic_cxchg_acqrel_failrelaxed; -atomic_cxchgweak :: intrinsics.atomic_cxchgweak; -atomic_cxchgweak_acq :: intrinsics.atomic_cxchgweak_acq; -atomic_cxchgweak_rel :: intrinsics.atomic_cxchgweak_rel; -atomic_cxchgweak_acqrel :: intrinsics.atomic_cxchgweak_acqrel; -atomic_cxchgweak_relaxed :: intrinsics.atomic_cxchgweak_relaxed; -atomic_cxchgweak_failrelaxed :: intrinsics.atomic_cxchgweak_failrelaxed; -atomic_cxchgweak_failacq :: intrinsics.atomic_cxchgweak_failacq; -atomic_cxchgweak_acq_failrelaxed :: intrinsics.atomic_cxchgweak_acq_failrelaxed; -atomic_cxchgweak_acqrel_failrelaxed :: intrinsics.atomic_cxchgweak_acqrel_failrelaxed; +atomic_compare_exchange_weak :: intrinsics.atomic_cxchgweak; +atomic_compare_exchange_weak_acquire :: intrinsics.atomic_cxchgweak_acq; +atomic_compare_exchange_weak_release :: intrinsics.atomic_cxchgweak_rel; +atomic_compare_exchange_weak_acqrel :: intrinsics.atomic_cxchgweak_acqrel; +atomic_compare_exchange_weak_relaxed :: intrinsics.atomic_cxchgweak_relaxed; +atomic_compare_exchange_weak_failrelaxed :: intrinsics.atomic_cxchgweak_failrelaxed; +atomic_compare_exchange_weak_failacquire :: intrinsics.atomic_cxchgweak_failacq; +atomic_compare_exchange_weak_acquire_failrelaxed :: intrinsics.atomic_cxchgweak_acq_failrelaxed; +atomic_compare_exchange_weak_acqrel_failrelaxed :: intrinsics.atomic_cxchgweak_acqrel_failrelaxed; diff --git a/core/sync/sync2/extended.odin b/core/sync/sync2/extended.odin index 3f44a172a..06051c822 100644 --- a/core/sync/sync2/extended.odin +++ b/core/sync/sync2/extended.odin @@ -122,6 +122,36 @@ barrier_wait :: proc(b: ^Barrier) -> (is_leader: bool) { } +Auto_Reset_Event :: struct { + // status == 0: Event is reset and no threads are waiting + // status == 1: Event is signaled + // status == -N: Event is reset and N threads are waiting + status: i32, + sema: Sema, +} + +auto_reset_event_signal :: proc(e: ^Auto_Reset_Event) { + old_status := atomic_load_relaxed(&e.status); + for { + new_status := old_status + 1 if old_status < 1 else 1; + if _, ok := atomic_compare_exchange_weak_release(&e.status, old_status, new_status); ok { + break; + } + + if old_status < 0 { + sema_post(&e.sema); + } + } +} + +auto_reset_event_wait :: proc(e: ^Auto_Reset_Event) { + old_status := atomic_sub_acquire(&e.status, 1); + if old_status < 1 { + sema_wait(&e.sema); + } +} + + Ticket_Mutex :: struct { ticket: uint, @@ -130,7 +160,7 @@ Ticket_Mutex :: struct { ticket_mutex_lock :: #force_inline proc(m: ^Ticket_Mutex) { ticket := atomic_add_relaxed(&m.ticket, 1); - for ticket != atomic_load_acq(&m.serving) { + for ticket != atomic_load_acquire(&m.serving) { cpu_relax(); } } @@ -142,23 +172,23 @@ ticket_mutex_unlock :: #force_inline proc(m: ^Ticket_Mutex) { Benaphore :: struct { - counter: int, + counter: i32, sema: Sema, } benaphore_lock :: proc(b: ^Benaphore) { - if atomic_add_acq(&b.counter, 1) > 1 { + if atomic_add_acquire(&b.counter, 1) > 1 { sema_wait(&b.sema); } } benaphore_try_lock :: proc(b: ^Benaphore) -> bool { - v, _ := atomic_cxchg_acq(&b.counter, 1, 0); + v, _ := atomic_compare_exchange_strong_acquire(&b.counter, 1, 0); return v == 0; } benaphore_unlock :: proc(b: ^Benaphore) { - if atomic_sub_rel(&b.counter, 1) > 0 { + if atomic_sub_release(&b.counter, 1) > 0 { sema_post(&b.sema); } } @@ -166,13 +196,13 @@ benaphore_unlock :: proc(b: ^Benaphore) { Recursive_Benaphore :: struct { counter: int, owner: int, - recursion: int, + recursion: i32, sema: Sema, } recursive_benaphore_lock :: proc(b: ^Recursive_Benaphore) { tid := runtime.current_thread_id(); - if atomic_add_acq(&b.counter, 1) > 1 { + if atomic_add_acquire(&b.counter, 1) > 1 { if tid != b.owner { sema_wait(&b.sema); } @@ -185,10 +215,10 @@ recursive_benaphore_lock :: proc(b: ^Recursive_Benaphore) { recursive_benaphore_try_lock :: proc(b: ^Recursive_Benaphore) -> bool { tid := runtime.current_thread_id(); if b.owner == tid { - atomic_add_acq(&b.counter, 1); + atomic_add_acquire(&b.counter, 1); } - if v, _ := atomic_cxchg_acq(&b.counter, 1, 0); v != 0 { + if v, _ := atomic_compare_exchange_strong_acquire(&b.counter, 1, 0); v != 0 { return false; } // inside the lock @@ -205,7 +235,7 @@ recursive_benaphore_unlock :: proc(b: ^Recursive_Benaphore) { if recursion == 0 { b.owner = 0; } - if atomic_sub_rel(&b.counter, 1) > 0 { + if atomic_sub_release(&b.counter, 1) > 0 { if recursion == 0 { sema_post(&b.sema); } @@ -223,7 +253,7 @@ Once :: struct { } once_do :: proc(o: ^Once, fn: proc()) { - if atomic_load_acq(&o.done) == false { + if atomic_load_acquire(&o.done) == false { _once_do_slow(o, fn); } } @@ -234,6 +264,6 @@ _once_do_slow :: proc(o: ^Once, fn: proc()) { defer mutex_unlock(&o.m); if !o.done { fn(); - atomic_store_rel(&o.done, true); + atomic_store_release(&o.done, true); } } diff --git a/core/sync/sync2/primitives.odin b/core/sync/sync2/primitives.odin index bf16ea735..1ed83f706 100644 --- a/core/sync/sync2/primitives.odin +++ b/core/sync/sync2/primitives.odin @@ -25,6 +25,18 @@ mutex_try_lock :: proc(m: ^Mutex) -> bool { return _mutex_try_lock(m); } +// Example: +// +// if mutex_guard(&m) { +// ... +// } +// +@(deferred_in=mutex_unlock) +mutex_guard :: proc(m: ^Mutex) -> bool { + mutex_lock(m); + return true; +} + // A RW_Mutex is a reader/writer mutual exclusion lock // The lock can be held by any arbitrary number of readers or a single writer // The zero value for a RW_Mutex is an unlocked mutex @@ -65,6 +77,31 @@ rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool { return _rw_mutex_try_shared_lock(rw); } +// Example: +// +// if rw_mutex_guard(&m) { +// ... +// } +// +@(deferred_in=rw_mutex_unlock) +rw_mutex_guard :: proc(m: ^RW_Mutex) -> bool { + rw_mutex_lock(m); + return true; +} + +// Example: +// +// if rw_mutex_shared_guard(&m) { +// ... +// } +// +@(deferred_in=rw_mutex_shared_unlock) +rw_mutex_shared_guard :: proc(m: ^RW_Mutex) -> bool { + rw_mutex_shared_lock(m); + return true; +} + + // A Recusrive_Mutex is a recursive mutual exclusion lock // The zero value for a Recursive_Mutex is an unlocked mutex @@ -87,6 +124,18 @@ recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool { } +// Example: +// +// if recursive_mutex_guard(&m) { +// ... +// } +// +@(deferred_in=recursive_mutex_unlock) +recursive_mutex_guard :: proc(m: ^Recursive_Mutex) -> bool { + recursive_mutex_lock(m); + return true; +} + // Cond implements a condition variable, a rendezvous point for threads // waiting for signalling the occurence of an event diff --git a/core/sync/sync2/primitives_pthreads.odin b/core/sync/sync2/primitives_pthreads.odin index ea0d140d8..5fd43d871 100644 --- a/core/sync/sync2/primitives_pthreads.odin +++ b/core/sync/sync2/primitives_pthreads.odin @@ -84,7 +84,7 @@ _rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) { state := atomic_load(&rw.impl.state); for state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 { ok: bool; - state, ok = atomic_cxchgweak(&rw.impl.state, state, state + RW_Mutex_State_Reader); + state, ok = atomic_compare_exchange_weak(&rw.impl.state, state, state + RW_Mutex_State_Reader); if ok { return; } @@ -107,7 +107,7 @@ _rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) { _rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool { state := atomic_load(&rw.impl.state); if state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 { - _, ok := atomic_cxchg(&rw.impl.state, state, state + RW_Mutex_State_Reader); + _, ok := atomic_compare_exchange_strong(&rw.impl.state, state, state + RW_Mutex_State_Reader); if ok { return true; } diff --git a/core/sync/sync2/primitives_windows.odin b/core/sync/sync2/primitives_windows.odin index a66b4a9bd..219af0162 100644 --- a/core/sync/sync2/primitives_windows.odin +++ b/core/sync/sync2/primitives_windows.odin @@ -58,7 +58,7 @@ _Recursive_Mutex :: struct { _recursive_mutex_lock :: proc(m: ^Recursive_Mutex) { tid := win32.GetCurrentThreadId(); for { - prev_owner := atomic_cxchg_acq(&m.impl.owner, tid, 0); + prev_owner := atomic_compare_exchange_strong_acquire(&m.impl.owner, tid, 0); switch prev_owner { case 0, tid: m.impl.claim_count += 1; @@ -80,7 +80,7 @@ _recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) { if m.impl.claim_count != 0 { return; } - atomic_xchg_rel(&m.impl.owner, 0); + atomic_exchange_release(&m.impl.owner, 0); win32.WakeByAddressSingle(&m.impl.owner); // outside the lock @@ -88,7 +88,7 @@ _recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) { _recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool { tid := win32.GetCurrentThreadId(); - prev_owner := atomic_cxchg_acq(&m.impl.owner, tid, 0); + prev_owner := atomic_compare_exchange_strong_acquire(&m.impl.owner, tid, 0); switch prev_owner { case 0, tid: m.impl.claim_count += 1; @@ -139,7 +139,7 @@ _sema_wait :: proc(s: ^Sema) { ); original_count = s.impl.count; } - if original_count == atomic_cxchg(&s.impl.count, original_count-1, original_count) { + if original_count == atomic_compare_exchange_strong(&s.impl.count, original_count-1, original_count) { return; } } -- cgit v1.2.3 From 5420cc083d08a4c0faffbcf3ec1deecf05b6265d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 12 May 2021 23:26:21 +0100 Subject: Implement #807 --- core/fmt/fmt.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'core') diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index f9c386ffb..3b3716a15 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -641,9 +641,9 @@ fmt_write_padding :: proc(fi: ^Info, width: int) { return; } - pad_byte: byte = '0'; - if fi.space { - pad_byte = ' '; + pad_byte: byte = ' '; + if !fi.space { + pad_byte = '0'; } for i := 0; i < width; i += 1 { -- cgit v1.2.3 From b37d344eb214336036c597250206f36043c75975 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 13 May 2021 12:04:51 +0100 Subject: Add intrinsics.type_is_variant_of --- core/intrinsics/intrinsics.odin | 1 + src/check_builtin.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ src/checker_builtin_procs.hpp | 4 ++++ 3 files changed, 45 insertions(+) (limited to 'core') diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index d767f5e47..60b595aab 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -159,6 +159,7 @@ type_is_simd_vector :: proc($T: typeid) -> bool --- type_has_nil :: proc($T: typeid) -> bool --- type_is_specialization_of :: proc($T, $S: typeid) -> bool --- +type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) --- type_has_field :: proc($T: typeid, $name: string) -> bool --- diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 6b299a68a..8932ac914 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -2429,6 +2429,46 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 } break; + case BuiltinProc_type_is_variant_of: + { + if (operand->mode != Addressing_Type) { + error(operand->expr, "Expected a type for '%.*s'", LIT(builtin_name)); + operand->mode = Addressing_Invalid; + operand->type = t_invalid; + return false; + } + + + Type *u = operand->type; + + if (!is_type_union(u)) { + error(operand->expr, "Expected a union type for '%.*s'", LIT(builtin_name)); + operand->mode = Addressing_Invalid; + operand->type = t_invalid; + return false; + } + + Type *v = check_type(c, ce->args[1]); + + u = base_type(u); + GB_ASSERT(u->kind == Type_Union); + + bool is_variant = false; + + for_array(i, u->Union.variants) { + Type *vt = u->Union.variants[i]; + if (are_types_identical(v, vt)) { + is_variant = true; + break; + } + } + + operand->mode = Addressing_Constant; + operand->type = t_untyped_bool; + operand->value = exact_value_bool(is_variant); + } + break; + case BuiltinProc_type_struct_field_count: operand->value = exact_value_i64(0); if (operand->mode != Addressing_Type) { diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 98ef5180b..5b1aecd68 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -197,6 +197,8 @@ BuiltinProc__type_simple_boolean_end, BuiltinProc_type_is_specialization_of, + BuiltinProc_type_is_variant_of, + BuiltinProc_type_struct_field_count, BuiltinProc_type_proc_parameter_count, @@ -415,6 +417,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("type_is_specialization_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_is_variant_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_struct_field_count"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_proc_parameter_count"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, -- cgit v1.2.3 From 465b6139d55441fbe6da8d2699865c414cd87d3a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 13 May 2021 12:05:23 +0100 Subject: Temporarily fix syscall in Linux and Freebsd (eventually to be replaced with a proper implementation) --- core/os/os_freebsd.odin | 2 +- core/os/os_linux.odin | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'core') diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index 137c6f864..2afa8bd14 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -10,7 +10,7 @@ import "core:c" Handle :: distinct i32; File_Time :: distinct u64; Errno :: distinct i32; -Syscall :: distinct int; +Syscall :: distinct i32; INVALID_HANDLE :: ~Handle(0); diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index dd0914f40..7569909d7 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -11,7 +11,7 @@ import "core:strconv" Handle :: distinct i32; File_Time :: distinct u64; Errno :: distinct i32; -Syscall :: distinct int; +Syscall :: distinct i32; INVALID_HANDLE :: ~Handle(0); @@ -269,7 +269,7 @@ SYS_GETTID: Syscall : 186; foreign libc { @(link_name="__errno_location") __errno_location :: proc() -> ^int ---; - @(link_name="syscall") syscall :: proc(number: Syscall, #c_vararg args: ..any) -> int ---; + @(link_name="syscall") syscall :: proc(number: Syscall, #c_vararg args: ..any) -> i32 ---; @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, mode: c.int) -> Handle ---; @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int ---; @@ -595,7 +595,7 @@ exit :: proc "contextless" (code: int) -> ! { } current_thread_id :: proc "contextless" () -> int { - return syscall(SYS_GETTID); + return cast(int)syscall(SYS_GETTID); } dlopen :: proc(filename: string, flags: int) -> rawptr { -- cgit v1.2.3 From 85e5be03d1d8660bbd4025c278bb59897cb153c9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 16 May 2021 00:25:47 +0100 Subject: Redesign `os2.Error` to work with the new extended `union` behaviour --- core/os/os2/errors.odin | 61 ++++++++++---------------------------- core/os/os2/file_stream.odin | 20 +++---------- core/os/os2/file_util.odin | 1 + core/os/os2/file_windows.odin | 20 ++++++------- core/os/os2/pipe_windows.odin | 2 +- core/os/os2/stat_windows.odin | 6 ++-- core/os/os2/temp_file_windows.odin | 4 +-- 7 files changed, 37 insertions(+), 77 deletions(-) (limited to 'core') diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index 00cd600a8..2fc49deed 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -1,11 +1,8 @@ package os2 -Platform_Error_Min_Bits :: 32; +import "core:io" -Error :: enum u64 { - None = 0, - - // General Errors +General_Error :: enum u32 { Invalid_Argument, Permission_Denied, @@ -13,42 +10,19 @@ Error :: enum u64 { Not_Exist, Closed, - // Timeout Errors Timeout, +} - // I/O Errors - // EOF is the error returned by `read` when no more input is available - EOF, - - // Unexpected_EOF means that EOF was encountered in the middle of reading a fixed-sized block of data - Unexpected_EOF, - - // Short_Write means that a write accepted fewer bytes than requested but failed to return an explicit error - Short_Write, - - // Invalid_Write means that a write returned an impossible count - Invalid_Write, - - // Short_Buffer means that a read required a longer buffer than was provided - Short_Buffer, - - // No_Progress is returned by some implementations of `io.Reader` when many calls - // to `read` have failed to return any data or error. - // This is usually a signed of a broken `io.Reader` implementation - No_Progress, - - Invalid_Whence, - Invalid_Offset, - Invalid_Unread, - - Negative_Read, - Negative_Write, - Negative_Count, - Buffer_Full, +Platform_Error :: struct { + err: i32, +} - // Platform Specific Errors - Platform_Minimum = 1< (err: i32, ok: bool) { - if ferr >= .Platform_Minimum { - err = i32(u64(ferr)>>Platform_Error_Min_Bits); - ok = true; + v: Platform_Error; + if v, ok = ferr.(Platform_Error); ok { + err = v.err; } return; } -error_from_platform_error :: proc(errno: i32) -> Error { - return Error(u64(errno) << Platform_Error_Min_Bits); -} error_string :: proc(ferr: Error) -> string { - #partial switch ferr { - case .None: return ""; + switch ferr { + case nil: return ""; case .Invalid_Argument: return "invalid argument"; case .Permission_Denied: return "permission denied"; case .Exist: return "file already exists"; diff --git a/core/os/os2/file_stream.odin b/core/os/os2/file_stream.odin index 6877faea4..208b2323b 100644 --- a/core/os/os2/file_stream.odin +++ b/core/os/os2/file_stream.odin @@ -10,23 +10,11 @@ file_to_stream :: proc(fd: Handle) -> (s: io.Stream) { @(private) error_to_io_error :: proc(ferr: Error) -> io.Error { - #partial switch ferr { - case .None: return .None; - case .EOF: return .EOF; - case .Unexpected_EOF: return .Unexpected_EOF; - case .Short_Write: return .Short_Write; - case .Invalid_Write: return .Invalid_Write; - case .Short_Buffer: return .Short_Buffer; - case .No_Progress: return .No_Progress; - case .Invalid_Whence: return .Invalid_Whence; - case .Invalid_Offset: return .Invalid_Offset; - case .Invalid_Unread: return .Invalid_Unread; - case .Negative_Read: return .Negative_Read; - case .Negative_Write: return .Negative_Write; - case .Negative_Count: return .Negative_Count; - case .Buffer_Full: return .Buffer_Full; + err, ok := ferr.(io.Error); + if !ok { + err = .Unknown; } - return .Unknown; + return err; } diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin index 435eba3ab..db6842cf8 100644 --- a/core/os/os2/file_util.odin +++ b/core/os/os2/file_util.odin @@ -1,6 +1,7 @@ package os2 import "core:mem" +import "core:io" import "core:strconv" import "core:unicode/utf8" diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 97fe6b3d9..5e87d80a4 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -5,19 +5,19 @@ import "core:io" import "core:time" _create :: proc(name: string) -> (Handle, Error) { - return 0, .None; + return 0, nil; } _open :: proc(name: string) -> (Handle, Error) { - return 0, .None; + return 0, nil; } _open_file :: proc(name: string, flag: int, perm: File_Mode) -> (Handle, Error) { - return 0, .None; + return 0, nil; } _close :: proc(fd: Handle) -> Error { - return .None; + return nil; } _name :: proc(fd: Handle, allocator := context.allocator) -> string { @@ -58,11 +58,11 @@ _file_size :: proc(fd: Handle) -> (n: i64, err: Error) { _sync :: proc(fd: Handle) -> Error { - return .None; + return nil; } _flush :: proc(fd: Handle) -> Error { - return .None; + return nil; } _truncate :: proc(fd: Handle, size: i64) -> Maybe(Path_Error) { @@ -92,20 +92,20 @@ _read_link :: proc(name: string) -> (string, Maybe(Path_Error)) { _chdir :: proc(fd: Handle) -> Error { - return .None; + return nil; } _chmod :: proc(fd: Handle, mode: File_Mode) -> Error { - return .None; + return nil; } _chown :: proc(fd: Handle, uid, gid: int) -> Error { - return .None; + return nil; } _lchown :: proc(name: string, uid, gid: int) -> Error { - return .None; + return nil; } diff --git a/core/os/os2/pipe_windows.odin b/core/os/os2/pipe_windows.odin index 68adb6c3b..04750bf88 100644 --- a/core/os/os2/pipe_windows.odin +++ b/core/os/os2/pipe_windows.odin @@ -6,7 +6,7 @@ import win32 "core:sys/windows" _pipe :: proc() -> (r, w: Handle, err: Error) { p: [2]win32.HANDLE; if !win32.CreatePipe(&p[0], &p[1], nil, 0) { - return 0, 0, error_from_platform_error(i32(win32.GetLastError())); + return 0, 0, Platform_Error{i32(win32.GetLastError())}; } return Handle(p[0]), Handle(p[1]), nil; } diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index ed739b894..48811340a 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -40,7 +40,7 @@ _same_file :: proc(fi1, fi2: File_Info) -> bool { _stat_errno :: proc(errno: win32.DWORD) -> Path_Error { - return Path_Error{err = error_from_platform_error(i32(errno))}; + return Path_Error{err = Platform_Error{i32(errno)}}; } @@ -89,7 +89,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co fd: win32.WIN32_FIND_DATAW; sh := win32.FindFirstFileW(wname, &fd); if sh == win32.INVALID_HANDLE_VALUE { - e = Path_Error{err = error_from_platform_error(i32(win32.GetLastError()))}; + e = Path_Error{err = Platform_Error{i32(win32.GetLastError())}}; return; } win32.FindClose(sh); @@ -99,7 +99,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil); if h == win32.INVALID_HANDLE_VALUE { - e = Path_Error{err = error_from_platform_error(i32(win32.GetLastError()))}; + e = Path_Error{err = Platform_Error{i32(win32.GetLastError())}}; return; } defer win32.CloseHandle(h); diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index 19dca1b04..dd050ab48 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -4,11 +4,11 @@ package os2 import win32 "core:sys/windows" _create_temp :: proc(dir, pattern: string) -> (Handle, Error) { - return 0, .None; + return 0, nil; } _mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) { - return "", .None; + return "", nil; } _temp_dir :: proc(allocator := context.allocator) -> string { -- cgit v1.2.3 From 24c89b3eeed9d36fd6adfdc2f4a412b39fc59c6b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 16 May 2021 00:29:22 +0100 Subject: Minor change --- core/os/os2/file_stream.odin | 3 +++ 1 file changed, 3 insertions(+) (limited to 'core') diff --git a/core/os/os2/file_stream.odin b/core/os/os2/file_stream.odin index 208b2323b..52f5b30e9 100644 --- a/core/os/os2/file_stream.odin +++ b/core/os/os2/file_stream.odin @@ -10,6 +10,9 @@ file_to_stream :: proc(fd: Handle) -> (s: io.Stream) { @(private) error_to_io_error :: proc(ferr: Error) -> io.Error { + if ferr == nil { + return .None; + } err, ok := ferr.(io.Error); if !ok { err = .Unknown; -- cgit v1.2.3 From ce08e832f7dcdeeae37cf0e432648efa2f27f2a7 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 16 May 2021 12:34:35 +0100 Subject: Allow `..=` alongside `..` as a "full range" operator; Update `core:odin/parser` etc --- core/odin/parser/parser.odin | 66 ++++++++++++++++++++++++++++++++++---- core/odin/tokenizer/token.odin | 2 ++ core/odin/tokenizer/tokenizer.odin | 3 ++ src/check_expr.cpp | 7 ++-- src/check_stmt.cpp | 1 + src/check_type.cpp | 1 + src/llvm_backend.cpp | 6 ++-- src/parser.cpp | 18 ++++++----- src/tokenizer.cpp | 4 +++ 9 files changed, 89 insertions(+), 19 deletions(-) (limited to 'core') diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 160eb3a5c..661c46751 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -12,6 +12,10 @@ Parser :: struct { file: ^ast.File, tok: tokenizer.Tokenizer, + // If optional_semicolons is true, semicolons are completely as statement terminators + // different to .Insert_Semicolon in tok.flags + optional_semicolons: bool, + warn: Warning_Handler, err: Error_Handler, @@ -128,6 +132,10 @@ parse_file :: proc(p: ^Parser, file: ^ast.File) -> bool { p.line_comment = nil; } + if p.optional_semicolons { + p.tok.flags += {.Insert_Semicolon}; + } + p.file = file; tokenizer.init(&p.tok, file.src, file.fullpath, p.err); if p.tok.ch <= 0 { @@ -400,6 +408,11 @@ is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool { if node == nil { return false; } + + if p.optional_semicolons { + return true; + } + switch n in node.derived { case ast.Empty_Stmt, ast.Block_Stmt: return true; @@ -439,14 +452,34 @@ is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool { return false; } +expect_semicolon_newline_error :: proc(p: ^Parser, token: tokenizer.Token, s: ^ast.Node) { + if !p.optional_semicolons && .Insert_Semicolon in p.tok.flags && token.text == "\n" { + #partial switch token.kind { + case .Close_Brace: + case .Close_Paren: + case .Else: + return; + } + if is_semicolon_optional_for_node(p, s) { + return; + } + + tok := token; + tok.pos.column -= 1; + error(p, tok.pos, "expected ';', got newline"); + } +} + expect_semicolon :: proc(p: ^Parser, node: ^ast.Node) -> bool { if allow_token(p, .Semicolon) { + expect_semicolon_newline_error(p, p.prev_tok, node); return true; } prev := p.prev_tok; if prev.kind == .Semicolon { + expect_semicolon_newline_error(p, p.prev_tok, node); return true; } @@ -615,7 +648,7 @@ parse_if_stmt :: proc(p: ^Parser) -> ^ast.If_Stmt { cond = parse_expr(p, false); } else { init = parse_simple_stmt(p, nil); - if allow_token(p, .Semicolon) { + if parse_control_statement_semicolon_separator(p) { cond = parse_expr(p, false); } else { cond = convert_stmt_to_expr(p, init, "boolean expression"); @@ -668,6 +701,18 @@ parse_if_stmt :: proc(p: ^Parser) -> ^ast.If_Stmt { return if_stmt; } +parse_control_statement_semicolon_separator :: proc(p: ^Parser) -> bool { + tok := peek_token(p); + if tok.kind != .Open_Brace { + return allow_token(p, .Semicolon); + } + if tok.text == ";" { + return allow_token(p, .Semicolon); + } + return false; + +} + parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt { if p.curr_proc == nil { error(p, p.curr_tok.pos, "you cannot use a for statement in the file scope"); @@ -716,7 +761,7 @@ parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt { } } - if !is_range && allow_token(p, .Semicolon) { + if !is_range && parse_control_statement_semicolon_separator(p) { init = cond; cond = nil; if p.curr_tok.kind != .Semicolon { @@ -820,7 +865,7 @@ parse_switch_stmt :: proc(p: ^Parser) -> ^ast.Stmt { tag = parse_simple_stmt(p, {Stmt_Allow_Flag.In}); if as, ok := tag.derived.(ast.Assign_Stmt); ok && as.op.kind == .In { is_type_switch = true; - } else if allow_token(p, .Semicolon) { + } else if parse_control_statement_semicolon_separator(p) { init = tag; tag = nil; if p.curr_tok.kind != .Open_Brace { @@ -831,6 +876,7 @@ parse_switch_stmt :: proc(p: ^Parser) -> ^ast.Stmt { } + skip_possible_newline(p); open := expect_token(p, .Open_Brace); for p.curr_tok.kind == .Case { @@ -958,6 +1004,7 @@ parse_foreign_block :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Foreign_Bl defer p.in_foreign_block = prev_in_foreign_block; p.in_foreign_block = true; + skip_possible_newline_for_literal(p); open := expect_token(p, .Open_Brace); for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF { decl := parse_foreign_block_decl(p); @@ -1287,7 +1334,7 @@ token_precedence :: proc(p: ^Parser, kind: tokenizer.Token_Kind) -> int { #partial switch kind { case .Question, .If, .When: return 1; - case .Ellipsis, .Range_Half: + case .Ellipsis, .Range_Half, .Range_Full: if !p.allow_range { return 0; } @@ -2233,6 +2280,8 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { } body: ^ast.Stmt; + skip_possible_newline_for_literal(p); + if allow_token(p, .Undef) { body = nil; if where_token.kind != .Invalid { @@ -2405,6 +2454,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { p.expr_level = where_prev_level; } + skip_possible_newline_for_literal(p); expect_token(p, .Open_Brace); fields, name_count = parse_field_list(p, .Close_Brace, ast.Field_Flags_Struct); close := expect_token(p, .Close_Brace); @@ -2473,6 +2523,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { variants: [dynamic]^ast.Expr; + skip_possible_newline_for_literal(p); expect_token_after(p, .Open_Brace, "union"); for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF { @@ -2503,6 +2554,8 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { if p.curr_tok.kind != .Open_Brace { base_type = parse_type(p); } + + skip_possible_newline_for_literal(p); open := expect_token(p, .Open_Brace); fields := parse_elem_list(p); close := expect_token(p, .Close_Brace); @@ -2601,6 +2654,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { } } + skip_possible_newline_for_literal(p); open := expect_token(p, .Open_Brace); asm_string := parse_expr(p, false); expect_token(p, .Comma); @@ -2811,7 +2865,7 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a open := expect_token(p, .Open_Bracket); #partial switch p.curr_tok.kind { - case .Colon, .Ellipsis, .Range_Half: + case .Colon, .Ellipsis, .Range_Half, .Range_Full: // NOTE(bill): Do not err yet break; case: @@ -2819,7 +2873,7 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a } #partial switch p.curr_tok.kind { - case .Ellipsis, .Range_Half: + case .Ellipsis, .Range_Half, .Range_Full: error(p, p.curr_tok.pos, "expected a colon, not a range"); fallthrough; case .Colon: diff --git a/core/odin/tokenizer/token.odin b/core/odin/tokenizer/token.odin index 1b37bae23..88908d7f8 100644 --- a/core/odin/tokenizer/token.odin +++ b/core/odin/tokenizer/token.odin @@ -107,6 +107,7 @@ Token_Kind :: enum u32 { Comma, // , Ellipsis, // .. Range_Half, // ..< + Range_Full, // ..= Back_Slash, // \ B_Operator_End, @@ -233,6 +234,7 @@ tokens := [Token_Kind.COUNT]string { ",", "..", "..<", + "..=", "\\", "", diff --git a/core/odin/tokenizer/tokenizer.odin b/core/odin/tokenizer/tokenizer.odin index 58f546191..297f42e3d 100644 --- a/core/odin/tokenizer/tokenizer.odin +++ b/core/odin/tokenizer/tokenizer.odin @@ -623,6 +623,9 @@ scan :: proc(t: ^Tokenizer) -> Token { if t.ch == '<' { advance_rune(t); kind = .Range_Half; + } else if t.ch == '=' { + advance_rune(t); + kind = .Range_Full; } } } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index fb3a51415..61cfd7d6e 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -5940,8 +5940,9 @@ bool check_range(CheckerContext *c, Ast *node, Operand *x, Operand *y, ExactValu TokenKind op = Token_Lt; switch (ie->op.kind) { - case Token_Ellipsis: op = Token_LtEq; break; - case Token_RangeHalf: op = Token_Lt; break; + case Token_Ellipsis: op = Token_LtEq; break; // .. + case Token_RangeFull: op = Token_LtEq; break; // ..= + case Token_RangeHalf: op = Token_Lt; break; // ..< default: error(ie->op, "Invalid range operator"); break; } bool ok = compare_exact_values(op, a, b); @@ -5952,7 +5953,7 @@ bool check_range(CheckerContext *c, Ast *node, Operand *x, Operand *y, ExactValu } ExactValue inline_for_depth = exact_value_sub(b, a); - if (ie->op.kind == Token_Ellipsis) { + if (ie->op.kind != Token_RangeHalf) { inline_for_depth = exact_value_increment_one(inline_for_depth); } diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index a954b44b6..2c9d8e00e 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -939,6 +939,7 @@ void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { TokenKind upper_op = Token_Invalid; switch (be->op.kind) { case Token_Ellipsis: upper_op = Token_GtEq; break; + case Token_RangeFull: upper_op = Token_GtEq; break; case Token_RangeHalf: upper_op = Token_Gt; break; default: GB_PANIC("Invalid range operator"); break; } diff --git a/src/check_type.cpp b/src/check_type.cpp index cdce6dae8..24a3e0a59 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -931,6 +931,7 @@ void check_bit_set_type(CheckerContext *c, Type *type, Type *named_type, Ast *no switch (be->op.kind) { case Token_Ellipsis: + case Token_RangeFull: if (upper - lower >= bits) { error(bs->elem, "bit_set range is greater than %lld bits, %lld bits are required", bits, (upper-lower+1)); } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 7bdc8b04a..41b1e8fc9 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -4041,6 +4041,7 @@ void lb_build_range_interval(lbProcedure *p, AstBinaryExpr *node, TokenKind op = Token_Lt; switch (node->op.kind) { case Token_Ellipsis: op = Token_LtEq; break; + case Token_RangeFull: op = Token_LtEq; break; case Token_RangeHalf: op = Token_Lt; break; default: GB_PANIC("Invalid interval operator"); break; } @@ -4454,7 +4455,7 @@ void lb_build_inline_range_stmt(lbProcedure *p, AstUnrollRangeStmt *rs, Scope *s ExactValue start = start_expr->tav.value; ExactValue end = end_expr->tav.value; - if (op == Token_Ellipsis) { // .. [start, end] + if (op != Token_RangeHalf) { // .. [start, end] (or ..=) ExactValue index = exact_value_i64(0); for (ExactValue val = start; compare_exact_values(Token_LtEq, val, end); @@ -4465,7 +4466,7 @@ void lb_build_inline_range_stmt(lbProcedure *p, AstUnrollRangeStmt *rs, Scope *s lb_build_stmt(p, rs->body); } - } else if (op == Token_RangeHalf) { // ..< [start, end) + } else { // ..< [start, end) ExactValue index = exact_value_i64(0); for (ExactValue val = start; compare_exact_values(Token_Lt, val, end); @@ -4632,6 +4633,7 @@ void lb_build_switch_stmt(lbProcedure *p, AstSwitchStmt *ss, Scope *scope) { TokenKind op = Token_Invalid; switch (ie->op.kind) { case Token_Ellipsis: op = Token_LtEq; break; + case Token_RangeFull: op = Token_LtEq; break; case Token_RangeHalf: op = Token_Lt; break; default: GB_PANIC("Invalid interval operator"); break; } diff --git a/src/parser.cpp b/src/parser.cpp index 0dae732e5..2e27b8698 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1344,6 +1344,7 @@ Token expect_token_after(AstFile *f, TokenKind kind, char const *msg) { bool is_token_range(TokenKind kind) { switch (kind) { case Token_Ellipsis: + case Token_RangeFull: case Token_RangeHalf: return true; } @@ -1574,6 +1575,10 @@ void expect_semicolon(AstFile *f, Ast *s) { return; } + if (f->curr_token.kind == Token_EOF) { + return; + } + if (s != nullptr) { bool insert_semi = (f->tokenizer.flags & TokenizerFlag_InsertSemicolon) != 0; if (insert_semi) { @@ -2315,7 +2320,7 @@ Ast *parse_operand(AstFile *f, bool lhs) { f->expr_level = prev_level; } - + skip_possible_newline_for_literal(f); Token open = expect_token_after(f, Token_OpenBrace, "struct"); isize name_count = 0; @@ -2675,6 +2680,7 @@ Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) { switch (f->curr_token.kind) { case Token_Ellipsis: + case Token_RangeFull: case Token_RangeHalf: // NOTE(bill): Do not err yet case Token_Colon: @@ -2686,6 +2692,7 @@ Ast *parse_atom_expr(AstFile *f, Ast *operand, bool lhs) { switch (f->curr_token.kind) { case Token_Ellipsis: + case Token_RangeFull: case Token_RangeHalf: syntax_error(f->curr_token, "Expected a colon, not a range"); /* fallthrough */ @@ -2812,6 +2819,7 @@ i32 token_precedence(AstFile *f, TokenKind t) { case Token_when: return 1; case Token_Ellipsis: + case Token_RangeFull: case Token_RangeHalf: if (!f->allow_range) { return 0; @@ -3926,12 +3934,6 @@ Ast *parse_return_stmt(AstFile *f) { while (f->curr_token.kind != Token_Semicolon) { Ast *arg = parse_expr(f, false); - // if (f->curr_token.kind == Token_Eq) { - // Token eq = expect_token(f, Token_Eq); - // Ast *value = parse_value(f); - // arg = ast_field_value(f, arg, value, eq); - // } - array_add(&results, arg); if (f->curr_token.kind != Token_Comma || f->curr_token.kind == Token_EOF) { @@ -4052,7 +4054,7 @@ Ast *parse_case_clause(AstFile *f, bool is_type) { } f->allow_range = prev_allow_range; f->allow_in_expr = prev_allow_in_expr; - expect_token(f, Token_Colon); // TODO(bill): Is this the best syntax? + expect_token(f, Token_Colon); Array stmts = parse_stmt_list(f); return ast_case_clause(f, token, list, stmts); diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 5936ac3fe..d1310b56e 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -76,6 +76,7 @@ TOKEN_KIND(Token__ComparisonEnd, ""), \ TOKEN_KIND(Token_Period, "."), \ TOKEN_KIND(Token_Comma, ","), \ TOKEN_KIND(Token_Ellipsis, ".."), \ + TOKEN_KIND(Token_RangeFull, "..="), \ TOKEN_KIND(Token_RangeHalf, "..<"), \ TOKEN_KIND(Token_BackSlash, "\\"), \ TOKEN_KIND(Token__OperatorEnd, ""), \ @@ -1204,6 +1205,9 @@ void tokenizer_get_token(Tokenizer *t, Token *token, int repeat=0) { if (t->curr_rune == '<') { advance_to_next_rune(t); token->kind = Token_RangeHalf; + } else if (t->curr_rune == '=') { + advance_to_next_rune(t); + token->kind = Token_RangeFull; } } else if ('0' <= t->curr_rune && t->curr_rune <= '9') { scan_number_to_token(t, token, true); -- cgit v1.2.3 From df3512b1120dc17a01f8cfebd1f2f9042acdb9b5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 16 May 2021 12:38:27 +0100 Subject: Make `core:odin` use a `string` for the source rather than `[]byte` --- core/odin/ast/ast.odin | 2 +- core/odin/parser/parse_files.odin | 2 +- core/odin/tokenizer/tokenizer.odin | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'core') diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index 0d015f9bb..cf2cdeacc 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -69,7 +69,7 @@ File :: struct { pkg: ^Package, fullpath: string, - src: []byte, + src: string, docs: ^Comment_Group, diff --git a/core/odin/parser/parse_files.odin b/core/odin/parser/parse_files.odin index 99275777c..f622c9781 100644 --- a/core/odin/parser/parse_files.odin +++ b/core/odin/parser/parse_files.odin @@ -39,7 +39,7 @@ collect_package :: proc(path: string) -> (pkg: ^ast.Package, success: bool) { } file := ast.new(ast.File, NO_POS, NO_POS); file.pkg = pkg; - file.src = src; + file.src = string(src); file.fullpath = fullpath; pkg.files[fullpath] = file; } diff --git a/core/odin/tokenizer/tokenizer.odin b/core/odin/tokenizer/tokenizer.odin index 297f42e3d..f13dd5b7a 100644 --- a/core/odin/tokenizer/tokenizer.odin +++ b/core/odin/tokenizer/tokenizer.odin @@ -14,7 +14,7 @@ Flags :: distinct bit_set[Flag; u32]; Tokenizer :: struct { // Immutable data path: string, - src: []byte, + src: string, err: Error_Handler, flags: Flags, @@ -31,7 +31,7 @@ Tokenizer :: struct { error_count: int, } -init :: proc(t: ^Tokenizer, src: []byte, path: string, err: Error_Handler = default_error_handler) { +init :: proc(t: ^Tokenizer, src: string, path: string, err: Error_Handler = default_error_handler) { t.src = src; t.err = err; t.ch = ' '; @@ -87,7 +87,7 @@ advance_rune :: proc(using t: ^Tokenizer) { case r == 0: error(t, t.offset, "illegal character NUL"); case r >= utf8.RUNE_SELF: - r, w = utf8.decode_rune(src[read_offset:]); + r, w = utf8.decode_rune_in_string(src[read_offset:]); if r == utf8.RUNE_ERROR && w == 1 { error(t, t.offset, "illegal UTF-8 encoding"); } else if r == utf8.RUNE_BOM && offset > 0 { -- cgit v1.2.3 From 9ccdc40f65542702d5a4603f34a468435d97c352 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 16 May 2021 12:43:35 +0100 Subject: Make `.Optional_Semicolons` a flag for the parser --- core/odin/parser/parser.odin | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'core') diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 661c46751..f8373eabe 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -8,13 +8,20 @@ import "core:fmt" Warning_Handler :: #type proc(pos: tokenizer.Pos, fmt: string, args: ..any); Error_Handler :: #type proc(pos: tokenizer.Pos, fmt: string, args: ..any); +Flag :: enum u32 { + Optional_Semicolons, +} + +Flags :: distinct bit_set[Flag; u32]; + + Parser :: struct { file: ^ast.File, tok: tokenizer.Tokenizer, - // If optional_semicolons is true, semicolons are completely as statement terminators + // If .Optional_Semicolons is true, semicolons are completely as statement terminators // different to .Insert_Semicolon in tok.flags - optional_semicolons: bool, + flags: Flags, warn: Warning_Handler, err: Error_Handler, @@ -104,8 +111,9 @@ end_pos :: proc(tok: tokenizer.Token) -> tokenizer.Pos { return pos; } -default_parser :: proc() -> Parser { +default_parser :: proc(flags := Flags{}) -> Parser { return Parser { + flags = flags, err = default_error_handler, warn = default_warning_handler, }; @@ -132,7 +140,7 @@ parse_file :: proc(p: ^Parser, file: ^ast.File) -> bool { p.line_comment = nil; } - if p.optional_semicolons { + if .Optional_Semicolons in p.flags { p.tok.flags += {.Insert_Semicolon}; } @@ -409,7 +417,7 @@ is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool { return false; } - if p.optional_semicolons { + if .Optional_Semicolons in p.flags { return true; } @@ -453,7 +461,7 @@ is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool { } expect_semicolon_newline_error :: proc(p: ^Parser, token: tokenizer.Token, s: ^ast.Node) { - if !p.optional_semicolons && .Insert_Semicolon in p.tok.flags && token.text == "\n" { + if .Optional_Semicolons not_in p.flags && .Insert_Semicolon in p.tok.flags && token.text == "\n" { #partial switch token.kind { case .Close_Brace: case .Close_Paren: -- cgit v1.2.3 From e0225c3579147557ad4fe2241d8fbe61851a6389 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 19 May 2021 10:32:41 +0100 Subject: Add `intrinsics.sqrt` for floating-point values --- core/runtime/internal.odin | 17 ++++++----------- src/check_builtin.cpp | 28 ++++++++++++++++++++++++++++ src/checker_builtin_procs.hpp | 4 ++++ src/llvm_backend.cpp | 22 ++++++++++++++++++++++ 4 files changed, 60 insertions(+), 11 deletions(-) (limited to 'core') diff --git a/core/runtime/internal.odin b/core/runtime/internal.odin index 0e128567a..b95f3e64d 100644 --- a/core/runtime/internal.odin +++ b/core/runtime/internal.odin @@ -409,11 +409,6 @@ string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int return rune(s0&MASK4)<<18 | rune(b1&MASKX)<<12 | rune(b2&MASKX)<<6 | rune(b3&MASKX), 4; } -@(default_calling_convention = "none") -foreign { - @(link_name="llvm.sqrt.f32") _sqrt_f32 :: proc(x: f32) -> f32 --- - @(link_name="llvm.sqrt.f64") _sqrt_f64 :: proc(x: f64) -> f64 --- -} abs_f16 :: #force_inline proc "contextless" (x: f16) -> f16 { return -x if x < 0 else x; } @@ -445,27 +440,27 @@ max_f64 :: proc(a, b: f64) -> f64 { abs_complex32 :: #force_inline proc "contextless" (x: complex32) -> f16 { r, i := real(x), imag(x); - return f16(_sqrt_f32(f32(r*r + i*i))); + return f16(intrinsics.sqrt(f32(r*r + i*i))); } abs_complex64 :: #force_inline proc "contextless" (x: complex64) -> f32 { r, i := real(x), imag(x); - return _sqrt_f32(r*r + i*i); + return intrinsics.sqrt(r*r + i*i); } abs_complex128 :: #force_inline proc "contextless" (x: complex128) -> f64 { r, i := real(x), imag(x); - return _sqrt_f64(r*r + i*i); + return intrinsics.sqrt(r*r + i*i); } abs_quaternion64 :: #force_inline proc "contextless" (x: quaternion64) -> f16 { r, i, j, k := real(x), imag(x), jmag(x), kmag(x); - return f16(_sqrt_f32(f32(r*r + i*i + j*j + k*k))); + return f16(intrinsics.sqrt(f32(r*r + i*i + j*j + k*k))); } abs_quaternion128 :: #force_inline proc "contextless" (x: quaternion128) -> f32 { r, i, j, k := real(x), imag(x), jmag(x), kmag(x); - return _sqrt_f32(r*r + i*i + j*j + k*k); + return intrinsics.sqrt(r*r + i*i + j*j + k*k); } abs_quaternion256 :: #force_inline proc "contextless" (x: quaternion256) -> f64 { r, i, j, k := real(x), imag(x), jmag(x), kmag(x); - return _sqrt_f64(r*r + i*i + j*j + k*k); + return intrinsics.sqrt(r*r + i*i + j*j + k*k); } diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 8932ac914..54cd21582 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -2026,6 +2026,34 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 } break; + case BuiltinProc_sqrt: + { + Operand x = {}; + check_expr(c, &x, ce->args[0]); + if (x.mode == Addressing_Invalid) { + return false; + } + if (!is_type_float(x.type)) { + gbString xts = type_to_string(x.type); + error(x.expr, "Expected a floating point value for '%.*s', got %s", LIT(builtin_procs[id].name), xts); + gb_string_free(xts); + return false; + } + + if (x.mode == Addressing_Constant) { + f64 v = exact_value_to_f64(x.value); + + operand->mode = Addressing_Constant; + operand->type = x.type; + operand->value = exact_value_float(gb_sqrt(v)); + break; + } + operand->mode = Addressing_Value; + operand->type = default_type(x.type); + } + break; + + case BuiltinProc_atomic_fence: case BuiltinProc_atomic_fence_acq: case BuiltinProc_atomic_fence_rel: diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 5b1aecd68..cb864f37d 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -56,6 +56,8 @@ enum BuiltinProcId { BuiltinProc_overflow_sub, BuiltinProc_overflow_mul, + BuiltinProc_sqrt, + BuiltinProc_volatile_store, BuiltinProc_volatile_load, @@ -278,6 +280,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("overflow_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("overflow_mul"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("sqrt"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("volatile_store"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("volatile_load"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index a15b6a172..9dfa123a2 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -9429,6 +9429,28 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, return res; } + case BuiltinProc_sqrt: + { + Type *type = tv.type; + + lbValue x = lb_build_expr(p, ce->args[0]); + x = lb_emit_conv(p, x, type); + + char const *name = "llvm.sqrt"; + LLVMTypeRef types[1] = {lb_type(p->module, type)}; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s.%s", name, LLVMPrintTypeToString(types[0])); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + + LLVMValueRef args[1] = {}; + args[0] = x.value; + + lbValue res = {}; + res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); + res.type = type; + return res; + } + case BuiltinProc_atomic_fence: LLVMBuildFence(p->builder, LLVMAtomicOrderingSequentiallyConsistent, false, ""); -- cgit v1.2.3 From e82e4398b65de36bf68e9f61d634165642b03af1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 19 May 2021 10:50:02 +0100 Subject: Add `intrinsics.mem_copy` and `intrinsics.mem_copy_non_overlapping` --- core/runtime/internal.odin | 24 ++++---------------- src/check_builtin.cpp | 53 +++++++++++++++++++++++++++++++++++++++++++ src/checker_builtin_procs.hpp | 6 +++++ src/llvm_backend.cpp | 38 +++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 20 deletions(-) (limited to 'core') diff --git a/core/runtime/internal.odin b/core/runtime/internal.odin index b95f3e64d..8a7b22ca4 100644 --- a/core/runtime/internal.odin +++ b/core/runtime/internal.odin @@ -105,17 +105,9 @@ mem_copy :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { if src == nil { return dst; } + // NOTE(bill): This _must_ be implemented like C's memmove - foreign _ { - when size_of(rawptr) == 8 { - @(link_name="llvm.memmove.p0i8.p0i8.i64") - llvm_memmove :: proc "none" (dst, src: rawptr, len: int, is_volatile: bool = false) ---; - } else { - @(link_name="llvm.memmove.p0i8.p0i8.i32") - llvm_memmove :: proc "none" (dst, src: rawptr, len: int, is_volatile: bool = false) ---; - } - } - llvm_memmove(dst, src, len); + intrinsics.mem_copy(dst, src, len); return dst; } @@ -123,17 +115,9 @@ mem_copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> r if src == nil { return dst; } + // NOTE(bill): This _must_ be implemented like C's memcpy - foreign _ { - when size_of(rawptr) == 8 { - @(link_name="llvm.memcpy.p0i8.p0i8.i64") - llvm_memcpy :: proc "none" (dst, src: rawptr, len: int, is_volatile: bool = false) ---; - } else { - @(link_name="llvm.memcpy.p0i8.p0i8.i32") - llvm_memcpy :: proc "none" (dst, src: rawptr, len: int, is_volatile: bool = false) ---; - } - } - llvm_memcpy(dst, src, len); + intrinsics.mem_copy_non_overlapping(dst, src, len); return dst; } diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 54cd21582..c9b911f92 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -2053,6 +2053,59 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 } break; + case BuiltinProc_mem_copy: + case BuiltinProc_mem_copy_non_overlapping: + { + operand->mode = Addressing_NoValue; + operand->type = t_invalid; + + Operand dst = {}; + Operand src = {}; + Operand len = {}; + check_expr(c, &dst, ce->args[0]); + check_expr(c, &src, ce->args[1]); + check_expr(c, &len, ce->args[2]); + if (dst.mode == Addressing_Invalid) { + return false; + } + if (src.mode == Addressing_Invalid) { + return false; + } + if (len.mode == Addressing_Invalid) { + return false; + } + + + if (!is_type_pointer(dst.type)) { + gbString str = type_to_string(dst.type); + error(dst.expr, "Expected a pointer value for '%.*s', got %s", LIT(builtin_procs[id].name), str); + gb_string_free(str); + return false; + } + if (!is_type_pointer(src.type)) { + gbString str = type_to_string(src.type); + error(src.expr, "Expected a pointer value for '%.*s', got %s", LIT(builtin_procs[id].name), str); + gb_string_free(str); + return false; + } + if (!is_type_integer(len.type)) { + gbString str = type_to_string(len.type); + error(len.expr, "Expected an integer value for the number of bytes for '%.*s', got %s", LIT(builtin_procs[id].name), str); + gb_string_free(str); + return false; + } + + if (len.mode == Addressing_Constant) { + i64 n = exact_value_to_i64(len.value); + if (n < 0) { + gbString str = expr_to_string(len.expr); + error(len.expr, "Expected a non-negative integer value for the number of bytes for '%.*s', got %s", LIT(builtin_procs[id].name), str); + gb_string_free(str); + } + } + } + break; + case BuiltinProc_atomic_fence: case BuiltinProc_atomic_fence_acq: diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index cb864f37d..b69bacd30 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -58,6 +58,9 @@ enum BuiltinProcId { BuiltinProc_sqrt, + BuiltinProc_mem_copy, + BuiltinProc_mem_copy_non_overlapping, + BuiltinProc_volatile_store, BuiltinProc_volatile_load, @@ -282,6 +285,9 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("sqrt"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("mem_copy"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("mem_copy_non_overlapping"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("volatile_store"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("volatile_load"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 9dfa123a2..b6d1990c3 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -9451,6 +9451,44 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, return res; } + case BuiltinProc_mem_copy: + case BuiltinProc_mem_copy_non_overlapping: + { + + + lbValue dst = lb_build_expr(p, ce->args[0]); + lbValue src = lb_build_expr(p, ce->args[1]); + lbValue len = lb_build_expr(p, ce->args[2]); + dst = lb_emit_conv(p, dst, t_rawptr); + src = lb_emit_conv(p, src, t_rawptr); + len = lb_emit_conv(p, len, t_int); + + char const *name = nullptr; + switch (id) { + case BuiltinProc_mem_copy: name = "llvm.memmove"; break; + case BuiltinProc_mem_copy_non_overlapping: name = "llvm.memcpy"; break; + } + + LLVMTypeRef types[3] = { + lb_type(p->module, t_rawptr), + lb_type(p->module, t_rawptr), + lb_type(p->module, t_int) + }; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s.%s.%s.%s", name, LLVMPrintTypeToString(types[0]), LLVMPrintTypeToString(types[1]), LLVMPrintTypeToString(types[2])); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + + LLVMValueRef args[4] = {}; + args[0] = dst.value; + args[1] = src.value; + args[2] = len.value; + args[3] = LLVMConstInt(LLVMInt1TypeInContext(p->module->ctx), 0, false); // is_volatile parameter + + LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); + + return {}; + } + case BuiltinProc_atomic_fence: LLVMBuildFence(p->builder, LLVMAtomicOrderingSequentiallyConsistent, false, ""); -- cgit v1.2.3 From a580cdbe7b61e9b2d9d7cb7e31dcb83cce88ae1e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 19 May 2021 10:50:27 +0100 Subject: Delete core:sync/sync2/channel* stuff (for the time being) --- core/sync/sync2/channel.odin | 886 ----------------------------------- core/sync/sync2/channel_unix.odin | 17 - core/sync/sync2/channel_windows.odin | 34 -- 3 files changed, 937 deletions(-) delete mode 100644 core/sync/sync2/channel.odin delete mode 100644 core/sync/sync2/channel_unix.odin delete mode 100644 core/sync/sync2/channel_windows.odin (limited to 'core') diff --git a/core/sync/sync2/channel.odin b/core/sync/sync2/channel.odin deleted file mode 100644 index fc30d8280..000000000 --- a/core/sync/sync2/channel.odin +++ /dev/null @@ -1,886 +0,0 @@ -package sync2 - -// TODO(bill): The Channel implementation needs a complete rewrite for this new package sync design -// Especially how the `select` things work - -import "core:mem" -import "core:time" -import "core:math/rand" - -_, _ :: time, rand; - -Channel_Direction :: enum i8 { - Both = 0, - Send = +1, - Recv = -1, -} - -Channel :: struct(T: typeid, Direction := Channel_Direction.Both) { - using _internal: ^Raw_Channel, -} - -channel_init :: proc(ch: ^$C/Channel($T, $D), cap := 0, allocator := context.allocator) { - context.allocator = allocator; - ch._internal = raw_channel_create(size_of(T), align_of(T), cap); - return; -} - -channel_make :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Both)) { - context.allocator = allocator; - ch._internal = raw_channel_create(size_of(T), align_of(T), cap); - return; -} - -channel_make_send :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Send)) { - context.allocator = allocator; - ch._internal = raw_channel_create(size_of(T), align_of(T), cap); - return; -} -channel_make_recv :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Recv)) { - context.allocator = allocator; - ch._internal = raw_channel_create(size_of(T), align_of(T), cap); - return; -} - -channel_destroy :: proc(ch: $C/Channel($T, $D)) { - raw_channel_destroy(ch._internal); -} - -channel_as_send :: proc(ch: $C/Channel($T, .Both)) -> (res: Channel(T, .Send)) { - res._internal = ch._internal; - return; -} - -channel_as_recv :: proc(ch: $C/Channel($T, .Both)) -> (res: Channel(T, .Recv)) { - res._internal = ch._internal; - return; -} - - -channel_len :: proc(ch: $C/Channel($T, $D)) -> int { - return ch._internal.len if ch._internal != nil else 0; -} -channel_cap :: proc(ch: $C/Channel($T, $D)) -> int { - return ch._internal.cap if ch._internal != nil else 0; -} - - -channel_send :: proc(ch: $C/Channel($T, $D), msg: T, loc := #caller_location) where D >= .Both { - msg := msg; - _ = raw_channel_send_impl(ch._internal, &msg, /*block*/true, loc); -} -channel_try_send :: proc(ch: $C/Channel($T, $D), msg: T, loc := #caller_location) -> bool where D >= .Both { - msg := msg; - return raw_channel_send_impl(ch._internal, &msg, /*block*/false, loc); -} - -channel_recv :: proc(ch: $C/Channel($T, $D), loc := #caller_location) -> (msg: T) where D <= .Both { - c := ch._internal; - if c == nil { - panic(message="cannot recv message; channel is nil", loc=loc); - } - mutex_lock(&c.mutex); - raw_channel_recv_impl(c, &msg, loc); - mutex_unlock(&c.mutex); - return; -} -channel_try_recv :: proc(ch: $C/Channel($T, $D), loc := #caller_location) -> (msg: T, ok: bool) where D <= .Both { - c := ch._internal; - if c != nil && mutex_try_lock(&c.mutex) { - if c.len > 0 { - raw_channel_recv_impl(c, &msg, loc); - ok = true; - } - mutex_unlock(&c.mutex); - } - return; -} -channel_try_recv_ptr :: proc(ch: $C/Channel($T, $D), msg: ^T, loc := #caller_location) -> (ok: bool) where D <= .Both { - res: T; - res, ok = channel_try_recv(ch, loc); - if ok && msg != nil { - msg^ = res; - } - return; -} - - -channel_is_nil :: proc(ch: $C/Channel($T, $D)) -> bool { - return ch._internal == nil; -} -channel_is_open :: proc(ch: $C/Channel($T, $D)) -> bool { - c := ch._internal; - return c != nil && !c.closed; -} - - -channel_eq :: proc(a, b: $C/Channel($T, $D)) -> bool { - return a._internal == b._internal; -} -channel_ne :: proc(a, b: $C/Channel($T, $D)) -> bool { - return a._internal != b._internal; -} - - -channel_can_send :: proc(ch: $C/Channel($T, $D)) -> (ok: bool) where D >= .Both { - return raw_channel_can_send(ch._internal); -} -channel_can_recv :: proc(ch: $C/Channel($T, $D)) -> (ok: bool) where D <= .Both { - return raw_channel_can_recv(ch._internal); -} - - -channel_peek :: proc(ch: $C/Channel($T, $D)) -> int { - c := ch._internal; - if c == nil { - return -1; - } - if atomic_load(&c.closed) { - return -1; - } - return atomic_load(&c.len); -} - - -channel_close :: proc(ch: $C/Channel($T, $D), loc := #caller_location) { - raw_channel_close(ch._internal, loc); -} - - -channel_iterator :: proc(ch: $C/Channel($T, $D)) -> (msg: T, ok: bool) where D <= .Both { - c := ch._internal; - if c == nil { - return; - } - - if !c.closed || c.len > 0 { - msg, ok = channel_recv(ch), true; - } - return; -} -channel_drain :: proc(ch: $C/Channel($T, $D)) where D >= .Both { - raw_channel_drain(ch._internal); -} - - -channel_move :: proc(dst: $C1/Channel($T, $D1) src: $C2/Channel(T, $D2)) where D1 <= .Both, D2 >= .Both { - for msg in channel_iterator(src) { - channel_send(dst, msg); - } -} - - -Raw_Channel_Wait_Queue :: struct { - next: ^Raw_Channel_Wait_Queue, - state: ^uintptr, -} - - -Raw_Channel :: struct { - closed: bool, - ready: bool, // ready to recv - data_offset: u16, // data is stored at the end of this data structure - elem_size: u32, - len, cap: int, - read, write: int, - mutex: Mutex, - cond: Cond, - allocator: mem.Allocator, - - sendq: ^Raw_Channel_Wait_Queue, - recvq: ^Raw_Channel_Wait_Queue, -} - -raw_channel_wait_queue_insert :: proc(head: ^^Raw_Channel_Wait_Queue, val: ^Raw_Channel_Wait_Queue) { - val.next = head^; - head^ = val; -} -raw_channel_wait_queue_remove :: proc(head: ^^Raw_Channel_Wait_Queue, val: ^Raw_Channel_Wait_Queue) { - p := head; - for p^ != nil && p^ != val { - p = &p^.next; - } - if p != nil { - p^ = p^.next; - } -} - - -raw_channel_create :: proc(elem_size, elem_align: int, cap := 0) -> ^Raw_Channel { - assert(int(u32(elem_size)) == elem_size); - - s := size_of(Raw_Channel); - s = mem.align_forward_int(s, elem_align); - data_offset := uintptr(s); - s += elem_size * max(cap, 1); - - a := max(elem_align, align_of(Raw_Channel)); - - c := (^Raw_Channel)(mem.alloc(s, a)); - if c == nil { - return nil; - } - - c.data_offset = u16(data_offset); - c.elem_size = u32(elem_size); - c.len, c.cap = 0, max(cap, 0); - c.read, c.write = 0, 0; - c.allocator = context.allocator; - c.closed = false; - - return c; -} - - -raw_channel_destroy :: proc(c: ^Raw_Channel) { - if c == nil { - return; - } - context.allocator = c.allocator; - atomic_store(&c.closed, true); - free(c); -} - -raw_channel_close :: proc(c: ^Raw_Channel, loc := #caller_location) { - if c == nil { - panic(message="cannot close nil channel", loc=loc); - } - mutex_lock(&c.mutex); - defer mutex_unlock(&c.mutex); - atomic_store(&c.closed, true); - - // Release readers and writers - raw_channel_wait_queue_broadcast(c.recvq); - raw_channel_wait_queue_broadcast(c.sendq); - cond_broadcast(&c.cond); -} - - - -raw_channel_send_impl :: proc(c: ^Raw_Channel, msg: rawptr, block: bool, loc := #caller_location) -> bool { - send :: proc(c: ^Raw_Channel, src: rawptr) { - data := uintptr(c) + uintptr(c.data_offset); - dst := data + uintptr(c.write * int(c.elem_size)); - mem.copy(rawptr(dst), src, int(c.elem_size)); - c.len += 1; - c.write = (c.write + 1) % max(c.cap, 1); - } - - switch { - case c == nil: - panic(message="cannot send message; channel is nil", loc=loc); - case c.closed: - panic(message="cannot send message; channel is closed", loc=loc); - } - - mutex_lock(&c.mutex); - defer mutex_unlock(&c.mutex); - - if c.cap > 0 { - if !block && c.len >= c.cap { - return false; - } - - for c.len >= c.cap { - cond_wait(&c.cond, &c.mutex); - } - } else if c.len > 0 { // TODO(bill): determine correct behaviour - if !block { - return false; - } - cond_wait(&c.cond, &c.mutex); - } else if c.len == 0 && !block { - return false; - } - - send(c, msg); - cond_signal(&c.cond); - raw_channel_wait_queue_signal(c.recvq); - - return true; -} - -raw_channel_recv_impl :: proc(c: ^Raw_Channel, res: rawptr, loc := #caller_location) { - recv :: proc(c: ^Raw_Channel, dst: rawptr, loc := #caller_location) { - if c.len < 1 { - panic(message="cannot recv message; channel is empty", loc=loc); - } - c.len -= 1; - - data := uintptr(c) + uintptr(c.data_offset); - src := data + uintptr(c.read * int(c.elem_size)); - mem.copy(dst, rawptr(src), int(c.elem_size)); - c.read = (c.read + 1) % max(c.cap, 1); - } - - if c == nil { - panic(message="cannot recv message; channel is nil", loc=loc); - } - atomic_store(&c.ready, true); - for c.len < 1 { - raw_channel_wait_queue_signal(c.sendq); - cond_wait(&c.cond, &c.mutex); - } - atomic_store(&c.ready, false); - recv(c, res, loc); - if c.cap > 0 { - if c.len == c.cap - 1 { - // NOTE(bill): Only signal on the last one - cond_signal(&c.cond); - } - } else { - cond_signal(&c.cond); - } -} - - -raw_channel_can_send :: proc(c: ^Raw_Channel) -> (ok: bool) { - if c == nil { - return false; - } - mutex_lock(&c.mutex); - switch { - case c.closed: - ok = false; - case c.cap > 0: - ok = c.ready && c.len < c.cap; - case: - ok = c.ready && c.len == 0; - } - mutex_unlock(&c.mutex); - return; -} -raw_channel_can_recv :: proc(c: ^Raw_Channel) -> (ok: bool) { - if c == nil { - return false; - } - mutex_lock(&c.mutex); - ok = c.len > 0; - mutex_unlock(&c.mutex); - return; -} - - -raw_channel_drain :: proc(c: ^Raw_Channel) { - if c == nil { - return; - } - mutex_lock(&c.mutex); - c.len = 0; - c.read = 0; - c.write = 0; - mutex_unlock(&c.mutex); -} - - - -MAX_SELECT_CHANNELS :: 64; -SELECT_MAX_TIMEOUT :: max(time.Duration); - -Select_Command :: enum { - Recv, - Send, -} - -Select_Channel :: struct { - channel: ^Raw_Channel, - command: Select_Command, -} - - - -select :: proc(channels: ..Select_Channel) -> (index: int) { - return select_timeout(SELECT_MAX_TIMEOUT, ..channels); -} -select_timeout :: proc(timeout: time.Duration, channels: ..Select_Channel) -> (index: int) { - switch len(channels) { - case 0: - panic("sync: select with no channels"); - } - - assert(len(channels) <= MAX_SELECT_CHANNELS); - - backing: [MAX_SELECT_CHANNELS]int; - queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue; - candidates := backing[:]; - cap := len(channels); - candidates = candidates[:cap]; - - count := u32(0); - for c, i in channels { - if c.channel == nil { - continue; - } - switch c.command { - case .Recv: - if raw_channel_can_recv(c.channel) { - candidates[count] = i; - count += 1; - } - case .Send: - if raw_channel_can_send(c.channel) { - candidates[count] = i; - count += 1; - } - } - } - - if count == 0 { - wait_state: uintptr = 0; - for _, i in channels { - q := &queues[i]; - q.state = &wait_state; - } - - for c, i in channels { - if c.channel == nil { - continue; - } - q := &queues[i]; - switch c.command { - case .Recv: raw_channel_wait_queue_insert(&c.channel.recvq, q); - case .Send: raw_channel_wait_queue_insert(&c.channel.sendq, q); - } - } - raw_channel_wait_queue_wait_on(&wait_state, timeout); - for c, i in channels { - if c.channel == nil { - continue; - } - q := &queues[i]; - switch c.command { - case .Recv: raw_channel_wait_queue_remove(&c.channel.recvq, q); - case .Send: raw_channel_wait_queue_remove(&c.channel.sendq, q); - } - } - - for c, i in channels { - switch c.command { - case .Recv: - if raw_channel_can_recv(c.channel) { - candidates[count] = i; - count += 1; - } - case .Send: - if raw_channel_can_send(c.channel) { - candidates[count] = i; - count += 1; - } - } - } - if count == 0 && timeout == SELECT_MAX_TIMEOUT { - index = -1; - return; - } - - assert(count != 0); - } - - t := time.now(); - r := rand.create(transmute(u64)t); - i := rand.uint32(&r); - - index = candidates[i % count]; - return; -} - -select_recv :: proc(channels: ..^Raw_Channel) -> (index: int) { - switch len(channels) { - case 0: - panic("sync: select with no channels"); - } - - assert(len(channels) <= MAX_SELECT_CHANNELS); - - backing: [MAX_SELECT_CHANNELS]int; - queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue; - candidates := backing[:]; - cap := len(channels); - candidates = candidates[:cap]; - - count := u32(0); - for c, i in channels { - if raw_channel_can_recv(c) { - candidates[count] = i; - count += 1; - } - } - - if count == 0 { - state: uintptr; - for c, i in channels { - q := &queues[i]; - q.state = &state; - raw_channel_wait_queue_insert(&c.recvq, q); - } - raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT); - for c, i in channels { - q := &queues[i]; - raw_channel_wait_queue_remove(&c.recvq, q); - } - - for c, i in channels { - if raw_channel_can_recv(c) { - candidates[count] = i; - count += 1; - } - } - assert(count != 0); - } - - t := time.now(); - r := rand.create(transmute(u64)t); - i := rand.uint32(&r); - - index = candidates[i % count]; - return; -} - -select_recv_msg :: proc(channels: ..$C/Channel($T, $D)) -> (msg: T, index: int) { - switch len(channels) { - case 0: - panic("sync: select with no channels"); - } - - assert(len(channels) <= MAX_SELECT_CHANNELS); - - queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue; - candidates: [MAX_SELECT_CHANNELS]int; - - count := u32(0); - for c, i in channels { - if raw_channel_can_recv(c) { - candidates[count] = i; - count += 1; - } - } - - if count == 0 { - state: uintptr; - for c, i in channels { - q := &queues[i]; - q.state = &state; - raw_channel_wait_queue_insert(&c.recvq, q); - } - raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT); - for c, i in channels { - q := &queues[i]; - raw_channel_wait_queue_remove(&c.recvq, q); - } - - for c, i in channels { - if raw_channel_can_recv(c) { - candidates[count] = i; - count += 1; - } - } - assert(count != 0); - } - - t := time.now(); - r := rand.create(transmute(u64)t); - i := rand.uint32(&r); - - index = candidates[i % count]; - msg = channel_recv(channels[index]); - - return; -} - -select_send_msg :: proc(msg: $T, channels: ..$C/Channel(T, $D)) -> (index: int) { - switch len(channels) { - case 0: - panic("sync: select with no channels"); - } - - assert(len(channels) <= MAX_SELECT_CHANNELS); - - backing: [MAX_SELECT_CHANNELS]int; - queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue; - candidates := backing[:]; - cap := len(channels); - candidates = candidates[:cap]; - - count := u32(0); - for c, i in channels { - if raw_channel_can_recv(c) { - candidates[count] = i; - count += 1; - } - } - - if count == 0 { - state: uintptr; - for c, i in channels { - q := &queues[i]; - q.state = &state; - raw_channel_wait_queue_insert(&c.recvq, q); - } - raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT); - for c, i in channels { - q := &queues[i]; - raw_channel_wait_queue_remove(&c.recvq, q); - } - - for c, i in channels { - if raw_channel_can_recv(c) { - candidates[count] = i; - count += 1; - } - } - assert(count != 0); - } - - t := time.now(); - r := rand.create(transmute(u64)t); - i := rand.uint32(&r); - - index = candidates[i % count]; - - if msg != nil { - channel_send(channels[index], msg); - } - - return; -} - -select_send :: proc(channels: ..^Raw_Channel) -> (index: int) { - switch len(channels) { - case 0: - panic("sync: select with no channels"); - } - - assert(len(channels) <= MAX_SELECT_CHANNELS); - candidates: [MAX_SELECT_CHANNELS]int; - queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue; - - count := u32(0); - for c, i in channels { - if raw_channel_can_send(c) { - candidates[count] = i; - count += 1; - } - } - - if count == 0 { - state: uintptr; - for c, i in channels { - q := &queues[i]; - q.state = &state; - raw_channel_wait_queue_insert(&c.sendq, q); - } - raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT); - for c, i in channels { - q := &queues[i]; - raw_channel_wait_queue_remove(&c.sendq, q); - } - - for c, i in channels { - if raw_channel_can_send(c) { - candidates[count] = i; - count += 1; - } - } - assert(count != 0); - } - - t := time.now(); - r := rand.create(transmute(u64)t); - i := rand.uint32(&r); - - index = candidates[i % count]; - return; -} - -select_try :: proc(channels: ..Select_Channel) -> (index: int) { - switch len(channels) { - case 0: - panic("sync: select with no channels"); - } - - assert(len(channels) <= MAX_SELECT_CHANNELS); - - backing: [MAX_SELECT_CHANNELS]int; - candidates := backing[:]; - cap := len(channels); - candidates = candidates[:cap]; - - count := u32(0); - for c, i in channels { - switch c.command { - case .Recv: - if raw_channel_can_recv(c.channel) { - candidates[count] = i; - count += 1; - } - case .Send: - if raw_channel_can_send(c.channel) { - candidates[count] = i; - count += 1; - } - } - } - - if count == 0 { - index = -1; - return; - } - - t := time.now(); - r := rand.create(transmute(u64)t); - i := rand.uint32(&r); - - index = candidates[i % count]; - return; -} - - -select_try_recv :: proc(channels: ..^Raw_Channel) -> (index: int) { - switch len(channels) { - case 0: - index = -1; - return; - case 1: - index = -1; - if raw_channel_can_recv(channels[0]) { - index = 0; - } - return; - } - - assert(len(channels) <= MAX_SELECT_CHANNELS); - candidates: [MAX_SELECT_CHANNELS]int; - - count := u32(0); - for c, i in channels { - if raw_channel_can_recv(c) { - candidates[count] = i; - count += 1; - } - } - - if count == 0 { - index = -1; - return; - } - - t := time.now(); - r := rand.create(transmute(u64)t); - i := rand.uint32(&r); - - index = candidates[i % count]; - return; -} - - -select_try_send :: proc(channels: ..^Raw_Channel) -> (index: int) #no_bounds_check { - switch len(channels) { - case 0: - return -1; - case 1: - if raw_channel_can_send(channels[0]) { - return 0; - } - return -1; - } - - assert(len(channels) <= MAX_SELECT_CHANNELS); - candidates: [MAX_SELECT_CHANNELS]int; - - count := u32(0); - for c, i in channels { - if raw_channel_can_send(c) { - candidates[count] = i; - count += 1; - } - } - - if count == 0 { - index = -1; - return; - } - - t := time.now(); - r := rand.create(transmute(u64)t); - i := rand.uint32(&r); - - index = candidates[i % count]; - return; -} - -select_try_recv_msg :: proc(channels: ..$C/Channel($T, $D)) -> (msg: T, index: int) { - switch len(channels) { - case 0: - index = -1; - return; - case 1: - ok: bool; - if msg, ok = channel_try_recv(channels[0]); ok { - index = 0; - } - return; - } - - assert(len(channels) <= MAX_SELECT_CHANNELS); - candidates: [MAX_SELECT_CHANNELS]int; - - count := u32(0); - for c, i in channels { - if channel_can_recv(c) { - candidates[count] = i; - count += 1; - } - } - - if count == 0 { - index = -1; - return; - } - - t := time.now(); - r := rand.create(transmute(u64)t); - i := rand.uint32(&r); - - index = candidates[i % count]; - msg = channel_recv(channels[index]); - return; -} - -select_try_send_msg :: proc(msg: $T, channels: ..$C/Channel(T, $D)) -> (index: int) { - index = -1; - switch len(channels) { - case 0: - return; - case 1: - if channel_try_send(channels[0], msg) { - index = 0; - } - return; - } - - - assert(len(channels) <= MAX_SELECT_CHANNELS); - candidates: [MAX_SELECT_CHANNELS]int; - - count := u32(0); - for c, i in channels { - if raw_channel_can_send(c) { - candidates[count] = i; - count += 1; - } - } - - if count == 0 { - index = -1; - return; - } - - t := time.now(); - r := rand.create(transmute(u64)t); - i := rand.uint32(&r); - - index = candidates[i % count]; - channel_send(channels[index], msg); - return; -} - diff --git a/core/sync/sync2/channel_unix.odin b/core/sync/sync2/channel_unix.odin deleted file mode 100644 index 7429b67db..000000000 --- a/core/sync/sync2/channel_unix.odin +++ /dev/null @@ -1,17 +0,0 @@ -//+build linux, darwin, freebsd -//+private -package sync2 - -import "core:time" - -raw_channel_wait_queue_wait_on :: proc(state: ^uintptr, timeout: time.Duration) { - // stub -} - -raw_channel_wait_queue_signal :: proc(q: ^Raw_Channel_Wait_Queue) { - // stub -} - -raw_channel_wait_queue_broadcast :: proc(q: ^Raw_Channel_Wait_Queue) { - // stub -} diff --git a/core/sync/sync2/channel_windows.odin b/core/sync/sync2/channel_windows.odin deleted file mode 100644 index e365506c8..000000000 --- a/core/sync/sync2/channel_windows.odin +++ /dev/null @@ -1,34 +0,0 @@ -//+build windows -//+private -package sync2 - -import win32 "core:sys/windows" -import "core:time" - -raw_channel_wait_queue_wait_on :: proc(state: ^uintptr, timeout: time.Duration) { - ms: win32.DWORD = win32.INFINITE; - if max(time.Duration) != SELECT_MAX_TIMEOUT { - ms = win32.DWORD((max(time.duration_nanoseconds(timeout), 0) + 999999)/1000000); - } - - v := atomic_load(state); - for v == 0 { - win32.WaitOnAddress(state, &v, size_of(state^), ms); - v = atomic_load(state); - } - atomic_store(state, 0); -} - -raw_channel_wait_queue_signal :: proc(q: ^Raw_Channel_Wait_Queue) { - for x := q; x != nil; x = x.next { - atomic_add(x.state, 1); - win32.WakeByAddressSingle(x.state); - } -} - -raw_channel_wait_queue_broadcast :: proc(q: ^Raw_Channel_Wait_Queue) { - for x := q; x != nil; x = x.next { - atomic_add(x.state, 1); - win32.WakeByAddressAll(x.state); - } -} -- cgit v1.2.3 From 26ce40c188c31539cbf7f97cf2ad1bb81000bc55 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 19 May 2021 11:51:48 +0100 Subject: Remove @(static) for global variables --- core/math/rand/rand.odin | 4 ++-- core/strings/builder.odin | 2 +- core/unicode/tables.odin | 10 ---------- src/check_decl.cpp | 5 ++--- src/llvm_backend.cpp | 2 +- 5 files changed, 6 insertions(+), 17 deletions(-) (limited to 'core') diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin index 4f6e7474f..f5558bb8c 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -6,9 +6,9 @@ Rand :: struct { } -@(private, static) +@(private) _GLOBAL_SEED_DATA := 1234567890; -@(private, static) +@(private) global_rand := create(u64(uintptr(&_GLOBAL_SEED_DATA))); set_global_seed :: proc(seed: u64) { diff --git a/core/strings/builder.odin b/core/strings/builder.odin index dd7fd4f1e..843f79381 100644 --- a/core/strings/builder.odin +++ b/core/strings/builder.odin @@ -221,7 +221,7 @@ pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) { } -@(private, static) +@(private) DIGITS_LOWER := "0123456789abcdefx"; write_quoted_string :: proc{ diff --git a/core/unicode/tables.odin b/core/unicode/tables.odin index bb858fd04..ff4793402 100644 --- a/core/unicode/tables.odin +++ b/core/unicode/tables.odin @@ -12,7 +12,6 @@ package unicode @(private) pLo :: pLl | pLu; // a letter that is neither upper nor lower case. @(private) pLmask :: pLo; -@(static) char_properties := [MAX_LATIN1+1]u8{ 0x00 = pC, // '\x00' 0x01 = pC, // '\x01' @@ -273,7 +272,6 @@ char_properties := [MAX_LATIN1+1]u8{ }; -@(static) alpha_ranges := [?]i32{ 0x00d8, 0x00f6, 0x00f8, 0x01f5, @@ -429,7 +427,6 @@ alpha_ranges := [?]i32{ 0xffda, 0xffdc, }; -@(static) alpha_singlets := [?]i32{ 0x00aa, 0x00b5, @@ -465,7 +462,6 @@ alpha_singlets := [?]i32{ 0xfe74, }; -@(static) space_ranges := [?]i32{ 0x0009, 0x000d, // tab and newline 0x0020, 0x0020, // space @@ -481,7 +477,6 @@ space_ranges := [?]i32{ 0xfeff, 0xfeff, }; -@(static) unicode_spaces := [?]i32{ 0x0009, // tab 0x000a, // LF @@ -499,7 +494,6 @@ unicode_spaces := [?]i32{ 0xfeff, // unknown }; -@(static) to_upper_ranges := [?]i32{ 0x0061, 0x007a, 468, // a-z A-Z 0x00e0, 0x00f6, 468, @@ -538,7 +532,6 @@ to_upper_ranges := [?]i32{ 0xff41, 0xff5a, 468, }; -@(static) to_upper_singlets := [?]i32{ 0x00ff, 621, 0x0101, 499, @@ -882,7 +875,6 @@ to_upper_singlets := [?]i32{ 0x1ff3, 509, }; -@(static) to_lower_ranges := [?]i32{ 0x0041, 0x005a, 532, // A-Z a-z 0x00c0, 0x00d6, 532, // - - @@ -922,7 +914,6 @@ to_lower_ranges := [?]i32{ 0xff21, 0xff3a, 532, // - - }; -@(static) to_lower_singlets := [?]i32{ 0x0100, 501, 0x0102, 501, @@ -1259,7 +1250,6 @@ to_lower_singlets := [?]i32{ 0x1ffc, 491, }; -@(static) to_title_singlets := [?]i32{ 0x01c4, 501, 0x01c6, 499, diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 5e8e79791..0aef40546 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -899,10 +899,9 @@ void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast *type_expr, e->Variable.thread_local_model = ac.thread_local_model; e->Variable.is_export = ac.is_export; + e->flags &= ~EntityFlag_Static; if (ac.is_static) { - e->flags |= EntityFlag_Static; - } else { - e->flags &= ~EntityFlag_Static; + error(e->token, "@(static) is not supported for global variables, nor required"); } ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix); diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index a0f22be44..8bd5aaa37 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -14910,7 +14910,7 @@ void lb_generate_code(lbGenerator *gen) { LLVMMetadataRef llvm_file = lb_get_llvm_metadata(m, e->file); LLVMMetadataRef llvm_scope = llvm_file; - LLVMBool local_to_unit = e->flags & EntityFlag_Static; + LLVMBool local_to_unit = LLVMGetLinkage(g.value) == LLVMInternalLinkage; LLVMMetadataRef llvm_expr = LLVMDIBuilderCreateExpression(m->debug_builder, nullptr, 0); LLVMMetadataRef llvm_decl = nullptr; -- cgit v1.2.3