diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2bd3ca9ad..3cc4283b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,9 @@ jobs: cd tests/vendor make timeout-minutes: 10 + - name: Odin issues tests + run: tests/issues/run.sh + timeout-minutes: 10 - name: Odin check examples/all for Linux i386 run: ./odin check examples/all -vet -strict-style -target:linux_i386 timeout-minutes: 10 @@ -87,6 +90,9 @@ jobs: cd tests/vendor make timeout-minutes: 10 + - name: Odin issues tests + run: tests/issues/run.sh + timeout-minutes: 10 - name: Odin check examples/all for Darwin arm64 run: ./odin check examples/all -vet -strict-style -target:darwin_arm64 timeout-minutes: 10 @@ -153,6 +159,12 @@ jobs: cd tests\core\math\big call build.bat timeout-minutes: 10 + - name: Odin issues tests + shell: cmd + run: | + call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat + call tests\issues\run.bat + timeout-minutes: 10 - name: Odin check examples/all for Windows 32bits shell: cmd run: | diff --git a/Makefile b/Makefile index bea0569a9..82150c6a2 100644 --- a/Makefile +++ b/Makefile @@ -1,74 +1,3 @@ -GIT_SHA=$(shell git rev-parse --short HEAD) -DISABLED_WARNINGS=-Wno-switch -Wno-macro-redefined -Wno-unused-value -LDFLAGS=-pthread -lm -lstdc++ -CFLAGS=-std=c++14 -DGIT_SHA=\"$(GIT_SHA)\" -CFLAGS:=$(CFLAGS) -DODIN_VERSION_RAW=\"dev-$(shell date +"%Y-%m")\" -CC=clang - -OS=$(shell uname) - -ifeq ($(OS), Darwin) - - ARCH=$(shell uname -m) - LLVM_CONFIG=llvm-config - - # allow for arm only llvm's with version 13 - ifeq ($(ARCH), arm64) - LLVM_VERSIONS = "13.%.%" - else - # allow for x86 / amd64 all llvm versions begining from 11 - LLVM_VERSIONS = "13.%.%" "12.0.1" "11.1.0" - endif - - LLVM_VERSION_PATTERN_SEPERATOR = )|( - LLVM_VERSION_PATTERNS_ESCAPED_DOT = $(subst .,\.,$(LLVM_VERSIONS)) - LLVM_VERSION_PATTERNS_REPLACE_PERCENT = $(subst %,.*,$(LLVM_VERSION_PATTERNS_ESCAPED_DOT)) - LLVM_VERSION_PATTERN_REMOVE_ELEMENTS = $(subst " ",$(LLVM_VERSION_PATTERN_SEPERATOR),$(LLVM_VERSION_PATTERNS_REPLACE_PERCENT)) - LLMV_VERSION_PATTERN_REMOVE_SINGLE_STR = $(subst ",,$(LLVM_VERSION_PATTERN_REMOVE_ELEMENTS)) - LLVM_VERSION_PATTERN = "^(($(LLMV_VERSION_PATTERN_REMOVE_SINGLE_STR)))" - - ifeq ($(shell $(LLVM_CONFIG) --version | grep -E $(LLVM_VERSION_PATTERN)),) - ifeq ($(ARCH), arm64) - $(error "Requirement: llvm-config must be base version 13 for arm64") - else - $(error "Requirement: llvm-config must be base version greater than 11 for amd64/x86") - endif - endif - - LDFLAGS:=$(LDFLAGS) -liconv -ldl - CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags) - LDFLAGS:=$(LDFLAGS) -lLLVM-C -endif -ifeq ($(OS), Linux) - LLVM_CONFIG=llvm-config-11 - ifneq ($(shell which llvm-config-11 2>/dev/null),) - LLVM_CONFIG=llvm-config-11 - else ifneq ($(shell which llvm-config-11-64 2>/dev/null),) - LLVM_CONFIG=llvm-config-11-64 - else - ifeq ($(shell $(LLVM_CONFIG) --version | grep '^11\.'),) - $(error "Requirement: llvm-config must be version 11") - endif - endif - - LDFLAGS:=$(LDFLAGS) -ldl - CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags) - LDFLAGS:=$(LDFLAGS) $(shell $(LLVM_CONFIG) --libs core native --system-libs) -endif -ifeq ($(OS), OpenBSD) - LLVM_CONFIG=/usr/local/bin/llvm-config - - LDFLAGS:=$(LDFLAGS) -liconv - CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags) - LDFLAGS:=$(LDFLAGS) $(shell $(LLVM_CONFIG) --libs core native --system-libs) -endif -ifeq ($(OS), FreeBSD) - LLVM_CONFIG=/usr/local/bin/llvm-config11 - - CFLAGS:=$(CFLAGS) $(shell $(LLVM_CONFIG) --cxxflags --ldflags) - LDFLAGS:=$(LDFLAGS) $(shell $(LLVM_CONFIG) --libs core native --system-libs) -endif - all: debug demo demo: @@ -78,13 +7,13 @@ report: ./odin report debug: - $(CC) src/main.cpp src/libtommath.cpp $(DISABLED_WARNINGS) $(CFLAGS) -g $(LDFLAGS) -o odin + ./build_odin.sh debug release: - $(CC) src/main.cpp src/libtommath.cpp $(DISABLED_WARNINGS) $(CFLAGS) -O3 $(LDFLAGS) -o odin + ./build_odin.sh release release_native: - $(CC) src/main.cpp src/libtommath.cpp $(DISABLED_WARNINGS) $(CFLAGS) -O3 -march=native $(LDFLAGS) -o odin + ./build_odin.sh release-native nightly: - $(CC) src/main.cpp src/libtommath.cpp $(DISABLED_WARNINGS) $(CFLAGS) -DNIGHTLY -O3 $(LDFLAGS) -o odin + ./build_odin.sh nightly diff --git a/build_odin.sh b/build_odin.sh index a323782a1..dd2725f5d 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -41,6 +41,13 @@ config_darwin() { LDFLAGS="$LDFLAGS -lLLVM-C" } +config_freebsd() { + LLVM_CONFIG=/usr/local/bin/llvm-config11 + + CFLAGS="$CFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" + LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" +} + config_openbsd() { LLVM_CONFIG=/usr/local/bin/llvm-config @@ -108,6 +115,9 @@ Darwin) OpenBSD) config_openbsd ;; +FreeBSD) + config_freebsd + ;; *) panic "Platform unsupported!" esac diff --git a/core/bufio/scanner.odin b/core/bufio/scanner.odin index cc3b4533a..86e6d8eb0 100644 --- a/core/bufio/scanner.odin +++ b/core/bufio/scanner.odin @@ -8,6 +8,7 @@ import "core:intrinsics" // Extra errors returns by scanning procedures Scanner_Extra_Error :: enum i32 { + None, Negative_Advance, Advanced_Too_Far, Bad_Read_Count, @@ -15,7 +16,7 @@ Scanner_Extra_Error :: enum i32 { Too_Short, } -Scanner_Error :: union { +Scanner_Error :: union #shared_nil { io.Error, Scanner_Extra_Error, } @@ -68,7 +69,7 @@ scanner_destroy :: proc(s: ^Scanner) { // Returns the first non-EOF error that was encounted by the scanner scanner_error :: proc(s: ^Scanner) -> Scanner_Error { switch s._err { - case .EOF, .None: + case .EOF, nil: return nil } return s._err @@ -93,10 +94,6 @@ scanner_text :: proc(s: ^Scanner) -> string { // scanner_scan advances the scanner scanner_scan :: proc(s: ^Scanner) -> bool { set_err :: proc(s: ^Scanner, err: Scanner_Error) { - err := err - if err == .None { - err = nil - } switch s._err { case nil, .EOF: s._err = err diff --git a/core/c/libc/stdatomic.odin b/core/c/libc/stdatomic.odin index fbe1ef7ea..9de3c4678 100644 --- a/core/c/libc/stdatomic.odin +++ b/core/c/libc/stdatomic.odin @@ -47,24 +47,25 @@ kill_dependency :: #force_inline proc(value: $T) -> T { // 7.17.4 Fences atomic_thread_fence :: #force_inline proc(order: memory_order) { - switch (order) { - case .relaxed: - return - case .consume: - intrinsics.atomic_fence_acq() - case .acquire: - intrinsics.atomic_fence_acq() - case .release: - intrinsics.atomic_fence_rel() - case .acq_rel: - intrinsics.atomic_fence_acqrel() - case .seq_cst: - intrinsics.atomic_fence_acqrel() + assert(order != .relaxed) + assert(order != .consume) + #partial switch order { + case .acquire: intrinsics.atomic_thread_fence(.Acquire) + case .release: intrinsics.atomic_thread_fence(.Release) + case .acq_rel: intrinsics.atomic_thread_fence(.Acq_Rel) + case .seq_cst: intrinsics.atomic_thread_fence(.Seq_Cst) } } atomic_signal_fence :: #force_inline proc(order: memory_order) { - atomic_thread_fence(order) + assert(order != .relaxed) + assert(order != .consume) + #partial switch order { + case .acquire: intrinsics.atomic_signal_fence(.Acquire) + case .release: intrinsics.atomic_signal_fence(.Release) + case .acq_rel: intrinsics.atomic_signal_fence(.Acq_Rel) + case .seq_cst: intrinsics.atomic_signal_fence(.Seq_Cst) + } } // 7.17.5 Lock-free property @@ -121,13 +122,10 @@ atomic_store_explicit :: #force_inline proc(object: ^$T, desired: T, order: memo assert(order != .acquire) assert(order != .acq_rel) - #partial switch (order) { - case .relaxed: - intrinsics.atomic_store_relaxed(object, desired) - case .release: - intrinsics.atomic_store_rel(object, desired) - case .seq_cst: - intrinsics.atomic_store(object, desired) + #partial switch order { + case .relaxed: intrinsics.atomic_store_explicit(object, desired, .Relaxed) + case .release: intrinsics.atomic_store_explicit(object, desired, .Release) + case .seq_cst: intrinsics.atomic_store_explicit(object, desired, .Seq_Cst) } } @@ -139,36 +137,26 @@ atomic_load_explicit :: #force_inline proc(object: ^$T, order: memory_order) { assert(order != .release) assert(order != .acq_rel) - #partial switch (order) { - case .relaxed: - return intrinsics.atomic_load_relaxed(object) - case .consume: - return intrinsics.atomic_load_acq(object) - case .acquire: - return intrinsics.atomic_load_acq(object) - case .seq_cst: - return intrinsics.atomic_load(object) + #partial switch order { + case .relaxed: return intrinsics.atomic_load_explicit(object, .Relaxed) + case .consume: return intrinsics.atomic_load_explicit(object, .Consume) + case .acquire: return intrinsics.atomic_load_explicit(object, .Acquire) + case .seq_cst: return intrinsics.atomic_load_explicit(object, .Seq_Cst) } } atomic_exchange :: #force_inline proc(object: ^$T, desired: T) -> T { - return intrinsics.atomic_xchg(object, desired) + return intrinsics.atomic_exchange(object, desired) } atomic_exchange_explicit :: #force_inline proc(object: ^$T, desired: T, order: memory_order) -> T { - switch (order) { - case .relaxed: - return intrinsics.atomic_xchg_relaxed(object, desired) - case .consume: - return intrinsics.atomic_xchg_acq(object, desired) - case .acquire: - return intrinsics.atomic_xchg_acq(object, desired) - case .release: - return intrinsics.atomic_xchg_rel(object, desired) - case .acq_rel: - return intrinsics.atomic_xchg_acqrel(object, desired) - case .seq_cst: - return intrinsics.atomic_xchg(object, desired) + switch order { + case .relaxed: return intrinsics.atomic_exchange_explicit(object, desired, .Relaxed) + case .consume: return intrinsics.atomic_exchange_explicit(object, desired, .Consume) + case .acquire: return intrinsics.atomic_exchange_explicit(object, desired, .Acquire) + case .release: return intrinsics.atomic_exchange_explicit(object, desired, .Release) + case .acq_rel: return intrinsics.atomic_exchange_explicit(object, desired, .Acq_Rel) + case .seq_cst: return intrinsics.atomic_exchange_explicit(object, desired, .Seq_Cst) } return false } @@ -189,102 +177,104 @@ atomic_exchange_explicit :: #force_inline proc(object: ^$T, desired: T, order: m // [success = seq_cst, failure = acquire] => failacq // [success = acquire, failure = relaxed] => acq_failrelaxed // [success = acq_rel, failure = relaxed] => acqrel_failrelaxed -atomic_compare_exchange_strong :: #force_inline proc(object, expected: ^$T, desired: T) { - value, ok := intrinsics.atomic_cxchg(object, expected^, desired) +atomic_compare_exchange_strong :: #force_inline proc(object, expected: ^$T, desired: T) -> bool { + value, ok := intrinsics.atomic_compare_exchange_strong(object, expected^, desired) if !ok { expected^ = value } return ok } -atomic_compare_exchange_strong_explicit :: #force_inline proc(object, expected: ^$T, desired: T, success, failure: memory_order) { +atomic_compare_exchange_strong_explicit :: #force_inline proc(object, expected: ^$T, desired: T, success, failure: memory_order) -> bool { assert(failure != .release) assert(failure != .acq_rel) value: T; ok: bool - #partial switch (failure) { + #partial switch failure { case .seq_cst: assert(success != .relaxed) - #partial switch (success) { + #partial switch success { case .seq_cst: - value, ok := intrinsics.atomic_cxchg(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Seq_Cst, .Seq_Cst) case .acquire: - value, ok := intrinsics.atomic_cxchg_acq(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Acquire, .Seq_Cst) case .consume: - value, ok := intrinsics.atomic_cxchg_acq(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Consume, .Seq_Cst) case .release: - value, ok := intrinsics.atomic_cxchg_rel(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Release, .Seq_Cst) case .acq_rel: - value, ok := intrinsics.atomic_cxchg_acqrel(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Acq_Rel, .Seq_Cst) } case .relaxed: assert(success != .release) - #partial switch (success) { + #partial switch success { case .relaxed: - value, ok := intrinsics.atomic_cxchg_relaxed(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Relaxed, .Relaxed) case .seq_cst: - value, ok := intrinsics.atomic_cxchg_failrelaxed(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Seq_Cst, .Relaxed) case .acquire: - value, ok := intrinsics.atomic_cxchg_acq_failrelaxed(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Acquire, .Relaxed) case .consume: - value, ok := intrinsics.atomic_cxchg_acq_failrelaxed(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Consume, .Relaxed) case .acq_rel: - value, ok := intrinsics.atomic_cxchg_acqrel_failrelaxed(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Acq_Rel, .Relaxed) } case .consume: - fallthrough + assert(success == .seq_cst) + value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Seq_Cst, .Consume) case .acquire: assert(success == .seq_cst) - value, ok := intrinsics.atomic_cxchg_failacq(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_strong_explicit(object, expected^, desired, .Seq_Cst, .Acquire) } if !ok { expected^ = value } return ok } -atomic_compare_exchange_weak :: #force_inline proc(object, expected: ^$T, desired: T) { - value, ok := intrinsics.atomic_cxchgweak(object, expected^, desired) +atomic_compare_exchange_weak :: #force_inline proc(object, expected: ^$T, desired: T) -> bool { + value, ok := intrinsics.atomic_compare_exchange_weak(object, expected^, desired) if !ok { expected^ = value } return ok } -atomic_compare_exchange_weak_explicit :: #force_inline proc(object, expected: ^$T, desited: T, success, failure: memory_order) { +atomic_compare_exchange_weak_explicit :: #force_inline proc(object, expected: ^$T, desited: T, success, failure: memory_order) -> bool { assert(failure != .release) assert(failure != .acq_rel) value: T; ok: bool - #partial switch (failure) { + #partial switch failure { case .seq_cst: assert(success != .relaxed) - #partial switch (success) { + #partial switch success { case .seq_cst: - value, ok := intrinsics.atomic_cxchgweak(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Seq_Cst, .Seq_Cst) case .acquire: - value, ok := intrinsics.atomic_cxchgweak_acq(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Acquire, .Seq_Cst) case .consume: - value, ok := intrinsics.atomic_cxchgweak_acq(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Consume, .Seq_Cst) case .release: - value, ok := intrinsics.atomic_cxchgweak_rel(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Release, .Seq_Cst) case .acq_rel: - value, ok := intrinsics.atomic_cxchgweak_acqrel(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Acq_Rel, .Seq_Cst) } case .relaxed: assert(success != .release) - #partial switch (success) { + #partial switch success { case .relaxed: - value, ok := intrinsics.atomic_cxchgweak_relaxed(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Relaxed, .Relaxed) case .seq_cst: - value, ok := intrinsics.atomic_cxchgweak_failrelaxed(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Seq_Cst, .Relaxed) case .acquire: - value, ok := intrinsics.atomic_cxchgweak_acq_failrelaxed(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Acquire, .Relaxed) case .consume: - value, ok := intrinsics.atomic_cxchgweak_acq_failrelaxed(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Consume, .Relaxed) case .acq_rel: - value, ok := intrinsics.atomic_cxchgweak_acqrel_failrelaxed(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Acq_Rel, .Relaxed) } case .consume: - fallthrough + assert(success == .seq_cst) + value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Seq_Cst, .Consume) case .acquire: assert(success == .seq_cst) - value, ok := intrinsics.atomic_cxchgweak_failacq(object, expected^, desired) + value, ok = intrinsics.atomic_compare_exchange_weak_explicit(object, expected^, desired, .Seq_Cst, .Acquire) } if !ok { expected^ = value } @@ -297,19 +287,14 @@ atomic_fetch_add :: #force_inline proc(object: ^$T, operand: T) -> T { } atomic_fetch_add_explicit :: #force_inline proc(object: ^$T, operand: T, order: memory_order) -> T { - switch (order) { - case .relaxed: - return intrinsics.atomic_add_relaxed(object, operand) - case .consume: - return intrinsics.atomic_add_acq(object, operand) - case .acquire: - return intrinsics.atomic_add_acq(object, operand) - case .release: - return intrinsics.atomic_add_rel(object, operand) - case .acq_rel: - return intrinsics.atomic_add_acqrel(object, operand) - case .seq_cst: - return intrinsics.atomic_add(object, operand) + switch order { + case .relaxed: return intrinsics.atomic_add_explicit(object, operand, .Relaxed) + case .consume: return intrinsics.atomic_add_explicit(object, operand, .Consume) + case .acquire: return intrinsics.atomic_add_explicit(object, operand, .Acquire) + case .release: return intrinsics.atomic_add_explicit(object, operand, .Release) + case .acq_rel: return intrinsics.atomic_add_explicit(object, operand, .Acq_Rel) + case: fallthrough + case .seq_cst: return intrinsics.atomic_add_explicit(object, operand, .Seq_Cst) } } @@ -318,19 +303,14 @@ atomic_fetch_sub :: #force_inline proc(object: ^$T, operand: T) -> T { } atomic_fetch_sub_explicit :: #force_inline proc(object: ^$T, operand: T, order: memory_order) -> T { - switch (order) { - case .relaxed: - return intrinsics.atomic_sub_relaxed(object, operand) - case .consume: - return intrinsics.atomic_sub_acq(object, operand) - case .acquire: - return intrinsics.atomic_sub_acq(object, operand) - case .release: - return intrinsics.atomic_sub_rel(object, operand) - case .acq_rel: - return intrinsics.atomic_sub_acqrel(object, operand) - case .seq_cst: - return intrinsics.atomic_sub(object, operand) + switch order { + case .relaxed: return intrinsics.atomic_sub_explicit(object, operand, .Relaxed) + case .consume: return intrinsics.atomic_sub_explicit(object, operand, .Consume) + case .acquire: return intrinsics.atomic_sub_explicit(object, operand, .Acquire) + case .release: return intrinsics.atomic_sub_explicit(object, operand, .Release) + case .acq_rel: return intrinsics.atomic_sub_explicit(object, operand, .Acq_Rel) + case: fallthrough + case .seq_cst: return intrinsics.atomic_sub_explicit(object, operand, .Seq_Cst) } } @@ -339,19 +319,14 @@ atomic_fetch_or :: #force_inline proc(object: ^$T, operand: T) -> T { } atomic_fetch_or_explicit :: #force_inline proc(object: ^$T, operand: T, order: memory_order) -> T { - switch (order) { - case .relaxed: - return intrinsics.atomic_or_relaxed(object, operand) - case .consume: - return intrinsics.atomic_or_acq(object, operand) - case .acquire: - return intrinsics.atomic_or_acq(object, operand) - case .release: - return intrinsics.atomic_or_rel(object, operand) - case .acq_rel: - return intrinsics.atomic_or_acqrel(object, operand) - case .seq_cst: - return intrinsics.atomic_or(object, operand) + switch order { + case .relaxed: return intrinsics.atomic_or_explicit(object, operand, .Relaxed) + case .consume: return intrinsics.atomic_or_explicit(object, operand, .Consume) + case .acquire: return intrinsics.atomic_or_explicit(object, operand, .Acquire) + case .release: return intrinsics.atomic_or_explicit(object, operand, .Release) + case .acq_rel: return intrinsics.atomic_or_explicit(object, operand, .Acq_Rel) + case: fallthrough + case .seq_cst: return intrinsics.atomic_or_explicit(object, operand, .Seq_Cst) } } @@ -360,19 +335,14 @@ atomic_fetch_xor :: #force_inline proc(object: ^$T, operand: T) -> T { } atomic_fetch_xor_explicit :: #force_inline proc(object: ^$T, operand: T, order: memory_order) -> T { - switch (order) { - case .relaxed: - return intrinsics.atomic_xor_relaxed(object, operand) - case .consume: - return intrinsics.atomic_xor_acq(object, operand) - case .acquire: - return intrinsics.atomic_xor_acq(object, operand) - case .release: - return intrinsics.atomic_xor_rel(object, operand) - case .acq_rel: - return intrinsics.atomic_xor_acqrel(object, operand) - case .seq_cst: - return intrinsics.atomic_xor(object, operand) + switch order { + case .relaxed: return intrinsics.atomic_xor_explicit(object, operand, .Relaxed) + case .consume: return intrinsics.atomic_xor_explicit(object, operand, .Consume) + case .acquire: return intrinsics.atomic_xor_explicit(object, operand, .Acquire) + case .release: return intrinsics.atomic_xor_explicit(object, operand, .Release) + case .acq_rel: return intrinsics.atomic_xor_explicit(object, operand, .Acq_Rel) + case: fallthrough + case .seq_cst: return intrinsics.atomic_xor_explicit(object, operand, .Seq_Cst) } } @@ -380,19 +350,14 @@ atomic_fetch_and :: #force_inline proc(object: ^$T, operand: T) -> T { return intrinsics.atomic_and(object, operand) } atomic_fetch_and_explicit :: #force_inline proc(object: ^$T, operand: T, order: memory_order) -> T { - switch (order) { - case .relaxed: - return intrinsics.atomic_and_relaxed(object, operand) - case .consume: - return intrinsics.atomic_and_acq(object, operand) - case .acquire: - return intrinsics.atomic_and_acq(object, operand) - case .release: - return intrinsics.atomic_and_rel(object, operand) - case .acq_rel: - return intrinsics.atomic_and_acqrel(object, operand) - case .seq_cst: - return intrinsics.atomic_and(object, operand) + switch order { + case .relaxed: return intrinsics.atomic_and_explicit(object, operand, .Relaxed) + case .consume: return intrinsics.atomic_and_explicit(object, operand, .Consume) + case .acquire: return intrinsics.atomic_and_explicit(object, operand, .Acquire) + case .release: return intrinsics.atomic_and_explicit(object, operand, .Release) + case .acq_rel: return intrinsics.atomic_and_explicit(object, operand, .Acq_Rel) + case: fallthrough + case .seq_cst: return intrinsics.atomic_and_explicit(object, operand, .Seq_Cst) } } diff --git a/core/compress/common.odin b/core/compress/common.odin index fb0f74e95..58aeac25b 100644 --- a/core/compress/common.odin +++ b/core/compress/common.odin @@ -47,7 +47,7 @@ when size_of(uintptr) == 8 { } -Error :: union { +Error :: union #shared_nil { General_Error, Deflate_Error, ZLIB_Error, @@ -58,6 +58,7 @@ Error :: union { } General_Error :: enum { + None = 0, File_Not_Found, Cannot_Open_File, File_Too_Short, @@ -76,6 +77,7 @@ General_Error :: enum { } GZIP_Error :: enum { + None = 0, Invalid_GZIP_Signature, Reserved_Flag_Set, Invalid_Extra_Data, @@ -100,6 +102,7 @@ GZIP_Error :: enum { } ZIP_Error :: enum { + None = 0, Invalid_ZIP_File_Signature, Unexpected_Signature, Insert_Next_Disk, @@ -107,6 +110,7 @@ ZIP_Error :: enum { } ZLIB_Error :: enum { + None = 0, Unsupported_Window_Size, FDICT_Unsupported, Unsupported_Compression_Level, @@ -114,6 +118,7 @@ ZLIB_Error :: enum { } Deflate_Error :: enum { + None = 0, Huffman_Bad_Sizes, Huffman_Bad_Code_Lengths, Inflate_Error, diff --git a/core/compress/zlib/zlib.odin b/core/compress/zlib/zlib.odin index d4b770929..855eef7a8 100644 --- a/core/compress/zlib/zlib.odin +++ b/core/compress/zlib/zlib.odin @@ -47,10 +47,10 @@ Options :: struct { level: u8, } -Error :: compress.Error -E_General :: compress.General_Error -E_ZLIB :: compress.ZLIB_Error -E_Deflate :: compress.Deflate_Error +Error :: compress.Error +General_Error :: compress.General_Error +ZLIB_Error :: compress.ZLIB_Error +Deflate_Error :: compress.Deflate_Error DEFLATE_MAX_CHUNK_SIZE :: 65535 DEFLATE_MAX_LITERAL_SIZE :: 65535 @@ -258,7 +258,7 @@ build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) { for i in 1 ..< HUFFMAN_MAX_BITS { if sizes[i] > (1 << uint(i)) { - return E_Deflate.Huffman_Bad_Sizes + return .Huffman_Bad_Sizes } } code := int(0) @@ -270,7 +270,7 @@ build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) { code = code + sizes[i] if sizes[i] != 0 { if code - 1 >= (1 << u16(i)) { - return E_Deflate.Huffman_Bad_Code_Lengths + return .Huffman_Bad_Code_Lengths } } z.maxcode[i] = code << (HUFFMAN_MAX_BITS - uint(i)) @@ -314,15 +314,15 @@ decode_huffman_slowpath :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Erro s += 1 } if s >= 16 { - return 0, E_Deflate.Bad_Huffman_Code + return 0, .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 + return 0, .Bad_Huffman_Code } if t.size[b] != s { - return 0, E_Deflate.Bad_Huffman_Code + return 0, .Bad_Huffman_Code } compress.consume_bits_lsb(z, s) @@ -335,11 +335,11 @@ decode_huffman_slowpath :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Erro decode_huffman :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check { if z.num_bits < 16 { if z.num_bits > 63 { - return 0, E_ZLIB.Code_Buffer_Malformed + return 0, .Code_Buffer_Malformed } compress.refill_lsb(z) if z.num_bits > 63 { - return 0, E_General.Stream_Too_Short + return 0, .Stream_Too_Short } } #no_bounds_check b := t.fast[z.code_buffer & ZFAST_MASK] @@ -361,7 +361,7 @@ parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err: if value < 256 { e := write_byte(z, u8(value)) if e != .None { - return E_General.Output_Too_Short + return .Output_Too_Short } } else { if value == 256 { @@ -377,7 +377,7 @@ parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err: value, e = decode_huffman(z, z_offset) if e != nil { - return E_Deflate.Bad_Huffman_Code + return .Bad_Huffman_Code } distance := Z_DIST_BASE[value] @@ -387,7 +387,7 @@ parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err: if z.bytes_written < i64(distance) { // Distance is longer than we've decoded so far. - return E_Deflate.Bad_Distance + return .Bad_Distance } /* @@ -405,14 +405,14 @@ parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err: c := z.output.buf[z.bytes_written - i64(distance)] e := repl_byte(z, length, c) if e != .None { - return E_General.Output_Too_Short + return .Output_Too_Short } } } else { if length > 0 { e := repl_bytes(z, length, distance) if e != .None { - return E_General.Output_Too_Short + return .Output_Too_Short } } } @@ -432,25 +432,25 @@ inflate_from_context :: proc(using ctx: ^compress.Context_Memory_Input, raw := f if !raw { size, size_err := compress.input_size(ctx) if size < 6 || size_err != nil { - return E_General.Stream_Too_Short + return .Stream_Too_Short } cmf, _ := compress.read_u8(ctx) method := Compression_Method(cmf & 0xf) if method != .DEFLATE { - return E_General.Unknown_Compression_Method + return .Unknown_Compression_Method } if cinfo := (cmf >> 4) & 0xf; cinfo > 7 { - return E_ZLIB.Unsupported_Window_Size + return .Unsupported_Window_Size } flg, _ := compress.read_u8(ctx) fcheck := flg & 0x1f fcheck_computed := (cmf << 8 | flg) & 0x1f if fcheck != fcheck_computed { - return E_General.Checksum_Failed + return .Checksum_Failed } /* @@ -458,7 +458,7 @@ inflate_from_context :: proc(using ctx: ^compress.Context_Memory_Input, raw := f They're application specific and PNG doesn't use them. */ if fdict := (flg >> 5) & 1; fdict != 0 { - return E_ZLIB.FDICT_Unsupported + return .FDICT_Unsupported } // flevel := Compression_Level((flg >> 6) & 3); @@ -485,7 +485,7 @@ inflate_from_context :: proc(using ctx: ^compress.Context_Memory_Input, raw := f output_hash := hash.adler32(ctx.output.buf[:]) if output_hash != u32(adler) { - return E_General.Checksum_Failed + return .Checksum_Failed } } return nil @@ -555,7 +555,7 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all if ~uncompressed_len != length_check { - return E_Deflate.Len_Nlen_Mismatch + return .Len_Nlen_Mismatch } /* @@ -571,7 +571,7 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all assert(uncompressed_len == 0) case 3: - return E_Deflate.BType_3 + return .BType_3 case: // fmt.printf("Err: %v | Final: %v | Type: %v\n", err, final, type) if type == 1 { @@ -604,7 +604,7 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all c = decode_huffman(z, codelength_ht) or_return if c < 0 || c >= 19 { - return E_Deflate.Huffman_Bad_Code_Lengths + return .Huffman_Bad_Code_Lengths } if c < 16 { lencodes[n] = u8(c) @@ -616,7 +616,7 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all case 16: c = u16(compress.read_bits_no_refill_lsb(z, 2) + 3) if n == 0 { - return E_Deflate.Huffman_Bad_Code_Lengths + return .Huffman_Bad_Code_Lengths } fill = lencodes[n - 1] case 17: @@ -624,11 +624,11 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all case 18: c = u16(compress.read_bits_no_refill_lsb(z, 7) + 11) case: - return E_Deflate.Huffman_Bad_Code_Lengths + return .Huffman_Bad_Code_Lengths } if ntot - n < u32(c) { - return E_Deflate.Huffman_Bad_Code_Lengths + return .Huffman_Bad_Code_Lengths } nc := n + u32(c) @@ -639,7 +639,7 @@ inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.all } if n != ntot { - return E_Deflate.Huffman_Bad_Code_Lengths + return .Huffman_Bad_Code_Lengths } build_huffman(z_repeat, lencodes[:hlit]) or_return diff --git a/core/encoding/hxa/read.odin b/core/encoding/hxa/read.odin index ef7edc8b7..abe295530 100644 --- a/core/encoding/hxa/read.odin +++ b/core/encoding/hxa/read.odin @@ -39,6 +39,9 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato read_value :: proc(r: ^Reader, $T: typeid) -> (value: T, err: Read_Error) { remaining := len(r.data) - r.offset if remaining < size_of(T) { + if r.print_error { + fmt.eprintf("file '%s' failed to read value at offset %v\n", r.filename, r.offset) + } err = .Short_Read return } @@ -51,6 +54,10 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato read_array :: proc(r: ^Reader, $T: typeid, count: int) -> (value: []T, err: Read_Error) { remaining := len(r.data) - r.offset if remaining < size_of(T)*count { + if r.print_error { + fmt.eprintf("file '%s' failed to read array of %d elements at offset %v\n", + r.filename, count, r.offset) + } err = .Short_Read return } @@ -82,7 +89,8 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato type := read_value(r, Meta_Value_Type) or_return if type > max(Meta_Value_Type) { if r.print_error { - fmt.eprintf("HxA Error: file '%s' has meta value type %d. Maximum value is ", r.filename, u8(type), u8(max(Meta_Value_Type))) + fmt.eprintf("HxA Error: file '%s' has meta value type %d. Maximum value is %d\n", + r.filename, u8(type), u8(max(Meta_Value_Type))) } err = .Invalid_Data return @@ -114,7 +122,8 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato type := read_value(r, Layer_Data_Type) or_return if type > max(type) { if r.print_error { - fmt.eprintf("HxA Error: file '%s' has layer data type %d. Maximum value is ", r.filename, u8(type), u8(max(Layer_Data_Type))) + fmt.eprintf("HxA Error: file '%s' has layer data type %d. Maximum value is %d\n", + r.filename, u8(type), u8(max(Layer_Data_Type))) } err = .Invalid_Data return @@ -134,13 +143,23 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato } if len(data) < size_of(Header) { + if print_error { + fmt.eprintf("HxA Error: file '%s' has no header\n", filename) + } + err = .Short_Read return } context.allocator = allocator header := cast(^Header)raw_data(data) - assert(header.magic_number == MAGIC_NUMBER) + if (header.magic_number != MAGIC_NUMBER) { + if print_error { + fmt.eprintf("HxA Error: file '%s' has invalid magic number 0x%x\n", filename, header.magic_number) + } + err = .Invalid_Data + return + } r := &Reader{ filename = filename, @@ -150,6 +169,7 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato } node_count := 0 + file.header = header^ file.nodes = make([]Node, header.internal_node_count) defer if err != nil { nodes_destroy(file.nodes) @@ -162,7 +182,8 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato type := read_value(r, Node_Type) or_return if type > max(Node_Type) { if r.print_error { - fmt.eprintf("HxA Error: file '%s' has node type %d. Maximum value is ", r.filename, u8(type), u8(max(Node_Type))) + fmt.eprintf("HxA Error: file '%s' has node type %d. Maximum value is %d\n", + r.filename, u8(type), u8(max(Node_Type))) } err = .Invalid_Data return diff --git a/core/encoding/hxa/write.odin b/core/encoding/hxa/write.odin index e774018b2..5bb950e81 100644 --- a/core/encoding/hxa/write.odin +++ b/core/encoding/hxa/write.odin @@ -84,7 +84,7 @@ write_internal :: proc(w: ^Writer, file: File) { write_metadata :: proc(w: ^Writer, meta_data: []Meta) { for m in meta_data { - name_len := max(len(m.name), 255) + name_len := min(len(m.name), 255) write_value(w, u8(name_len)) write_string(w, m.name[:name_len]) @@ -127,7 +127,7 @@ write_internal :: proc(w: ^Writer, file: File) { write_layer_stack :: proc(w: ^Writer, layers: Layer_Stack) { write_value(w, u32(len(layers))) for layer in layers { - name_len := max(len(layer.name), 255) + name_len := min(len(layer.name), 255) write_value(w, u8(name_len)) write_string(w, layer .name[:name_len]) @@ -152,7 +152,7 @@ write_internal :: proc(w: ^Writer, file: File) { return } - write_value(w, &Header{ + write_value(w, Header{ magic_number = MAGIC_NUMBER, version = LATEST_VERSION, internal_node_count = u32le(len(file.nodes)), diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index aa1c1559c..9c54f35f0 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -8,17 +8,18 @@ import "core:strings" import "core:io" Marshal_Data_Error :: enum { + None, Unsupported_Type, } -Marshal_Error :: union { +Marshal_Error :: union #shared_nil { Marshal_Data_Error, io.Error, } marshal :: proc(v: any, allocator := context.allocator) -> (data: []byte, err: Marshal_Error) { b := strings.make_builder(allocator) - defer if err != .None { + defer if err != nil { strings.destroy_builder(&b) } @@ -27,7 +28,7 @@ marshal :: proc(v: any, allocator := context.allocator) -> (data: []byte, err: M if len(b.buf) != 0 { data = b.buf[:] } - return data, .None + return data, nil } marshal_to_builder :: proc(b: ^strings.Builder, v: any) -> Marshal_Error { diff --git a/core/image/common.odin b/core/image/common.odin index d72b770d5..adaa094d8 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -118,7 +118,7 @@ Option :: enum { } Options :: distinct bit_set[Option] -Error :: union { +Error :: union #shared_nil { General_Image_Error, PNG_Error, @@ -137,6 +137,7 @@ General_Image_Error :: enum { } PNG_Error :: enum { + None = 0, Invalid_PNG_Signature, IHDR_Not_First_Chunk, IHDR_Corrupt, diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index 6ec674544..c64e6f471 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -439,7 +439,7 @@ when false { 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 + return .Invalid_Image_Dimensions } mode: int = 0 @@ -450,7 +450,7 @@ when false { fd, fderr := open(filename, flags, mode) if fderr != 0 { - return E_General.Cannot_Open_File + return .Cannot_Open_File } defer close(fd) @@ -473,7 +473,7 @@ when false { 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 + return .Unknown_Color_Type } h := make_chunk(ihdr, .IHDR) write_chunk(fd, h) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index c3ef787bc..ce8fd96ea 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -62,77 +62,44 @@ syscall :: proc(id: uintptr, args: ..uintptr) -> uintptr --- // Atomics -atomic_fence :: proc() --- -atomic_fence_acq :: proc() --- -atomic_fence_rel :: proc() --- -atomic_fence_acqrel :: proc() --- +Atomic_Memory_Order :: enum { + Relaxed = 0, // Unordered + Consume = 1, // Monotonic + Acquire = 2, + Release = 3, + Acq_Rel = 4, + Seq_Cst = 5, +} -atomic_store :: proc(dst: ^$T, val: T) --- -atomic_store_rel :: proc(dst: ^$T, val: T) --- -atomic_store_relaxed :: proc(dst: ^$T, val: T) --- -atomic_store_unordered :: proc(dst: ^$T, val: T) --- +atomic_thread_fence :: proc(order: Atomic_Memory_Order) --- +atomic_signal_fence :: proc(order: Atomic_Memory_Order) --- + +atomic_store :: proc(dst: ^$T, val: T) --- +atomic_store_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) --- atomic_load :: proc(dst: ^$T) -> T --- -atomic_load_acq :: proc(dst: ^$T) -> T --- -atomic_load_relaxed :: proc(dst: ^$T) -> T --- -atomic_load_unordered :: proc(dst: ^$T) -> T --- +atomic_load_explicit :: proc(dst: ^$T, order: Atomic_Memory_Order) -> T --- -atomic_add :: proc(dst; ^$T, val: T) -> T --- -atomic_add_acq :: proc(dst; ^$T, val: T) -> T --- -atomic_add_rel :: proc(dst; ^$T, val: T) -> T --- -atomic_add_acqrel :: proc(dst; ^$T, val: T) -> T --- -atomic_add_relaxed :: proc(dst; ^$T, val: T) -> T --- -atomic_sub :: proc(dst; ^$T, val: T) -> T --- -atomic_sub_acq :: proc(dst; ^$T, val: T) -> T --- -atomic_sub_rel :: proc(dst; ^$T, val: T) -> T --- -atomic_sub_acqrel :: proc(dst; ^$T, val: T) -> T --- -atomic_sub_relaxed :: proc(dst; ^$T, val: T) -> T --- -atomic_and :: proc(dst; ^$T, val: T) -> T --- -atomic_and_acq :: proc(dst; ^$T, val: T) -> T --- -atomic_and_rel :: proc(dst; ^$T, val: T) -> T --- -atomic_and_acqrel :: proc(dst; ^$T, val: T) -> T --- -atomic_and_relaxed :: proc(dst; ^$T, val: T) -> T --- -atomic_nand :: proc(dst; ^$T, val: T) -> T --- -atomic_nand_acq :: proc(dst; ^$T, val: T) -> T --- -atomic_nand_rel :: proc(dst; ^$T, val: T) -> T --- -atomic_nand_acqrel :: proc(dst; ^$T, val: T) -> T --- -atomic_nand_relaxed :: proc(dst; ^$T, val: T) -> T --- -atomic_or :: proc(dst; ^$T, val: T) -> T --- -atomic_or_acq :: proc(dst; ^$T, val: T) -> T --- -atomic_or_rel :: proc(dst; ^$T, val: T) -> T --- -atomic_or_acqrel :: proc(dst; ^$T, val: T) -> T --- -atomic_or_relaxed :: proc(dst; ^$T, val: T) -> T --- -atomic_xor :: proc(dst; ^$T, val: T) -> T --- -atomic_xor_acq :: proc(dst; ^$T, val: T) -> T --- -atomic_xor_rel :: proc(dst; ^$T, val: T) -> T --- -atomic_xor_acqrel :: proc(dst; ^$T, val: T) -> T --- -atomic_xor_relaxed :: proc(dst; ^$T, val: T) -> T --- +atomic_add :: proc(dst; ^$T, val: T) -> T --- +atomic_add_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_sub :: proc(dst; ^$T, val: T) -> T --- +atomic_sub_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_and :: proc(dst; ^$T, val: T) -> T --- +atomic_and_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_nand :: proc(dst; ^$T, val: T) -> T --- +atomic_nand_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_or :: proc(dst; ^$T, val: T) -> T --- +atomic_or_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_xor :: proc(dst; ^$T, val: T) -> T --- +atomic_xor_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_exchange :: proc(dst; ^$T, val: T) -> T --- +atomic_exchange_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- -atomic_xchg :: proc(dst; ^$T, val: T) -> T --- -atomic_xchg_acq :: proc(dst; ^$T, val: T) -> T --- -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_compare_exchange_strong :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_compare_exchange_strong_explicit :: proc(dst: ^$T, old, new: T, success, failure: Atomic_Memory_Order) -> (T, bool) #optional_ok --- +atomic_compare_exchange_weak :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- +atomic_compare_exchange_weak_explicit :: proc(dst: ^$T, old, new: T, success, failure: Atomic_Memory_Order) -> (T, bool) #optional_ok --- -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 @@ -201,6 +168,10 @@ type_equal_proc :: proc($T: typeid) -> (equal: proc "contextless" (rawptr, raw type_hasher_proc :: proc($T: typeid) -> (hasher: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr) where type_is_comparable(T) --- +// WASM targets only +wasm_memory_grow :: proc(index, delta: uintptr) -> int --- +wasm_memory_size :: proc(index: uintptr) -> int --- + // Internal compiler use only __entry_point :: proc() --- \ No newline at end of file diff --git a/core/math/ease/ease.odin b/core/math/ease/ease.odin new file mode 100644 index 000000000..5a767b5a9 --- /dev/null +++ b/core/math/ease/ease.odin @@ -0,0 +1,483 @@ +// easing procedures and flux easing used for animations +package ease + +import "core:math" +import "core:intrinsics" +import "core:time" + +@(private) PI_2 :: math.PI / 2 + +// converted to odin from https://github.com/warrenm/AHEasing +// with additional enum based call + +// Modeled after the parabola y = x^2 +quadratic_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + return p * p +} + +// Modeled after the parabola y = -x^2 + 2x +quadratic_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + return -(p * (p - 2)) +} + +// Modeled after the piecewise quadratic +// y = (1/2)((2x)^2) ; [0, 0.5) +// y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1] +quadratic_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + if p < 0.5 { + return 2 * p * p + } else { + return (-2 * p * p) + (4 * p) - 1 + } +} + +// Modeled after the cubic y = x^3 +cubic_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + return p * p * p +} + +// Modeled after the cubic y = (x - 1)^3 + 1 +cubic_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + f := p - 1 + return f * f * f + 1 +} + +// Modeled after the piecewise cubic +// y = (1/2)((2x)^3) ; [0, 0.5) +// y = (1/2)((2x-2)^3 + 2) ; [0.5, 1] +cubic_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + if p < 0.5 { + return 4 * p * p * p + } else { + f := (2 * p) - 2 + return 0.5 * f * f * f + 1 + } +} + +// Modeled after the quartic x^4 +quartic_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + return p * p * p * p +} + +// Modeled after the quartic y = 1 - (x - 1)^4 +quartic_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + f := p - 1 + return f * f * f * (1 - p) + 1 +} + +// Modeled after the piecewise quartic +// y = (1/2)((2x)^4) ; [0, 0.5) +// y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1] +quartic_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + if p < 0.5 { + return 8 * p * p * p * p + } else { + f := p - 1 + return -8 * f * f * f * f + 1 + } +} + +// Modeled after the quintic y = x^5 +quintic_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + return p * p * p * p * p +} + +// Modeled after the quintic y = (x - 1)^5 + 1 +quintic_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + f := p - 1 + return f * f * f * f * f + 1 +} + +// Modeled after the piecewise quintic +// y = (1/2)((2x)^5) ; [0, 0.5) +// y = (1/2)((2x-2)^5 + 2) ; [0.5, 1] +quintic_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + if p < 0.5 { + return 16 * p * p * p * p * p + } else { + f := (2 * p) - 2 + return 0.5 * f * f * f * f * f + 1 + } +} + +// Modeled after quarter-cycle of sine wave +sine_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + return math.sin((p - 1) * PI_2) + 1 +} + +// Modeled after quarter-cycle of sine wave (different phase) +sine_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + return math.sin(p * PI_2) +} + +// Modeled after half sine wave +sine_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + return 0.5 * (1 - math.cos(p * math.PI)) +} + +// Modeled after shifted quadrant IV of unit circle +circular_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + return 1 - math.sqrt(1 - (p * p)) +} + +// Modeled after shifted quadrant II of unit circle +circular_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + return math.sqrt((2 - p) * p) +} + +// Modeled after the piecewise circular function +// y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5) +// y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1] +circular_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + if p < 0.5 { + return 0.5 * (1 - math.sqrt(1 - 4 * (p * p))) + } else { + return 0.5 * (math.sqrt(-((2 * p) - 3) * ((2 * p) - 1)) + 1) + } +} + +// Modeled after the exponential function y = 2^(10(x - 1)) +exponential_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + return p == 0.0 ? p : math.pow(2, 10 * (p - 1)) +} + +// Modeled after the exponential function y = -2^(-10x) + 1 +exponential_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + return p == 1.0 ? p : 1 - math.pow(2, -10 * p) +} + +// Modeled after the piecewise exponential +// y = (1/2)2^(10(2x - 1)) ; [0,0.5) +// y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1] +exponential_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + if p == 0.0 || p == 1.0 { + return p + } + + if p < 0.5 { + return 0.5 * math.pow(2, (20 * p) - 10) + } else { + return -0.5 * math.pow(2, (-20 * p) + 10) + 1 + } +} + +// Modeled after the damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1)) +elastic_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + return math.sin(13 * PI_2 * p) * math.pow(2, 10 * (p - 1)) +} + +// Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1 +elastic_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + return math.sin(-13 * PI_2 * (p + 1)) * math.pow(2, -10 * p) + 1 +} + +// Modeled after the piecewise exponentially-damped sine wave: +// y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5) +// y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1] +elastic_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + if p < 0.5 { + return 0.5 * math.sin(13 * PI_2 * (2 * p)) * math.pow(2, 10 * ((2 * p) - 1)) + } else { + return 0.5 * (math.sin(-13 * PI_2 * ((2 * p - 1) + 1)) * math.pow(2, -10 * (2 * p - 1)) + 2) + } +} + +// Modeled after the overshooting cubic y = x^3-x*sin(x*pi) +back_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + return p * p * p - p * math.sin(p * math.PI) +} + +// Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi)) +back_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + f := 1 - p + return 1 - (f * f * f - f * math.sin(f * math.PI)) +} + +// Modeled after the piecewise overshooting cubic function: +// y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5) +// y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1] +back_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + if p < 0.5 { + f := 2 * p + return 0.5 * (f * f * f - f * math.sin(f * math.PI)) + } else { + f := (1 - (2*p - 1)) + return 0.5 * (1 - (f * f * f - f * math.sin(f * math.PI))) + 0.5 + } +} + +bounce_in :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + return 1 - bounce_out(1 - p) +} + +bounce_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + if p < 4/11.0 { + return (121 * p * p)/16.0 + } else if p < 8/11.0 { + return (363/40.0 * p * p) - (99/10.0 * p) + 17/5.0 + } else if p < 9/10.0 { + return (4356/361.0 * p * p) - (35442/1805.0 * p) + 16061/1805.0 + } else { + return (54/5.0 * p * p) - (513/25.0 * p) + 268/25.0 + } +} + +bounce_in_out :: proc "contextless" (p: $T) -> T where intrinsics.type_is_float(T) { + if p < 0.5 { + return 0.5 * bounce_in(p*2) + } else { + return 0.5 * bounce_out(p * 2 - 1) + 0.5 + } +} + +// additional enum variant + +Ease :: enum { + Linear, + + Quadratic_In, + Quadratic_Out, + Quadratic_In_Out, + + Cubic_In, + Cubic_Out, + Cubic_In_Out, + + Quartic_In, + Quartic_Out, + Quartic_In_Out, + + Quintic_In, + Quintic_Out, + Quintic_In_Out, + + Sine_In, + Sine_Out, + Sine_In_Out, + + Circular_In, + Circular_Out, + Circular_In_Out, + + Exponential_In, + Exponential_Out, + Exponential_In_Out, + + Elastic_In, + Elastic_Out, + Elastic_In_Out, + + Back_In, + Back_Out, + Back_In_Out, + + Bounce_In, + Bounce_Out, + Bounce_In_Out, +} + +ease :: proc "contextless" (type: Ease, p: $T) -> T + where intrinsics.type_is_float(T) { + switch type { + case .Linear: return p + + case .Quadratic_In: return quadratic_in(p) + case .Quadratic_Out: return quadratic_out(p) + case .Quadratic_In_Out: return quadratic_in_out(p) + + case .Cubic_In: return cubic_in(p) + case .Cubic_Out: return cubic_out(p) + case .Cubic_In_Out: return cubic_in_out(p) + + case .Quartic_In: return quartic_in(p) + case .Quartic_Out: return quartic_out(p) + case .Quartic_In_Out: return quartic_in_out(p) + + case .Quintic_In: return quintic_in(p) + case .Quintic_Out: return quintic_out(p) + case .Quintic_In_Out: return quintic_in_out(p) + + case .Sine_In: return sine_in(p) + case .Sine_Out: return sine_out(p) + case .Sine_In_Out: return sine_in_out(p) + + case .Circular_In: return circular_in(p) + case .Circular_Out: return circular_out(p) + case .Circular_In_Out: return circular_in_out(p) + + case .Exponential_In: return exponential_in(p) + case .Exponential_Out: return exponential_out(p) + case .Exponential_In_Out: return exponential_in_out(p) + + case .Elastic_In: return elastic_in(p) + case .Elastic_Out: return elastic_out(p) + case .Elastic_In_Out: return elastic_in_out(p) + + case .Back_In: return back_in(p) + case .Back_Out: return back_out(p) + case .Back_In_Out: return back_in_out(p) + + case .Bounce_In: return bounce_in(p) + case .Bounce_Out: return bounce_out(p) + case .Bounce_In_Out: return bounce_in_out(p) + } + + // in case type was invalid + return 0 +} + +Flux_Map :: struct($T: typeid) { + values: map[^T]Flux_Tween(T), +} + +Flux_Tween :: struct($T: typeid) { + value: ^T, + start: T, + diff: T, + goal: T, + + delay: f64, // in seconds + duration: time.Duration, + + progress: f64, + rate: f64, + type: Ease, + + inited: bool, + + // callbacks, data can be set, will be pushed to callback + data: rawptr, // by default gets set to value input + on_start: proc(flux: ^Flux_Map(T), data: rawptr), + on_update: proc(flux: ^Flux_Map(T), data: rawptr), + on_complete: proc(flux: ^Flux_Map(T), data: rawptr), +} + +// init flux map to a float type and a wanted cap +flux_init :: proc($T: typeid, cap := 8) -> Flux_Map(T) where intrinsics.type_is_float(T) { + return { + make(map[^T]Flux_Tween(T), cap), + } +} + +// delete map content +flux_destroy :: proc(flux: Flux_Map($T)) where intrinsics.type_is_float(T) { + delete(flux.values) +} + +// clear map content, stops all animations +flux_clear :: proc(flux: ^Flux_Map($T)) where intrinsics.type_is_float(T) { + clear(&flux.values) +} + +// append / overwrite existing tween value to parameters +// rest is initialized in flux_tween_init, inside update +// return value can be used to set callbacks +flux_to :: proc( + flux: ^Flux_Map($T), + value: ^f32, + goal: f32, + type: Ease = .Quadratic_Out, + duration: time.Duration = time.Second, + delay: f64 = 0, +) -> (tween: ^Flux_Tween(T)) where intrinsics.type_is_float(T) { + if res, ok := &flux.values[value]; ok { + tween = res + } else { + flux.values[value] = {} + tween = &flux.values[value] + } + + tween^ = { + value = value, + goal = goal, + duration = duration, + delay = delay, + type = type, + data = value, + } + + return +} + +// init internal properties +flux_tween_init :: proc(tween: ^Flux_Tween($T), duration: time.Duration) where intrinsics.type_is_float(T) { + tween.inited = true + tween.start = tween.value^ + tween.diff = tween.goal - tween.value^ + s := time.duration_seconds(duration) + tween.rate = duration > 0 ? 1.0 / s : 0 + tween.progress = duration > 0 ? 0 : 1 +} + +// update all tweens, wait for their delay if one exists +// calls callbacks in all stages, when they're filled +// deletes tween from the map after completion +flux_update :: proc(flux: ^Flux_Map($T), dt: f64) where intrinsics.type_is_float(T) { + for key, tween in &flux.values { + delay_remainder := f64(0) + + // Update delay if necessary. + if tween.delay > 0 { + tween.delay -= dt + + if tween.delay < 0 { + // We finished the delay, but in doing so consumed part of this frame's `dt` budget. + // Keep track of it so we can apply it to this tween without affecting others. + delay_remainder = tween.delay + // We're done with this delay. + tween.delay = 0 + } + } + + // We either had no delay, or the delay has been consumed. + if tween.delay <= 0 { + if !tween.inited { + flux_tween_init(&tween, tween.duration) + + if tween.on_start != nil { + tween.on_start(flux, tween.data) + } + } + + // If part of the `dt` budget was consumed this frame, then `delay_remainder` will be + // that remainder, a negative value. Adding it to `dt` applies what's left of the `dt` + // to the tween so it advances properly, instead of too much or little. + tween.progress += tween.rate * (dt + delay_remainder) + x := tween.progress >= 1 ? 1 : ease(tween.type, tween.progress) + tween.value^ = tween.start + tween.diff * T(x) + + if tween.on_update != nil { + tween.on_update(flux, tween.data) + } + + if tween.progress >= 1 { + delete_key(&flux.values, key) + + if tween.on_complete != nil { + tween.on_complete(flux, tween.data) + } + } + } + } +} + +// stop a specific key inside the map +// returns true when it successfully removed the key +flux_stop :: proc(flux: ^Flux_Map($T), key: ^T) -> bool where intrinsics.type_is_float(T) { + if key in flux.values { + delete_key(&flux.values, key) + return true + } + + return false +} + +// returns the amount of time left for the tween animation, if the key exists in the map +// returns 0 if the tween doesnt exist on the map +flux_tween_time_left :: proc(flux: Flux_Map($T), key: ^T) -> f64 { + if tween, ok := flux.values[key]; ok { + return ((1 - tween.progress) * tween.rate) + tween.delay + } else { + return 0 + } +} diff --git a/core/math/math.odin b/core/math/math.odin index afbda051d..b711c160f 100644 --- a/core/math/math.odin +++ b/core/math/math.odin @@ -1206,7 +1206,7 @@ prod :: proc "contextless" (x: $T/[]$E) -> (res: E) return } -cumsum_inplace :: proc "contextless" (x: $T/[]$E) -> T +cumsum_inplace :: proc "contextless" (x: $T/[]$E) where intrinsics.type_is_numeric(E) { for i in 1.. ([]byte, Allocator_Error) { - return nil, nil + switch mode { + case .Alloc: + return nil, .Out_Of_Memory + case .Free: + return nil, .None + case .Free_All: + return nil, .Mode_Not_Implemented + case .Resize: + if size == 0 { + return nil, .None + } + return nil, .Out_Of_Memory + case .Query_Features: + return nil, .Mode_Not_Implemented + case .Query_Info: + return nil, .Mode_Not_Implemented + } + return nil, .None } nil_allocator :: proc() -> Allocator { diff --git a/core/mem/mem.odin b/core/mem/mem.odin index 8eb877e75..817c0ea5f 100644 --- a/core/mem/mem.odin +++ b/core/mem/mem.odin @@ -16,7 +16,7 @@ zero_explicit :: proc "contextless" (data: rawptr, len: int) -> rawptr { // equivalent semantics to those provided by the C11 Annex K 3.7.4.1 // memset_s call. intrinsics.mem_zero_volatile(data, len) // Use the volatile mem_zero - intrinsics.atomic_fence() // Prevent reordering + intrinsics.atomic_thread_fence(.Seq_Cst) // Prevent reordering return data } zero_item :: proc "contextless" (item: $P/^$T) { diff --git a/core/mem/virtual/virtual_platform.odin b/core/mem/virtual/virtual_platform.odin index d707bc427..367346f63 100644 --- a/core/mem/virtual/virtual_platform.odin +++ b/core/mem/virtual/virtual_platform.odin @@ -1,7 +1,7 @@ //+private package mem_virtual -import sync "core:sync/sync2" +import "core:sync" Platform_Memory_Block :: struct { block: Memory_Block, diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index 740299539..f4aa67446 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -708,13 +708,19 @@ Struct_Type :: struct { name_count: int, } +Union_Type_Kind :: enum u8 { + Normal, + maybe, + no_nil, + shared_nil, +} + Union_Type :: struct { using node: Expr, tok_pos: tokenizer.Pos, poly_params: ^Field_List, align: ^Expr, - is_maybe: bool, - is_no_nil: bool, + kind: Union_Type_Kind, where_token: tokenizer.Token, where_clauses: []^Expr, variants: []^Expr, diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 1546dce2d..25eda6bed 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -2630,8 +2630,9 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { tok := expect_token(p, .Union) poly_params: ^ast.Field_List align: ^ast.Expr - is_maybe: bool - is_no_nil: bool + is_maybe: bool + is_no_nil: bool + is_shared_nil: bool if allow_token(p, .Open_Paren) { param_count: int @@ -2663,12 +2664,34 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { error(p, tag.pos, "duplicate union tag '#%s'", tag.text) } is_no_nil = true + case "shared_nil": + if is_shared_nil { + error(p, tag.pos, "duplicate union tag '#%s'", tag.text) + } + is_shared_nil = true case: error(p, tag.pos, "invalid union tag '#%s", tag.text) } } p.expr_level = prev_level + if is_no_nil && is_maybe { + error(p, p.curr_tok.pos, "#maybe and #no_nil cannot be applied together") + } + if is_no_nil && is_shared_nil { + error(p, p.curr_tok.pos, "#shared_nil and #no_nil cannot be applied together") + } + if is_shared_nil && is_maybe { + error(p, p.curr_tok.pos, "#maybe and #shared_nil cannot be applied together") + } + + union_kind := ast.Union_Type_Kind.Normal + switch { + case is_maybe: union_kind = .maybe + case is_no_nil: union_kind = .no_nil + case is_shared_nil: union_kind = .shared_nil + } + where_token: tokenizer.Token where_clauses: []^ast.Expr @@ -2699,14 +2722,15 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { close := expect_closing_brace_of_field_list(p) + + ut := ast.new(ast.Union_Type, tok.pos, end_pos(close)) ut.poly_params = poly_params ut.variants = variants[:] ut.align = align ut.where_token = where_token ut.where_clauses = where_clauses - ut.is_maybe = is_maybe - ut.is_no_nil = is_no_nil + ut.kind = union_kind return ut diff --git a/core/odin/printer/visit.odin b/core/odin/printer/visit.odin index 9eba29987..70140f180 100644 --- a/core/odin/printer/visit.odin +++ b/core/odin/printer/visit.odin @@ -1046,8 +1046,11 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) { push_poly_params(p, v.poly_params) - if v.is_maybe { - push_ident_token(p, "#maybe", 1) + switch v.kind { + case .Normal: + case .maybe: push_ident_token(p, "#maybe", 1) + case .no_nil: push_ident_token(p, "#no_nil", 1) + case .shared_nil: push_ident_token(p, "#shared_nil", 1) } push_where_clauses(p, v.where_clauses) diff --git a/core/os/dir_windows.odin b/core/os/dir_windows.odin index ff7e53293..3261b8cb3 100644 --- a/core/os/dir_windows.odin +++ b/core/os/dir_windows.odin @@ -82,6 +82,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F wpath_search[len(wpath)+2] = 0 path := cleanpath_from_buf(wpath) + defer delete(path) find_data := &win32.WIN32_FIND_DATAW{} find_handle := win32.FindFirstFileW(raw_data(wpath_search), find_data) diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 028951fe3..7fc7a4ac0 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -1,6 +1,6 @@ package os2 -import sync "core:sync/sync2" +import "core:sync" import "core:time" import "core:runtime" diff --git a/core/os/stat.odin b/core/os/stat.odin index 6f4c8b0ef..1b64ad33b 100644 --- a/core/os/stat.odin +++ b/core/os/stat.odin @@ -2,7 +2,6 @@ package os import "core:time" - File_Info :: struct { fullpath: string, name: string, diff --git a/core/os/stat_windows.odin b/core/os/stat_windows.odin index 964625315..5da925560 100644 --- a/core/os/stat_windows.odin +++ b/core/os/stat_windows.odin @@ -80,7 +80,7 @@ stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) return _stat(name, attrs, allocator) } -fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Errno) { +fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, errno: Errno) { if fd == 0 { return {}, ERROR_INVALID_HANDLE } @@ -94,14 +94,14 @@ fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Errno) h := win32.HANDLE(fd) switch win32.GetFileType(h) { case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: - fi: File_Info - fi.fullpath = path fi.name = basename(path) fi.mode |= file_type_mode(h) - return fi, ERROR_NONE + errno = ERROR_NONE + case: + fi, errno = file_info_from_get_file_information_by_handle(path, h) } - - return file_info_from_get_file_information_by_handle(path, h) + fi.fullpath = path + return } diff --git a/core/path/filepath/match.odin b/core/path/filepath/match.odin index 6399f86a2..252912710 100644 --- a/core/path/filepath/match.odin +++ b/core/path/filepath/match.odin @@ -220,9 +220,11 @@ get_escape :: proc(chunk: string) -> (r: rune, next_chunk: string, err: Match_Er // glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []string, err: Match_Error) { + context.allocator = allocator + if !has_meta(pattern) { // TODO(bill): os.lstat on here to check for error - m := make([]string, 1, allocator) + m := make([]string, 1) m[0] = pattern return m[:], .None } @@ -232,6 +234,7 @@ glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []str when ODIN_OS == .Windows { temp_buf: [8]byte volume_len, dir = clean_glob_path_windows(dir, temp_buf[:]) + } else { dir = clean_glob_path(dir) } @@ -246,7 +249,7 @@ glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []str if err != .None { return } - dmatches := make([dynamic]string, 0, 0, allocator) + dmatches := make([dynamic]string, 0, 0) for d in m { dmatches, err = _glob(d, file, &dmatches) if err != .None { @@ -258,11 +261,13 @@ glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []str } return } -_glob :: proc(dir, pattern: string, matches: ^[dynamic]string) -> (m: [dynamic]string, e: Match_Error) { +_glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := context.allocator) -> (m: [dynamic]string, e: Match_Error) { + context.allocator = allocator + if matches != nil { m = matches^ } else { - m = make([dynamic]string, 0, 0, context.allocator) + m = make([dynamic]string, 0, 0) } @@ -275,6 +280,7 @@ _glob :: proc(dir, pattern: string, matches: ^[dynamic]string) -> (m: [dynamic]s { file_info, ferr := os.fstat(d) defer os.file_info_delete(file_info) + if ferr != 0 { return } diff --git a/core/path/filepath/path.odin b/core/path/filepath/path.odin index ba6e11044..42714d736 100644 --- a/core/path/filepath/path.odin +++ b/core/path/filepath/path.odin @@ -1,5 +1,5 @@ // The path/filepath package uses either forward slashes or backslashes depending on the operating system -// To process paths usch as URLs that depend on forward slashes regardless of the OS, use the path package +// To process paths such as URLs that depend on forward slashes regardless of the OS, use the path package package filepath import "core:strings" @@ -122,6 +122,7 @@ clean :: proc(path: string, allocator := context.allocator) -> string { vol_and_path = original_path, vol_len = vol_len, } + defer lazy_buffer_destroy(out) r, dot_dot := 0, 0 if rooted { @@ -170,7 +171,6 @@ clean :: proc(path: string, allocator := context.allocator) -> string { cleaned, new_allocation := from_slash(s) if new_allocation { delete(s) - lazy_buffer_destroy(out) } return cleaned } @@ -300,6 +300,11 @@ dir :: proc(path: string, allocator := context.allocator) -> string { +// Splits the PATH-like `path` string, returning an array of its separated components (delete after use). +// For Windows the separator is `;`, for Unix it's `:`. +// An empty string returns nil. A non-empty string with no separators returns a 1-element array. +// Any empty components will be included, e.g. `a::b` will return a 3-element array, as will `::`. +// Separators within pairs of double-quotes will be ignored and stripped, e.g. `"a:b"c:d` will return []{`a:bc`, `d`}. split_list :: proc(path: string, allocator := context.allocator) -> []string { if path == "" { return nil @@ -322,7 +327,7 @@ split_list :: proc(path: string, allocator := context.allocator) -> []string { } start, quote = 0, false - list := make([]string, count, allocator) + list := make([]string, count + 1, allocator) index := 0 for i := 0; i < len(path); i += 1 { c := path[i] @@ -336,6 +341,7 @@ split_list :: proc(path: string, allocator := context.allocator) -> []string { } } assert(index == count) + list[index] = path[start:] for s0, i in list { s, new := strings.replace_all(s0, `"`, ``, allocator) diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index d05026532..49d7ef9b5 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -1054,6 +1054,7 @@ as_u64 :: proc(a: any) -> (value: u64, valid: bool) { case Type_Info_Float: valid = true switch v in a { + case f16: value = u64(v) case f32: value = u64(v) case f64: value = u64(v) case f32le: value = u64(v) @@ -1159,6 +1160,7 @@ as_f64 :: proc(a: any) -> (value: f64, valid: bool) { case Type_Info_Float: valid = true switch v in a { + case f16: value = f64(v) case f32: value = f64(v) case f64: value = (v) case f32le: value = f64(v) diff --git a/core/runtime/core.odin b/core/runtime/core.odin index 8c95a234f..a5a190a9c 100644 --- a/core/runtime/core.odin +++ b/core/runtime/core.odin @@ -136,6 +136,7 @@ Type_Info_Union :: struct { custom_align: bool, no_nil: bool, maybe: bool, + shared_nil: bool, } Type_Info_Enum :: struct { base: ^Type_Info, diff --git a/core/runtime/entry_unix.odin b/core/runtime/entry_unix.odin index 1a3def200..9f7d219c3 100644 --- a/core/runtime/entry_unix.odin +++ b/core/runtime/entry_unix.odin @@ -30,4 +30,4 @@ when ODIN_BUILD_MODE == .Dynamic { #force_no_inline _cleanup_runtime() return 0 } -} \ No newline at end of file +} diff --git a/core/strings/strings.odin b/core/strings/strings.odin index 670da166b..d01be7989 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -1,16 +1,21 @@ +// simple procedures to manipulate UTF-8 encoded strings package strings import "core:io" import "core:mem" +import "core:slice" import "core:unicode" import "core:unicode/utf8" +// returns a clone of the string `s` allocated using the `allocator` clone :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> string { c := make([]byte, len(s), allocator, loc) copy(c, s) return string(c[:len(s)]) } +// returns a clone of the string `s` allocated using the `allocator` as a cstring +// a nul byte is appended to the clone, to make the cstring safe clone_to_cstring :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> cstring { c := make([]byte, len(s)+1, allocator, loc) copy(c, s) @@ -18,27 +23,35 @@ clone_to_cstring :: proc(s: string, allocator := context.allocator, loc := #call return cstring(&c[0]) } +// returns a string from a byte pointer `ptr` and byte length `len` +// the string is valid as long as the parameters stay alive string_from_ptr :: proc(ptr: ^byte, len: int) -> string { return transmute(string)mem.Raw_String{ptr, len} } +// returns a string from a byte pointer `ptr and byte length `len` +// searches for a nul byte from 0.. string { s := transmute(string)mem.Raw_String{ptr, len} s = truncate_to_byte(s, 0) return s } - +// returns the raw ^byte start of the string `str` ptr_from_string :: proc(str: string) -> ^byte { d := transmute(mem.Raw_String)str return d.data } +// returns the transmute of string `str` to a cstring +// not safe since the origin string may not contain a nul byte unsafe_string_to_cstring :: proc(str: string) -> cstring { d := transmute(mem.Raw_String)str return cstring(d.data) } +// returns a string truncated to the first time it finds the byte `b` +// uses the `len` of the string `str` when it couldn't find the input truncate_to_byte :: proc(str: string, b: byte) -> string { n := index_byte(str, b) if n < 0 { @@ -46,6 +59,9 @@ truncate_to_byte :: proc(str: string, b: byte) -> string { } return str[:n] } + +// returns a string truncated to the first time it finds the rune `r` +// uses the `len` of the string `str` when it couldn't find the input truncate_to_rune :: proc(str: string, r: rune) -> string { n := index_rune(str, r) if n < 0 { @@ -54,20 +70,28 @@ truncate_to_rune :: proc(str: string, r: rune) -> string { return str[:n] } +// returns a cloned string of the byte array `s` using the `allocator` +// appends a leading nul byte clone_from_bytes :: proc(s: []byte, allocator := context.allocator, loc := #caller_location) -> string { c := make([]byte, len(s)+1, allocator, loc) copy(c, s) c[len(s)] = 0 return string(c[:len(s)]) } + +// returns a clone of the cstring `s` using the `allocator` as a string clone_from_cstring :: proc(s: cstring, allocator := context.allocator, loc := #caller_location) -> string { return clone(string(s), allocator, loc) } + +// returns a cloned string from the pointer `ptr` and a byte length `len` using the `allocator` +// same to `string_from_ptr` but allocates clone_from_ptr :: proc(ptr: ^byte, len: int, allocator := context.allocator, loc := #caller_location) -> string { s := string_from_ptr(ptr, len) return clone(s, allocator, loc) } +// overload to clone from a `string`, `[]byte`, `cstring` or a `^byte + length` to a string clone_from :: proc{ clone, clone_from_bytes, @@ -75,6 +99,8 @@ clone_from :: proc{ clone_from_ptr, } +// returns a cloned string from the cstring `ptr` and a byte length `len` using the `allocator` +// truncates till the first nul byte it finds or the byte len clone_from_cstring_bounded :: proc(ptr: cstring, len: int, allocator := context.allocator, loc := #caller_location) -> string { s := string_from_ptr((^u8)(ptr), len) s = truncate_to_byte(s, 0) @@ -82,11 +108,12 @@ clone_from_cstring_bounded :: proc(ptr: cstring, len: int, allocator := context. } // Compares two strings, returning a value representing which one comes first lexiographically. -// -1 for `a`; 1 for `b`, or 0 if they are equal. +// -1 for `lhs`; 1 for `rhs`, or 0 if they are equal. compare :: proc(lhs, rhs: string) -> int { return mem.compare(transmute([]byte)lhs, transmute([]byte)rhs) } +// returns the byte offset of the rune `r` in the string `s`, -1 when not found contains_rune :: proc(s: string, r: rune) -> int { for c, offset in s { if c == r { @@ -96,20 +123,48 @@ contains_rune :: proc(s: string, r: rune) -> int { return -1 } +/* + returns true when the string `substr` is contained inside the string `s` + + strings.contains("testing", "test") -> true + strings.contains("testing", "ing") -> true + strings.contains("testing", "text") -> false +*/ contains :: proc(s, substr: string) -> bool { return index(s, substr) >= 0 } +/* + returns true when the string `s` contains any of the characters inside the string `chars` + + strings.contains_any("test", "test") -> true + strings.contains_any("test", "ts") -> true + strings.contains_any("test", "et") -> true + strings.contains_any("test", "a") -> false +*/ contains_any :: proc(s, chars: string) -> bool { return index_any(s, chars) >= 0 } +/* + returns the utf8 rune count of the string `s` + strings.rune_count("test") -> 4 + strings.rune_count("testö") -> 5, where len("testö") -> 6 +*/ rune_count :: proc(s: string) -> int { return utf8.rune_count_in_string(s) } +/* + returns wether the strings `u` and `v` are the same alpha characters + works with utf8 string content and ignores different casings + strings.equal_fold("test", "test") -> true + strings.equal_fold("Test", "test") -> true + strings.equal_fold("Test", "tEsT") -> true + strings.equal_fold("test", "tes") -> false +*/ equal_fold :: proc(u, v: string) -> bool { s, t := u, v loop: for s != "" && t != "" { @@ -153,15 +208,39 @@ equal_fold :: proc(u, v: string) -> bool { return s == t } +/* + return true when the string `prefix` is contained at the start of the string `s` + + strings.has_prefix("testing", "test") -> true + strings.has_prefix("testing", "te") -> true + strings.has_prefix("telephone", "te") -> true + strings.has_prefix("testing", "est") -> false +*/ has_prefix :: proc(s, prefix: string) -> bool { return len(s) >= len(prefix) && s[0:len(prefix)] == prefix } +/* + returns true when the string `suffix` is contained at the end of the string `s` + good example to use this is for file extensions + + strings.has_suffix("todo.txt", ".txt") -> true + strings.has_suffix("todo.doc", ".txt") -> false + strings.has_suffix("todo.doc.txt", ".txt") -> true +*/ has_suffix :: proc(s, suffix: string) -> bool { return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix } +/* + returns a combined string from the slice of strings `a` seperated with the `sep` string + allocates the string using the `allocator` + a := [?]string { "a", "b", "c" } + b := strings.join(a[:], " ") -> "a b c" + c := strings.join(a[:], "-") -> "a-b-c" + d := strings.join(a[:], "...") -> "a...b...c" +*/ join :: proc(a: []string, sep: string, allocator := context.allocator) -> string { if len(a) == 0 { return "" @@ -181,6 +260,14 @@ join :: proc(a: []string, sep: string, allocator := context.allocator) -> string return string(b) } +/* + returns a combined string from the slice of strings `a` without a seperator + allocates the string using the `allocator` + + + a := [?]string { "a", "b", "c" } + b := strings.concatenate(a[:]) -> "abc" +*/ concatenate :: proc(a: []string, allocator := context.allocator) -> string { if len(a) == 0 { return "" @@ -200,30 +287,56 @@ concatenate :: proc(a: []string, allocator := context.allocator) -> string { /* `rune_offset` and `rune_length` are in runes, not bytes. - If `rune_length` <= 0, then it'll return the remainder of the string starting with `rune_offset`. + If `rune_length` <= 0, then it'll return the remainder of the string starting at `rune_offset`. + + strings.cut("some example text", 0, 4) -> "some" + strings.cut("some example text", 2, 2) -> "me" + strings.cut("some example text", 5, 7) -> "example" */ cut :: proc(s: string, rune_offset := int(0), rune_length := int(0), allocator := context.allocator) -> (res: string) { s := s; rune_length := rune_length - l := utf8.rune_count_in_string(s) + context.allocator = allocator - if rune_offset >= l { return "" } + // If we signal that we want the entire remainder (length <= 0) *and* + // the offset is zero, then we can early out by cloning the input if rune_offset == 0 && rune_length <= 0 { - return clone(s, allocator) + return clone(s) } - if rune_length == 0 { rune_length = l } + // We need to know if we have enough runes to cover offset + length. + rune_count := utf8.rune_count_in_string(s) + + // We're asking for a substring starting after the end of the input string. + // That's just an empty string. + if rune_offset >= rune_count { + return "" + } + + // If we don't specify the length of the substring, use the remainder. + if rune_length <= 0 { + rune_length = rune_count - rune_offset + } + + // We don't yet know how many bytes we need exactly. + // But we do know it's bounded by the number of runes * 4 bytes, + // and can be no more than the size of the input string. bytes_needed := min(rune_length * 4, len(s)) - buf := make([]u8, bytes_needed, allocator) + buf := make([]u8, bytes_needed) byte_offset := 0 - for i := 0; i < l; i += 1 { + for i := 0; i < rune_count; i += 1 { _, w := utf8.decode_rune_in_string(s) + + // If the rune is part of the substring, copy it to the output buffer. if i >= rune_offset { for j := 0; j < w; j += 1 { buf[byte_offset+j] = s[j] } byte_offset += w } + + // We're done if we reach the end of the input string, *or* + // if we've reached a specified length in runes. if rune_length > 0 { if i == rune_offset + rune_length - 1 { break } } @@ -307,17 +420,37 @@ split_n :: proc(s, sep: string, n: int, allocator := context.allocator) -> []str return _split(s, sep, 0, n, allocator) } +/* + splits the string `s` after the seperator string `sep` appears + returns the slice of split strings allocated using `allocator` + + a := "aaa.bbb.ccc.ddd.eee" + aa := strings.split_after(a, ".") + fmt.eprintln(aa) // [aaa., bbb., ccc., ddd., eee] +*/ split_after :: proc(s, sep: string, allocator := context.allocator) -> []string { return _split(s, sep, len(sep), -1, allocator) } +/* + splits the string `s` after the seperator string `sep` appears into a total of `n` parts + returns the slice of split strings allocated using `allocator` + + a := "aaa.bbb.ccc.ddd.eee" + aa := strings.split_after(a, ".") + fmt.eprintln(aa) // [aaa., bbb., ccc., ddd., eee] +*/ split_after_n :: proc(s, sep: string, n: int, allocator := context.allocator) -> []string { return _split(s, sep, len(sep), n, allocator) } - @private _split_iterator :: proc(s: ^string, sep: string, sep_save: int) -> (res: string, ok: bool) { + // stop once the string is empty or nil + if s == nil || len(s^) == 0 { + return + } + if sep == "" { res = s[:] ok = true @@ -339,11 +472,52 @@ _split_iterator :: proc(s: ^string, sep: string, sep_save: int) -> (res: string, return } +/* + split the ^string `s` by the byte seperator `sep` in an iterator fashion + consumes the original string till the end, leaving the string `s` with len == 0 + text := "a.b.c.d.e" + for str in strings.split_by_byte_iterator(&text, '.') { + fmt.eprintln(str) // every loop -> a b c d e + } +*/ +split_by_byte_iterator :: proc(s: ^string, sep: u8) -> (res: string, ok: bool) { + m := index_byte(s^, sep) + if m < 0 { + // not found + res = s[:] + ok = res != "" + s^ = {} + } else { + res = s[:m] + ok = true + s^ = s[m+1:] + } + return +} + +/* + split the ^string `s` by the seperator string `sep` in an iterator fashion + consumes the original string till the end + + text := "a.b.c.d.e" + for str in strings.split_iterator(&text, ".") { + fmt.eprintln(str) // every loop -> a b c d e + } +*/ split_iterator :: proc(s: ^string, sep: string) -> (string, bool) { return _split_iterator(s, sep, 0) } +/* + split the ^string `s` after every seperator string `sep` in an iterator fashion + consumes the original string till the end + + text := "a.b.c.d.e" + for str in strings.split_after_iterator(&text, ".") { + fmt.eprintln(str) // every loop -> a. b. c. d. e + } +*/ split_after_iterator :: proc(s: ^string, sep: string) -> (string, bool) { return _split_iterator(s, sep, len(sep)) } @@ -360,6 +534,14 @@ _trim_cr :: proc(s: string) -> string { return s } +/* + split the string `s` at every line break '\n' + return an allocated slice of strings + + a := "a\nb\nc\nd\ne" + b := strings.split_lines(a) + fmt.eprintln(b) // [a, b, c, d, e] +*/ split_lines :: proc(s: string, allocator := context.allocator) -> []string { sep :: "\n" lines := _split(s, sep, 0, -1, allocator) @@ -369,6 +551,14 @@ split_lines :: proc(s: string, allocator := context.allocator) -> []string { return lines } +/* + split the string `s` at every line break '\n' for `n` parts + return an allocated slice of strings + + a := "a\nb\nc\nd\ne" + b := strings.split_lines_n(a, 3) + fmt.eprintln(b) // [a, b, c, d\ne\n] +*/ split_lines_n :: proc(s: string, n: int, allocator := context.allocator) -> []string { sep :: "\n" lines := _split(s, sep, 0, n, allocator) @@ -378,6 +568,14 @@ split_lines_n :: proc(s: string, n: int, allocator := context.allocator) -> []st return lines } +/* + split the string `s` at every line break '\n' leaving the '\n' in the resulting strings + return an allocated slice of strings + + a := "a\nb\nc\nd\ne" + b := strings.split_lines_after(a) + fmt.eprintln(b) // [a\n, b\n, c\n, d\n, e\n] +*/ split_lines_after :: proc(s: string, allocator := context.allocator) -> []string { sep :: "\n" lines := _split(s, sep, len(sep), -1, allocator) @@ -387,6 +585,15 @@ split_lines_after :: proc(s: string, allocator := context.allocator) -> []string return lines } +/* + split the string `s` at every line break '\n' leaving the '\n' in the resulting strings + only runs for `n` parts + return an allocated slice of strings + + a := "a\nb\nc\nd\ne" + b := strings.split_lines_after_n(a, 3) + fmt.eprintln(b) // [a\n, b\n, c\n, d\ne\n] +*/ split_lines_after_n :: proc(s: string, n: int, allocator := context.allocator) -> []string { sep :: "\n" lines := _split(s, sep, len(sep), n, allocator) @@ -396,21 +603,45 @@ split_lines_after_n :: proc(s: string, n: int, allocator := context.allocator) - return lines } +/* + split the string `s` at every line break '\n' + returns the current split string every iteration till the string is consumed + + text := "a\nb\nc\nd\ne" + for str in strings.split_lines_iterator(&text) { + fmt.eprintln(text) // every loop -> a b c d e + } +*/ split_lines_iterator :: proc(s: ^string) -> (line: string, ok: bool) { sep :: "\n" line = _split_iterator(s, sep, 0) or_return return _trim_cr(line), true } +/* + split the string `s` at every line break '\n' + returns the current split string every iteration till the string is consumed + + text := "a\nb\nc\nd\ne" + for str in strings.split_lines_after_iterator(&text) { + fmt.eprintln(text) // every loop -> a\n b\n c\n d\n e\n + } +*/ split_lines_after_iterator :: proc(s: ^string) -> (line: string, ok: bool) { sep :: "\n" line = _split_iterator(s, sep, len(sep)) or_return return _trim_cr(line), true } +/* + returns the byte offset of the first byte `c` in the string `s` it finds, -1 when not found + can't find utf8 based runes - - + strings.index_byte("test", 't') -> 0 + strings.index_byte("test", 'e') -> 1 + strings.index_byte("test", 'x') -> -1 + strings.index_byte("teäst", 'ä') -> -1 +*/ index_byte :: proc(s: string, c: byte) -> int { for i := 0; i < len(s); i += 1 { if s[i] == c { @@ -420,7 +651,15 @@ index_byte :: proc(s: string, c: byte) -> int { return -1 } -// Returns -1 if c is not present +/* + returns the byte offset of the last byte `c` in the string `s` it finds, -1 when not found + can't find utf8 based runes + + strings.index_byte("test", 't') -> 3 + strings.index_byte("test", 'e') -> 1 + strings.index_byte("test", 'x') -> -1 + strings.index_byte("teäst", 'ä') -> -1 +*/ last_index_byte :: proc(s: string, c: byte) -> int { for i := len(s)-1; i >= 0; i -= 1 { if s[i] == c { @@ -431,9 +670,50 @@ last_index_byte :: proc(s: string, c: byte) -> int { } +/* + returns the byte offset of the first rune `r` in the string `s` it finds, -1 when not found + avoids invalid runes + + strings.index_rune("abcädef", 'x') -> -1 + strings.index_rune("abcädef", 'a') -> 0 + strings.index_rune("abcädef", 'b') -> 1 + strings.index_rune("abcädef", 'c') -> 2 + strings.index_rune("abcädef", 'ä') -> 3 + strings.index_rune("abcädef", 'd') -> 5 + strings.index_rune("abcädef", 'e') -> 6 + strings.index_rune("abcädef", 'f') -> 7 +*/ +index_rune :: proc(s: string, r: rune) -> int { + switch { + case 0 <= r && r < utf8.RUNE_SELF: + return index_byte(s, byte(r)) + + case r == utf8.RUNE_ERROR: + for c, i in s { + if c == utf8.RUNE_ERROR { + return i + } + } + return -1 + + case !utf8.valid_rune(r): + return -1 + } + + b, w := utf8.encode_rune(r) + return index(s, string(b[:w])) +} @private PRIME_RABIN_KARP :: 16777619 +/* + returns the byte offset of the string `substr` in the string `s`, -1 when not found + + strings.index("test", "t") -> 0 + strings.index("test", "te") -> 0 + strings.index("test", "st") -> 2 + strings.index("test", "tt") -> -1 +*/ index :: proc(s, substr: string) -> int { hash_str_rabin_karp :: proc(s: string) -> (hash: u32 = 0, pow: u32 = 1) { for i := 0; i < len(s); i += 1 { @@ -484,6 +764,14 @@ index :: proc(s, substr: string) -> int { return -1 } +/* + returns the last byte offset of the string `substr` in the string `s`, -1 when not found + + strings.index("test", "t") -> 3 + strings.index("test", "te") -> 0 + strings.index("test", "st") -> 2 + strings.index("test", "tt") -> -1 +*/ last_index :: proc(s, substr: string) -> int { hash_str_rabin_karp_reverse :: proc(s: string) -> (hash: u32 = 0, pow: u32 = 1) { for i := len(s) - 1; i >= 0; i -= 1 { @@ -532,7 +820,15 @@ last_index :: proc(s, substr: string) -> int { return -1 } -// index_any returns the index of the first char of `chars` found in `s`. -1 if not found. +/* + returns the index of any first char of `chars` found in `s`, -1 if not found + + strings.index_any("test", "s") -> 2 + strings.index_any("test", "se") -> 1 + strings.index_any("test", "et") -> 0 + strings.index_any("test", "set") -> 0 + strings.index_any("test", "x") -> -1 +*/ index_any :: proc(s, chars: string) -> int { if chars == "" { return -1 @@ -565,6 +861,16 @@ index_any :: proc(s, chars: string) -> int { return -1 } +/* + returns the index of any first char of `chars` found in `s`, -1 if not found + iterates the string in reverse + + strings.index_any("test", "s") -> 2 + strings.index_any("test", "se") -> 2 + strings.index_any("test", "et") -> 1 + strings.index_any("test", "set") -> 3 + strings.index_any("test", "x") -> -1 +*/ last_index_any :: proc(s, chars: string) -> int { if chars == "" { return -1 @@ -614,6 +920,16 @@ last_index_any :: proc(s, chars: string) -> int { return -1 } +/* + returns the count of the string `substr` found in the string `s` + returns the rune_count + 1 of the string `s` on empty `substr` + + strings.count("abbccc", "a") -> 1 + strings.count("abbccc", "b") -> 2 + strings.count("abbccc", "c") -> 3 + strings.count("abbccc", "ab") -> 1 + strings.count("abbccc", " ") -> 0 +*/ count :: proc(s, substr: string) -> int { if len(substr) == 0 { // special case return rune_count(s) + 1 @@ -649,7 +965,12 @@ count :: proc(s, substr: string) -> int { return n } +/* + repeats the string `s` multiple `count` times and returns the allocated string + panics when `count` is below 0 + strings.repeat("abc", 2) -> "abcabc" +*/ repeat :: proc(s: string, count: int, allocator := context.allocator) -> string { if count < 0 { panic("strings: negative repeat count") @@ -666,11 +987,28 @@ repeat :: proc(s: string, count: int, allocator := context.allocator) -> string return string(b) } +/* + replaces all instances of `old` in the string `s` with the `new` string + returns the `output` string and true when an a allocation through a replace happened + + strings.replace_all("xyzxyz", "xyz", "abc") -> "abcabc", true + strings.replace_all("xyzxyz", "abc", "xyz") -> "xyzxyz", false + strings.replace_all("xyzxyz", "xy", "z") -> "zzzz", true +*/ replace_all :: proc(s, old, new: string, allocator := context.allocator) -> (output: string, was_allocation: bool) { return replace(s, old, new, -1, allocator) } -// if n < 0, no limit on the number of replacements +/* + replaces `n` instances of `old` in the string `s` with the `new` string + if n < 0, no limit on the number of replacements + returns the `output` string and true when an a allocation through a replace happened + + strings.replace("xyzxyz", "xyz", "abc", 2) -> "abcabc", true + strings.replace("xyzxyz", "xyz", "abc", 1) -> "abcxyz", true + strings.replace("xyzxyz", "abc", "xyz", -1) -> "xyzxyz", false + strings.replace("xyzxyz", "xy", "z", -1) -> "zzzz", true +*/ replace :: proc(s, old, new: string, n: int, allocator := context.allocator) -> (output: string, was_allocation: bool) { if old == new || n == 0 { was_allocation = false @@ -711,17 +1049,35 @@ replace :: proc(s, old, new: string, n: int, allocator := context.allocator) -> return } +/* + removes the `key` string `n` times from the `s` string + if n < 0, no limit on the number of removes + returns the `output` string and true when an a allocation through a remove happened + + strings.remove("abcabc", "abc", 1) -> "abc", true + strings.remove("abcabc", "abc", -1) -> "", true + strings.remove("abcabc", "a", -1) -> "bcbc", true + strings.remove("abcabc", "x", -1) -> "abcabc", false +*/ remove :: proc(s, key: string, n: int, allocator := context.allocator) -> (output: string, was_allocation: bool) { return replace(s, key, "", n, allocator) } +/* + removes all the `key` string instanes from the `s` string + returns the `output` string and true when an a allocation through a remove happened + + strings.remove("abcabc", "abc") -> "", true + strings.remove("abcabc", "a") -> "bcbc", true + strings.remove("abcabc", "x") -> "abcabc", false +*/ remove_all :: proc(s, key: string, allocator := context.allocator) -> (output: string, was_allocation: bool) { return remove(s, key, -1, allocator) } @(private) _ascii_space := [256]bool{'\t' = true, '\n' = true, '\v' = true, '\f' = true, '\r' = true, ' ' = true} - +// return true when the `r` rune is '\t', '\n', '\v', '\f', '\r' or ' ' is_ascii_space :: proc(r: rune) -> bool { if r < utf8.RUNE_SELF { return _ascii_space[u8(r)] @@ -729,6 +1085,7 @@ is_ascii_space :: proc(r: rune) -> bool { return false } +// returns true when the `r` rune is any asci or utf8 based whitespace is_space :: proc(r: rune) -> bool { if r < 0x2000 { switch r { @@ -747,10 +1104,24 @@ is_space :: proc(r: rune) -> bool { return false } +// returns true when the `r` rune is a nul byte is_null :: proc(r: rune) -> bool { return r == 0x0000 } +/* + runs trough the `s` string linearly and watches wether the `p` procedure matches the `truth` bool + returns the rune offset or -1 when no match was found + + call :: proc(r: rune) -> bool { + return r == 'a' + } + strings.index_proc("abcabc", call) -> 0 + strings.index_proc("cbacba", call) -> 2 + strings.index_proc("cbacba", call, false) -> 0 + strings.index_proc("abcabc", call, false) -> 1 + strings.index_proc("xyz", call) -> -1 +*/ index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> int { for r, i in s { if p(r) == truth { @@ -760,6 +1131,7 @@ index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> int { return -1 } +// same as `index_proc` but with a `p` procedure taking a rawptr for state index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: rawptr, truth := true) -> int { for r, i in s { if p(state, r) == truth { @@ -769,6 +1141,7 @@ index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: r return -1 } +// same as `index_proc` but runs through the string in reverse last_index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> int { // TODO(bill): Probably use Rabin-Karp Search for i := len(s); i > 0; { @@ -781,6 +1154,7 @@ last_index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> int return -1 } +// same as `index_proc_with_state` but runs through the string in reverse last_index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: rawptr, truth := true) -> int { // TODO(bill): Probably use Rabin-Karp Search for i := len(s); i > 0; { @@ -792,7 +1166,17 @@ last_index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, sta } return -1 } + +/* + trims the input string `s` until the procedure `p` returns false + does not allocate - only returns a cut variant of the input string + returns an empty string when no match was found at all + find :: proc(r: rune) -> bool { + return r != 'i' + } + strings.trim_left_proc("testing", find) -> "ing" +*/ trim_left_proc :: proc(s: string, p: proc(rune) -> bool) -> string { i := index_proc(s, p, false) if i == -1 { @@ -801,29 +1185,10 @@ trim_left_proc :: proc(s: string, p: proc(rune) -> bool) -> string { return s[i:] } - -index_rune :: proc(s: string, r: rune) -> int { - switch { - case 0 <= r && r < utf8.RUNE_SELF: - return index_byte(s, byte(r)) - - case r == utf8.RUNE_ERROR: - for c, i in s { - if c == utf8.RUNE_ERROR { - return i - } - } - return -1 - - case !utf8.valid_rune(r): - return -1 - } - - b, w := utf8.encode_rune(r) - return index(s, string(b[:w])) -} - - +/* + trims the input string `s` until the procedure `p` with state returns false + returns an empty string when no match was found at all +*/ trim_left_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: rawptr) -> string { i := index_proc_with_state(s, p, state, false) if i == -1 { @@ -832,6 +1197,16 @@ trim_left_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, stat return s[i:] } +/* + trims the input string `s` from the right until the procedure `p` returns false + does not allocate - only returns a cut variant of the input string + returns an empty string when no match was found at all + + find :: proc(r: rune) -> bool { + return r != 't' + } + strings.trim_left_proc("testing", find) -> "test" +*/ trim_right_proc :: proc(s: string, p: proc(rune) -> bool) -> string { i := last_index_proc(s, p, false) if i >= 0 && s[i] >= utf8.RUNE_SELF { @@ -843,6 +1218,10 @@ trim_right_proc :: proc(s: string, p: proc(rune) -> bool) -> string { return s[0:i] } +/* + trims the input string `s` from the right until the procedure `p` with state returns false + returns an empty string when no match was found at all +*/ trim_right_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: rawptr) -> string { i := last_index_proc_with_state(s, p, state, false) if i >= 0 && s[i] >= utf8.RUNE_SELF { @@ -854,7 +1233,7 @@ trim_right_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, sta return s[0:i] } - +// procedure for `trim_*_proc` variants, which has a string rawptr cast + rune comparison is_in_cutset :: proc(state: rawptr, r: rune) -> bool { if state == nil { return false @@ -868,7 +1247,7 @@ is_in_cutset :: proc(state: rawptr, r: rune) -> bool { return false } - +// trims the `cutset` string from the `s` string trim_left :: proc(s: string, cutset: string) -> string { if s == "" || cutset == "" { return s @@ -877,6 +1256,7 @@ trim_left :: proc(s: string, cutset: string) -> string { return trim_left_proc_with_state(s, is_in_cutset, &state) } +// trims the `cutset` string from the `s` string from the right trim_right :: proc(s: string, cutset: string) -> string { if s == "" || cutset == "" { return s @@ -885,35 +1265,48 @@ trim_right :: proc(s: string, cutset: string) -> string { return trim_right_proc_with_state(s, is_in_cutset, &state) } +// trims the `cutset` string from the `s` string, both from left and right trim :: proc(s: string, cutset: string) -> string { return trim_right(trim_left(s, cutset), cutset) } +// trims until a valid non space rune: "\t\txyz\t\t" -> "xyz\t\t" trim_left_space :: proc(s: string) -> string { return trim_left_proc(s, is_space) } +// trims from the right until a valid non space rune: "\t\txyz\t\t" -> "\t\txyz" trim_right_space :: proc(s: string) -> string { return trim_right_proc(s, is_space) } +// trims from both sides until a valid non space rune: "\t\txyz\t\t" -> "xyz" trim_space :: proc(s: string) -> string { return trim_right_space(trim_left_space(s)) } - +// trims nul runes from the left: "\x00\x00testing\x00\x00" -> "testing\x00\x00" trim_left_null :: proc(s: string) -> string { return trim_left_proc(s, is_null) } +// trims nul runes from the right: "\x00\x00testing\x00\x00" -> "\x00\x00testing" trim_right_null :: proc(s: string) -> string { return trim_right_proc(s, is_null) } +// trims nul runes from both sides: "\x00\x00testing\x00\x00" -> "testing" trim_null :: proc(s: string) -> string { return trim_right_null(trim_left_null(s)) } +/* + trims a `prefix` string from the start of the `s` string and returns the trimmed string + returns the input string `s` when no prefix was found + + strings.trim_prefix("testing", "test") -> "ing" + strings.trim_prefix("testing", "abc") -> "testing" +*/ trim_prefix :: proc(s, prefix: string) -> string { if has_prefix(s, prefix) { return s[len(prefix):] @@ -921,6 +1314,13 @@ trim_prefix :: proc(s, prefix: string) -> string { return s } +/* + trims a `suffix` string from the end of the `s` string and returns the trimmed string + returns the input string `s` when no suffix was found + + strings.trim_suffix("todo.txt", ".txt") -> "todo" + strings.trim_suffix("todo.doc", ".txt") -> "todo.doc" +*/ trim_suffix :: proc(s, suffix: string) -> string { if has_suffix(s, suffix) { return s[:len(s)-len(suffix)] @@ -928,142 +1328,151 @@ trim_suffix :: proc(s, suffix: string) -> string { return s } -split_multi :: proc(s: string, substrs: []string, skip_empty := false, allocator := context.allocator) -> []string #no_bounds_check { +/* + splits the input string `s` by all possible `substrs` []string + returns the allocated []string, nil on any empty substring or no matches + + splits := [?]string { "---", "~~~", ".", "_", "," } + res := strings.split_multi("testing,this.out_nice---done~~~last", splits[:]) + fmt.eprintln(res) // -> [testing, this, out, nice, done, last] +*/ +split_multi :: proc(s: string, substrs: []string, allocator := context.allocator) -> (buf: []string) #no_bounds_check { if s == "" || len(substrs) <= 0 { - return nil + return } - sublen := len(substrs[0]) - - for substr in substrs[1:] { - sublen = min(sublen, len(substr)) + // disallow "" substr + for substr in substrs { + if len(substr) == 0 { + return + } } - shared := len(s) - sublen + // TODO maybe remove duplicate substrs + // sort substrings by string size, largest to smallest + temp_substrs := slice.clone(substrs, context.temp_allocator) + slice.sort_by(temp_substrs, proc(a, b: string) -> bool { + return len(a) > len(b) + }) - if shared <= 0 { - return nil - } + substrings_found: int + temp := s - // number, index, last - n, i, l := 0, 0, 0 - - // count results - first_pass: for i <= shared { - for substr in substrs { - if s[i:i+sublen] == substr { - if !skip_empty || i - l > 0 { - n += 1 - } - - i += sublen - l = i + // count substr results found in string + first_pass: for len(temp) > 0 { + for substr in temp_substrs { + size := len(substr) + // check range and compare string to substr + if size <= len(temp) && temp[:size] == substr { + substrings_found += 1 + temp = temp[size:] continue first_pass } } - _, skip := utf8.decode_rune_in_string(s[i:]) - i += skip + // step through string + _, skip := utf8.decode_rune_in_string(temp[:]) + temp = temp[skip:] } - if !skip_empty || len(s) - l > 0 { - n += 1 + // skip when no results + if substrings_found < 1 { + return } - if n < 1 { - // no results - return nil - } + buf = make([]string, substrings_found + 1, allocator) + buf_index: int + temp = s + temp_old := temp - buf := make([]string, n, allocator) - - n, i, l = 0, 0, 0 - - // slice results - second_pass: for i <= shared { - for substr in substrs { - if s[i:i+sublen] == substr { - if !skip_empty || i - l > 0 { - buf[n] = s[l:i] - n += 1 - } - - i += sublen - l = i + // gather results in the same fashion + second_pass: for len(temp) > 0 { + for substr in temp_substrs { + size := len(substr) + // check range and compare string to substr + if size <= len(temp) && temp[:size] == substr { + buf[buf_index] = temp_old[:len(temp_old) - len(temp)] + buf_index += 1 + temp = temp[size:] + temp_old = temp continue second_pass } } - _, skip := utf8.decode_rune_in_string(s[i:]) - i += skip + // step through string + _, skip := utf8.decode_rune_in_string(temp[:]) + temp = temp[skip:] } - if !skip_empty || len(s) - l > 0 { - buf[n] = s[l:] - } + buf[buf_index] = temp_old[:] return buf } +// state for the split multi iterator +Split_Multi :: struct { + temp: string, + temp_old: string, + substrs: []string, +} +// returns split multi state with sorted `substrs` +split_multi_init :: proc(s: string, substrs: []string) -> Split_Multi { + // sort substrings, largest to smallest + temp_substrs := slice.clone(substrs, context.temp_allocator) + slice.sort_by(temp_substrs, proc(a, b: string) -> bool { + return len(a) > len(b) + }) - -split_multi_iterator :: proc(s: ^string, substrs: []string, skip_empty := false) -> (string, bool) #no_bounds_check { - if s == nil || s^ == "" || len(substrs) <= 0 { - return "", false + return { + temp = s, + temp_old = s, + substrs = temp_substrs, } +} - sublen := len(substrs[0]) +/* + splits the input string `s` by all possible `substrs` []string in an iterator fashion + returns the split string every iteration, the full string on no match - for substr in substrs[1:] { - sublen = min(sublen, len(substr)) + splits := [?]string { "---", "~~~", ".", "_", "," } + state := strings.split_multi_init("testing,this.out_nice---done~~~last", splits[:]) + for str in strings.split_multi_iterate(&state) { + fmt.eprintln(str) // every iteration -> [testing, this, out, nice, done, last] } - - shared := len(s) - sublen - - if shared <= 0 { - return "", false - } - - // index, last - i, l := 0, 0 - - loop: for i <= shared { +*/ +split_multi_iterate :: proc(using sm: ^Split_Multi) -> (res: string, ok: bool) #no_bounds_check { + pass: for len(temp) > 0 { for substr in substrs { - if s[i:i+sublen] == substr { - if !skip_empty || i - l > 0 { - res := s[l:i] - s^ = s[i:] - return res, true - } + size := len(substr) - i += sublen - l = i - - continue loop + // check range and compare string to substr + if size <= len(temp) && temp[:size] == substr { + res = temp_old[:len(temp_old) - len(temp)] + temp = temp[size:] + temp_old = temp + ok = true + return } } - _, skip := utf8.decode_rune_in_string(s[i:]) - i += skip + // step through string + _, skip := utf8.decode_rune_in_string(temp[:]) + temp = temp[skip:] } - if !skip_empty || len(s) - l > 0 { - res := s[l:] - s^ = s[len(s):] - return res, true + // allow last iteration + if temp_old != "" { + res = temp_old[:] + ok = true + temp_old = "" } - return "", false + return } - - - - - // scrub scruvs invalid utf-8 characters and replaces them with the replacement string // Adjacent invalid bytes are only replaced once scrub :: proc(s: string, replacement: string, allocator := context.allocator) -> string { @@ -1098,7 +1507,13 @@ scrub :: proc(s: string, replacement: string, allocator := context.allocator) -> return to_string(b) } +/* + returns a reversed version of the `s` string + a := "abcxyz" + b := strings.reverse(a) + fmt.eprintln(a, b) // abcxyz zyxcba +*/ reverse :: proc(s: string, allocator := context.allocator) -> string { str := s n := len(str) @@ -1114,12 +1529,19 @@ reverse :: proc(s: string, allocator := context.allocator) -> string { return string(buf) } +/* + expands the string to a grid spaced by `tab_size` whenever a `\t` character appears + returns the tabbed string, panics on tab_size <= 0 + + strings.expand_tabs("abc1\tabc2\tabc3", 4) -> abc1 abc2 abc3 + strings.expand_tabs("abc1\tabc2\tabc3", 5) -> abc1 abc2 abc3 + strings.expand_tabs("abc1\tabc2\tabc3", 6) -> abc1 abc2 abc3 +*/ expand_tabs :: proc(s: string, tab_size: int, allocator := context.allocator) -> string { if tab_size <= 0 { panic("tab size must be positive") } - if s == "" { return "" } @@ -1157,7 +1579,16 @@ expand_tabs :: proc(s: string, tab_size: int, allocator := context.allocator) -> return to_string(b) } +/* + splits the `str` string by the seperator `sep` string and returns 3 parts + `head`: before the split, `match`: the seperator, `tail`: the end of the split + returns the input string when the `sep` was not found + text := "testing this out" + strings.partition(text, " this ") -> head: "testing", match: " this ", tail: "out" + strings.partition(text, "hi") -> head: "testing t", match: "hi", tail: "s out" + strings.partition(text, "xyz") -> head: "testing this out", match: "", tail: "" +*/ partition :: proc(str, sep: string) -> (head, match, tail: string) { i := index(str, sep) if i == -1 { @@ -1346,3 +1777,35 @@ fields_proc :: proc(s: string, f: proc(rune) -> bool, allocator := context.alloc return substrings[:] } + + +// `fields_iterator` returns the first run of characters in `s` that does not contain white space, defined by `unicode.is_space` +// `s` will then start from any space after the substring, or be an empty string if the substring was the remaining characters +fields_iterator :: proc(s: ^string) -> (field: string, ok: bool) { + start, end := -1, -1 + for r, offset in s { + end = offset + if unicode.is_space(r) { + if start >= 0 { + field = s[start : end] + ok = true + s^ = s[end:] + return + } + } else { + if start < 0 { + start = end + } + } + } + + // if either of these are true, the string did not contain any characters + if end < 0 || start < 0 { + return "", false + } + + field = s[:len(s)] + ok = true + s^ = s[len(s):] + return +} diff --git a/core/sync/atomic.odin b/core/sync/atomic.odin index 21dcea178..f537764c4 100644 --- a/core/sync/atomic.odin +++ b/core/sync/atomic.odin @@ -2,167 +2,44 @@ package sync import "core:intrinsics" -Ordering :: enum { - Relaxed, // Monotonic - Release, - Acquire, - Acquire_Release, - Sequentially_Consistent, -} - -strongest_failure_ordering_table := [Ordering]Ordering{ - .Relaxed = .Relaxed, - .Release = .Relaxed, - .Acquire = .Acquire, - .Acquire_Release = .Acquire, - .Sequentially_Consistent = .Sequentially_Consistent, -} - -strongest_failure_ordering :: #force_inline proc(order: Ordering) -> Ordering { - return strongest_failure_ordering_table[order] -} - -fence :: #force_inline proc($order: Ordering) { - when order == .Relaxed { #panic("there is no such thing as a relaxed fence") } - else when order == .Release { intrinsics.atomic_fence_rel() } - else when order == .Acquire { intrinsics.atomic_fence_acq() } - else when order == .Acquire_Release { intrinsics.atomic_fence_acqrel() } - else when order == .Sequentially_Consistent { intrinsics.atomic_fence() } - else { #panic("unknown order") } +cpu_relax :: intrinsics.cpu_relax + +/* +Atomic_Memory_Order :: enum { + Relaxed = 0, + Consume = 1, + Acquire = 2, + Release = 3, + Acq_Rel = 4, + Seq_Cst = 5, } +*/ +Atomic_Memory_Order :: intrinsics.Atomic_Memory_Order -atomic_store :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) { - when order == .Relaxed { intrinsics.atomic_store_relaxed(dst, val) } - else when order == .Release { intrinsics.atomic_store_rel(dst, val) } - else when order == .Sequentially_Consistent { intrinsics.atomic_store(dst, val) } - else when order == .Acquire { #panic("there is not such thing as an acquire store") } - else when order == .Acquire_Release { #panic("there is not such thing as an acquire/release store") } - else { #panic("unknown order") } -} - -atomic_load :: #force_inline proc(dst: ^$T, $order: Ordering) -> T { - when order == .Relaxed { return intrinsics.atomic_load_relaxed(dst) } - else when order == .Acquire { return intrinsics.atomic_load_acq(dst) } - else when order == .Sequentially_Consistent { return intrinsics.atomic_load(dst) } - else when order == .Release { #panic("there is no such thing as a release load") } - else when order == .Acquire_Release { #panic("there is no such thing as an acquire/release load") } - else { #panic("unknown order") } -} - -atomic_swap :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { - when order == .Relaxed { return intrinsics.atomic_xchg_relaxed(dst, val) } - else when order == .Release { return intrinsics.atomic_xchg_rel(dst, val) } - else when order == .Acquire { return intrinsics.atomic_xchg_acq(dst, val) } - else when order == .Acquire_Release { return intrinsics.atomic_xchg_acqrel(dst, val) } - else when order == .Sequentially_Consistent { return intrinsics.atomic_xchg(dst, val) } - else { #panic("unknown order") } -} - -atomic_compare_exchange :: #force_inline proc(dst: ^$T, old, new: T, $success, $failure: Ordering) -> (val: T, ok: bool) { - when failure == .Relaxed { - when success == .Relaxed { return intrinsics.atomic_cxchg_relaxed(dst, old, new) } - else when success == .Acquire { return intrinsics.atomic_cxchg_acq_failrelaxed(dst, old, new) } - else when success == .Acquire_Release { return intrinsics.atomic_cxchg_acqrel_failrelaxed(dst, old, new) } - else when success == .Sequentially_Consistent { return intrinsics.atomic_cxchg_failrelaxed(dst, old, new) } - else when success == .Release { return intrinsics.atomic_cxchg_rel(dst, old, new) } - else { #panic("an unknown ordering combination") } - } else when failure == .Acquire { - when success == .Release { return intrinsics.atomic_cxchg_acqrel(dst, old, new) } - else when success == .Acquire { return intrinsics.atomic_cxchg_acq(dst, old, new) } - else { #panic("an unknown ordering combination") } - } else when failure == .Sequentially_Consistent { - when success == .Sequentially_Consistent { return intrinsics.atomic_cxchg(dst, old, new) } - else { #panic("an unknown ordering combination") } - } else when failure == .Acquire_Release { - #panic("there is not such thing as an acquire/release failure ordering") - } else when failure == .Release { - when success == .Acquire { return instrinsics.atomic_cxchg_failacq(dst, old, new) } - else { #panic("an unknown ordering combination") } - } else { - return T{}, false - } - -} - -atomic_compare_exchange_weak :: #force_inline proc(dst: ^$T, old, new: T, $success, $failure: Ordering) -> (val: T, ok: bool) { - when failure == .Relaxed { - when success == .Relaxed { return intrinsics.atomic_cxchgweak_relaxed(dst, old, new) } - else when success == .Acquire { return intrinsics.atomic_cxchgweak_acq_failrelaxed(dst, old, new) } - else when success == .Acquire_Release { return intrinsics.atomic_cxchgweak_acqrel_failrelaxed(dst, old, new) } - else when success == .Sequentially_Consistent { return intrinsics.atomic_cxchgweak_failrelaxed(dst, old, new) } - else when success == .Release { return intrinsics.atomic_cxchgweak_rel(dst, old, new) } - else { #panic("an unknown ordering combination") } - } else when failure == .Acquire { - when success == .Release { return intrinsics.atomic_cxchgweak_acqrel(dst, old, new) } - else when success == .Acquire { return intrinsics.atomic_cxchgweak_acq(dst, old, new) } - else { #panic("an unknown ordering combination") } - } else when failure == .Sequentially_Consistent { - when success == .Sequentially_Consistent { return intrinsics.atomic_cxchgweak(dst, old, new) } - else { #panic("an unknown ordering combination") } - } else when failure == .Acquire_Release { - #panic("there is not such thing as an acquire/release failure ordering") - } else when failure == .Release { - when success == .Acquire { return intrinsics.atomic_cxchgweak_failacq(dst, old, new) } - else { #panic("an unknown ordering combination") } - } else { - return T{}, false - } - -} - - -atomic_add :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { - when order == .Relaxed { return intrinsics.atomic_add_relaxed(dst, val) } - else when order == .Release { return intrinsics.atomic_add_rel(dst, val) } - else when order == .Acquire { return intrinsics.atomic_add_acq(dst, val) } - else when order == .Acquire_Release { return intrinsics.atomic_add_acqrel(dst, val) } - else when order == .Sequentially_Consistent { return intrinsics.atomic_add(dst, val) } - else { #panic("unknown order") } -} - -atomic_sub :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { - when order == .Relaxed { return intrinsics.atomic_sub_relaxed(dst, val) } - else when order == .Release { return intrinsics.atomic_sub_rel(dst, val) } - else when order == .Acquire { return intrinsics.atomic_sub_acq(dst, val) } - else when order == .Acquire_Release { return intrinsics.atomic_sub_acqrel(dst, val) } - else when order == .Sequentially_Consistent { return intrinsics.atomic_sub(dst, val) } - else { #panic("unknown order") } -} - -atomic_and :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { - when order == .Relaxed { return intrinsics.atomic_and_relaxed(dst, val) } - else when order == .Release { return intrinsics.atomic_and_rel(dst, val) } - else when order == .Acquire { return intrinsics.atomic_and_acq(dst, val) } - else when order == .Acquire_Release { return intrinsics.atomic_and_acqrel(dst, val) } - else when order == .Sequentially_Consistent { return intrinsics.atomic_and(dst, val) } - else { #panic("unknown order") } -} - -atomic_nand :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { - when order == .Relaxed { return intrinsics.atomic_nand_relaxed(dst, val) } - else when order == .Release { return intrinsics.atomic_nand_rel(dst, val) } - else when order == .Acquire { return intrinsics.atomic_nand_acq(dst, val) } - else when order == .Acquire_Release { return intrinsics.atomic_nand_acqrel(dst, val) } - else when order == .Sequentially_Consistent { return intrinsics.atomic_nand(dst, val) } - else { #panic("unknown order") } -} - -atomic_or :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { - when order == .Relaxed { return intrinsics.atomic_or_relaxed(dst, val) } - else when order == .Release { return intrinsics.atomic_or_rel(dst, val) } - else when order == .Acquire { return intrinsics.atomic_or_acq(dst, val) } - else when order == .Acquire_Release { return intrinsics.atomic_or_acqrel(dst, val) } - else when order == .Sequentially_Consistent { return intrinsics.atomic_or(dst, val) } - else { #panic("unknown order") } -} - -atomic_xor :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { - when order == .Relaxed { return intrinsics.atomic_xor_relaxed(dst, val) } - else when order == .Release { return intrinsics.atomic_xor_rel(dst, val) } - else when order == .Acquire { return intrinsics.atomic_xor_acq(dst, val) } - else when order == .Acquire_Release { return intrinsics.atomic_xor_acqrel(dst, val) } - else when order == .Sequentially_Consistent { return intrinsics.atomic_xor(dst, val) } - else { #panic("unknown order") } -} +atomic_thread_fence :: intrinsics.atomic_thread_fence +atomic_signal_fence :: intrinsics.atomic_signal_fence +atomic_store :: intrinsics.atomic_store +atomic_store_explicit :: intrinsics.atomic_store_explicit +atomic_load :: intrinsics.atomic_load +atomic_load_explicit :: intrinsics.atomic_load_explicit +atomic_add :: intrinsics.atomic_add +atomic_add_explicit :: intrinsics.atomic_add_explicit +atomic_sub :: intrinsics.atomic_sub +atomic_sub_explicit :: intrinsics.atomic_sub_explicit +atomic_and :: intrinsics.atomic_and +atomic_and_explicit :: intrinsics.atomic_and_explicit +atomic_nand :: intrinsics.atomic_nand +atomic_nand_explicit :: intrinsics.atomic_nand_explicit +atomic_or :: intrinsics.atomic_or +atomic_or_explicit :: intrinsics.atomic_or_explicit +atomic_xor :: intrinsics.atomic_xor +atomic_xor_explicit :: intrinsics.atomic_xor_explicit +atomic_exchange :: intrinsics.atomic_exchange +atomic_exchange_explicit :: intrinsics.atomic_exchange_explicit +// Returns value and optional ok boolean +atomic_compare_exchange_strong :: intrinsics.atomic_compare_exchange_strong +atomic_compare_exchange_strong_explicit :: intrinsics.atomic_compare_exchange_strong_explicit +atomic_compare_exchange_weak :: intrinsics.atomic_compare_exchange_weak +atomic_compare_exchange_weak_explicit :: intrinsics.atomic_compare_exchange_weak_explicit \ No newline at end of file diff --git a/core/sync/barrier.odin b/core/sync/barrier.odin deleted file mode 100644 index 13c870a3b..000000000 --- a/core/sync/barrier.odin +++ /dev/null @@ -1,80 +0,0 @@ -package sync - - -/* -A barrier enabling multiple threads to synchronize the beginning of some computation -Example: - - package example - - import "core:fmt" - import "core:sync" - import "core:thread" - - barrier := &sync.Barrier{}; - - main :: proc() { - fmt.println("Start"); - - THREAD_COUNT :: 4; - threads: [THREAD_COUNT]^thread.Thread; - - sync.barrier_init(barrier, THREAD_COUNT); - defer sync.barrier_destroy(barrier); - - - for _, i in threads { - threads[i] = thread.create_and_start(proc(t: ^thread.Thread) { - // Same messages will be printed together but without any interleaving - fmt.println("Getting ready!"); - sync.barrier_wait(barrier); - fmt.println("Off their marks they go!"); - }); - } - - for t in threads { - thread.destroy(t); // join and free thread - } - fmt.println("Finished"); - } -*/ -Barrier :: struct { - mutex: Blocking_Mutex, - cond: Condition, - index: int, - generation_id: int, - thread_count: int, -} - -barrier_init :: proc(b: ^Barrier, thread_count: int) { - blocking_mutex_init(&b.mutex) - condition_init(&b.cond, &b.mutex) - b.index = 0 - b.generation_id = 0 - b.thread_count = thread_count -} - -barrier_destroy :: proc(b: ^Barrier) { - blocking_mutex_destroy(&b.mutex) - condition_destroy(&b.cond) -} - -// Block the current thread until all threads have rendezvoused -// Barrier can be reused after all threads rendezvoused once, and can be used continuously -barrier_wait :: proc(b: ^Barrier) -> (is_leader: bool) { - blocking_mutex_lock(&b.mutex) - defer blocking_mutex_unlock(&b.mutex) - local_gen := b.generation_id - b.index += 1 - if b.index < b.thread_count { - for local_gen == b.generation_id && b.index < b.thread_count { - condition_wait_for(&b.cond) - } - return false - } - - b.index = 0 - b.generation_id += 1 - condition_broadcast(&b.cond) - return true -} diff --git a/core/sync/channel.odin b/core/sync/channel.odin deleted file mode 100644 index 82b9504f4..000000000 --- a/core/sync/channel.odin +++ /dev/null @@ -1,889 +0,0 @@ -package sync - -import "core:mem" -import "core:time" -import "core:intrinsics" -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 intrinsics.atomic_load(&c.closed) { - return -1 - } - return intrinsics.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: Condition, - 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 - mutex_init(&c.mutex) - condition_init(&c.cond, &c.mutex) - c.allocator = context.allocator - c.closed = false - - return c -} - - -raw_channel_destroy :: proc(c: ^Raw_Channel) { - if c == nil { - return - } - context.allocator = c.allocator - intrinsics.atomic_store(&c.closed, true) - - condition_destroy(&c.cond) - mutex_destroy(&c.mutex) - 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) - intrinsics.atomic_store(&c.closed, true) - - // Release readers and writers - raw_channel_wait_queue_broadcast(c.recvq) - raw_channel_wait_queue_broadcast(c.sendq) - condition_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 { - condition_wait_for(&c.cond) - } - } else if c.len > 0 { // TODO(bill): determine correct behaviour - if !block { - return false - } - condition_wait_for(&c.cond) - } else if c.len == 0 && !block { - return false - } - - send(c, msg) - condition_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) - } - intrinsics.atomic_store(&c.ready, true) - for c.len < 1 { - raw_channel_wait_queue_signal(c.sendq) - condition_wait_for(&c.cond) - } - intrinsics.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 - condition_signal(&c.cond) - } - } else { - condition_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/channel_unix.odin b/core/sync/channel_unix.odin deleted file mode 100644 index 47aa46004..000000000 --- a/core/sync/channel_unix.odin +++ /dev/null @@ -1,16 +0,0 @@ -// +build linux, darwin, freebsd, openbsd -package sync - -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/channel_windows.odin b/core/sync/channel_windows.odin deleted file mode 100644 index 5d469ffff..000000000 --- a/core/sync/channel_windows.odin +++ /dev/null @@ -1,33 +0,0 @@ -package sync - -import "core:intrinsics" -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 := intrinsics.atomic_load(state) - for v == 0 { - win32.WaitOnAddress(state, &v, size_of(state^), ms) - v = intrinsics.atomic_load(state) - } - intrinsics.atomic_store(state, 0) -} - -raw_channel_wait_queue_signal :: proc(q: ^Raw_Channel_Wait_Queue) { - for x := q; x != nil; x = x.next { - intrinsics.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 { - intrinsics.atomic_add(x.state, 1) - win32.WakeByAddressAll(x.state) - } -} diff --git a/core/sync/sync2/extended.odin b/core/sync/extended.odin similarity index 85% rename from core/sync/sync2/extended.odin rename to core/sync/extended.odin index deb48a22d..24f7c096c 100644 --- a/core/sync/sync2/extended.odin +++ b/core/sync/extended.odin @@ -1,4 +1,4 @@ -package sync2 +package sync import "core:time" @@ -146,10 +146,10 @@ Auto_Reset_Event :: struct { } auto_reset_event_signal :: proc(e: ^Auto_Reset_Event) { - old_status := atomic_load_relaxed(&e.status) + old_status := atomic_load_explicit(&e.status, .Relaxed) 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 { + if _, ok := atomic_compare_exchange_weak_explicit(&e.status, old_status, new_status, .Release, .Relaxed); ok { break } @@ -160,7 +160,7 @@ auto_reset_event_signal :: proc(e: ^Auto_Reset_Event) { } auto_reset_event_wait :: proc(e: ^Auto_Reset_Event) { - old_status := atomic_sub_acquire(&e.status, 1) + old_status := atomic_sub_explicit(&e.status, 1, .Acquire) if old_status < 1 { sema_wait(&e.sema) } @@ -174,14 +174,14 @@ Ticket_Mutex :: struct { } ticket_mutex_lock :: #force_inline proc(m: ^Ticket_Mutex) { - ticket := atomic_add_relaxed(&m.ticket, 1) - for ticket != atomic_load_acquire(&m.serving) { + ticket := atomic_add_explicit(&m.ticket, 1, .Relaxed) + for ticket != atomic_load_explicit(&m.serving, .Acquire) { cpu_relax() } } ticket_mutex_unlock :: #force_inline proc(m: ^Ticket_Mutex) { - atomic_add_relaxed(&m.serving, 1) + atomic_add_explicit(&m.serving, 1, .Relaxed) } @(deferred_in=ticket_mutex_unlock) ticket_mutex_guard :: proc(m: ^Ticket_Mutex) -> bool { @@ -196,18 +196,18 @@ Benaphore :: struct { } benaphore_lock :: proc(b: ^Benaphore) { - if atomic_add_acquire(&b.counter, 1) > 1 { + if atomic_add_explicit(&b.counter, 1, .Acquire) > 1 { sema_wait(&b.sema) } } benaphore_try_lock :: proc(b: ^Benaphore) -> bool { - v, _ := atomic_compare_exchange_strong_acquire(&b.counter, 1, 0) + v, _ := atomic_compare_exchange_strong_explicit(&b.counter, 1, 0, .Acquire, .Acquire) return v == 0 } benaphore_unlock :: proc(b: ^Benaphore) { - if atomic_sub_release(&b.counter, 1) > 0 { + if atomic_sub_explicit(&b.counter, 1, .Release) > 0 { sema_post(&b.sema) } } @@ -227,7 +227,7 @@ Recursive_Benaphore :: struct { recursive_benaphore_lock :: proc(b: ^Recursive_Benaphore) { tid := current_thread_id() - if atomic_add_acquire(&b.counter, 1) > 1 { + if atomic_add_explicit(&b.counter, 1, .Acquire) > 1 { if tid != b.owner { sema_wait(&b.sema) } @@ -240,10 +240,10 @@ recursive_benaphore_lock :: proc(b: ^Recursive_Benaphore) { recursive_benaphore_try_lock :: proc(b: ^Recursive_Benaphore) -> bool { tid := current_thread_id() if b.owner == tid { - atomic_add_acquire(&b.counter, 1) + atomic_add_explicit(&b.counter, 1, .Acquire) } - if v, _ := atomic_compare_exchange_strong_acquire(&b.counter, 1, 0); v != 0 { + if v, _ := atomic_compare_exchange_strong_explicit(&b.counter, 1, 0, .Acquire, .Acquire); v != 0 { return false } // inside the lock @@ -260,7 +260,7 @@ recursive_benaphore_unlock :: proc(b: ^Recursive_Benaphore) { if recursion == 0 { b.owner = 0 } - if atomic_sub_release(&b.counter, 1) > 0 { + if atomic_sub_explicit(&b.counter, 1, .Release) > 0 { if recursion == 0 { sema_post(&b.sema) } @@ -293,12 +293,12 @@ once_do :: proc(o: ^Once, fn: proc()) { defer mutex_unlock(&o.m) if !o.done { fn() - atomic_store_release(&o.done, true) + atomic_store_explicit(&o.done, true, .Release) } } - if atomic_load_acquire(&o.done) == false { + if atomic_load_explicit(&o.done, .Acquire) == false { do_slow(o, fn) } } diff --git a/core/sync/sync2/futex_darwin.odin b/core/sync/futex_darwin.odin similarity index 99% rename from core/sync/sync2/futex_darwin.odin rename to core/sync/futex_darwin.odin index 9dad8d375..88e354827 100644 --- a/core/sync/sync2/futex_darwin.odin +++ b/core/sync/futex_darwin.odin @@ -1,6 +1,6 @@ //+private //+build darwin -package sync2 +package sync import "core:c" import "core:time" diff --git a/core/sync/sync2/futex_freebsd.odin b/core/sync/futex_freebsd.odin similarity index 98% rename from core/sync/sync2/futex_freebsd.odin rename to core/sync/futex_freebsd.odin index 2cbdb4aaa..2e1d065bc 100644 --- a/core/sync/sync2/futex_freebsd.odin +++ b/core/sync/futex_freebsd.odin @@ -1,6 +1,6 @@ //+private //+build freebsd -package sync2 +package sync import "core:c" import "core:os" diff --git a/core/sync/sync2/futex_linux.odin b/core/sync/futex_linux.odin similarity index 94% rename from core/sync/sync2/futex_linux.odin rename to core/sync/futex_linux.odin index fca28cace..c429a9d64 100644 --- a/core/sync/sync2/futex_linux.odin +++ b/core/sync/futex_linux.odin @@ -1,6 +1,6 @@ //+private //+build linux -package sync2 +package sync import "core:c" import "core:time" @@ -14,12 +14,6 @@ FUTEX_PRIVATE_FLAG :: 128 FUTEX_WAIT_PRIVATE :: (FUTEX_WAIT | FUTEX_PRIVATE_FLAG) FUTEX_WAKE_PRIVATE :: (FUTEX_WAKE | FUTEX_PRIVATE_FLAG) -foreign import libc "system:c" - -foreign libc { - __errno_location :: proc "c" () -> ^c.int --- -} - ESUCCESS :: 0 EINTR :: -4 EAGAIN :: -11 diff --git a/core/sync/sync2/futex_openbsd.odin b/core/sync/futex_openbsd.odin similarity index 99% rename from core/sync/sync2/futex_openbsd.odin rename to core/sync/futex_openbsd.odin index dbc80747b..6ac9d3efb 100644 --- a/core/sync/sync2/futex_openbsd.odin +++ b/core/sync/futex_openbsd.odin @@ -1,6 +1,6 @@ //+private //+build openbsd -package sync2 +package sync import "core:c" import "core:os" diff --git a/core/sync/sync2/futex_windows.odin b/core/sync/futex_windows.odin similarity index 98% rename from core/sync/sync2/futex_windows.odin rename to core/sync/futex_windows.odin index 200a119ff..1c9d8b845 100644 --- a/core/sync/sync2/futex_windows.odin +++ b/core/sync/futex_windows.odin @@ -1,6 +1,6 @@ //+private //+build windows -package sync2 +package sync import "core:time" diff --git a/core/sync/sync2/primitives.odin b/core/sync/primitives.odin similarity index 99% rename from core/sync/sync2/primitives.odin rename to core/sync/primitives.odin index 6d056d439..483f85343 100644 --- a/core/sync/sync2/primitives.odin +++ b/core/sync/primitives.odin @@ -1,4 +1,4 @@ -package sync2 +package sync import "core:time" diff --git a/core/sync/sync2/primitives_atomic.odin b/core/sync/primitives_atomic.odin similarity index 93% rename from core/sync/sync2/primitives_atomic.odin rename to core/sync/primitives_atomic.odin index 5fc6fba85..11fff4e60 100644 --- a/core/sync/sync2/primitives_atomic.odin +++ b/core/sync/primitives_atomic.odin @@ -1,4 +1,4 @@ -package sync2 +package sync import "core:time" @@ -24,7 +24,7 @@ atomic_mutex_lock :: proc(m: ^Atomic_Mutex) { new_state := curr_state // Make a copy of it spin_lock: for spin in 0.. bool { - _, ok := atomic_compare_exchange_strong_acquire(&m.state, .Unlocked, .Locked) + _, ok := atomic_compare_exchange_strong_explicit(&m.state, .Unlocked, .Locked, .Acquire, .Consume) return ok } @@ -291,7 +290,7 @@ Queue_Item :: struct { @(private="file") queue_item_wait :: proc(item: ^Queue_Item) { - for atomic_load_acquire(&item.futex) == 0 { + for atomic_load_explicit(&item.futex, .Acquire) == 0 { futex_wait(&item.futex, 0) cpu_relax() } @@ -299,7 +298,7 @@ queue_item_wait :: proc(item: ^Queue_Item) { @(private="file") queue_item_wait_with_timeout :: proc(item: ^Queue_Item, duration: time.Duration) -> bool { start := time.tick_now() - for atomic_load_acquire(&item.futex) == 0 { + for atomic_load_explicit(&item.futex, .Acquire) == 0 { remaining := duration - time.tick_since(start) if remaining < 0 { return false @@ -313,7 +312,7 @@ queue_item_wait_with_timeout :: proc(item: ^Queue_Item, duration: time.Duration) } @(private="file") queue_item_signal :: proc(item: ^Queue_Item) { - atomic_store_release(&item.futex, 1) + atomic_store_explicit(&item.futex, 1, .Release) futex_signal(&item.futex) } diff --git a/core/sync/sync2/primitives_darwin.odin b/core/sync/primitives_darwin.odin similarity index 98% rename from core/sync/sync2/primitives_darwin.odin rename to core/sync/primitives_darwin.odin index 66995bd01..514f66f3e 100644 --- a/core/sync/sync2/primitives_darwin.odin +++ b/core/sync/primitives_darwin.odin @@ -1,6 +1,6 @@ //+build darwin //+private -package sync2 +package sync import "core:c" import "core:time" diff --git a/core/sync/primitives_freebsd.odin b/core/sync/primitives_freebsd.odin new file mode 100644 index 000000000..b88fca181 --- /dev/null +++ b/core/sync/primitives_freebsd.odin @@ -0,0 +1,46 @@ +//+build freebsd +//+private +package sync + +import "core:os" +import "core:time" + +_current_thread_id :: proc "contextless" () -> int { + return os.current_thread_id() +} + +_Mutex :: struct { + mutex: Atomic_Mutex, +} + +_mutex_lock :: proc(m: ^Mutex) { + atomic_mutex_lock(&m.impl.mutex) +} + +_mutex_unlock :: proc(m: ^Mutex) { + atomic_mutex_unlock(&m.impl.mutex) +} + +_mutex_try_lock :: proc(m: ^Mutex) -> bool { + return atomic_mutex_try_lock(&m.impl.mutex) +} + +_Cond :: struct { + cond: Atomic_Cond, +} + +_cond_wait :: proc(c: ^Cond, m: ^Mutex) { + atomic_cond_wait(&c.impl.cond, &m.impl.mutex) +} + +_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, duration: time.Duration) -> bool { + return atomic_cond_wait_with_timeout(&c.impl.cond, &m.impl.mutex, duration) +} + +_cond_signal :: proc(c: ^Cond) { + atomic_cond_signal(&c.impl.cond) +} + +_cond_broadcast :: proc(c: ^Cond) { + atomic_cond_broadcast(&c.impl.cond) +} diff --git a/core/sync/primitives_internal.odin b/core/sync/primitives_internal.odin new file mode 100644 index 000000000..15967d45e --- /dev/null +++ b/core/sync/primitives_internal.odin @@ -0,0 +1,125 @@ +//+private +package sync + +when #config(ODIN_SYNC_RECURSIVE_MUTEX_USE_FUTEX, true) { + _Recursive_Mutex :: struct { + owner: Futex, + recursion: i32, + } + + _recursive_mutex_lock :: proc(m: ^Recursive_Mutex) { + tid := Futex(current_thread_id()) + for { + prev_owner := atomic_compare_exchange_strong_explicit(&m.impl.owner, tid, 0, .Acquire, .Acquire) + switch prev_owner { + case 0, tid: + m.impl.recursion += 1 + // inside the lock + return + } + + futex_wait(&m.impl.owner, u32(prev_owner)) + } + } + + _recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) { + m.impl.recursion -= 1 + if m.impl.recursion != 0 { + return + } + atomic_exchange_explicit(&m.impl.owner, 0, .Release) + + futex_signal(&m.impl.owner) + // outside the lock + + } + + _recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool { + tid := Futex(current_thread_id()) + prev_owner := atomic_compare_exchange_strong_explicit(&m.impl.owner, tid, 0, .Acquire, .Acquire) + switch prev_owner { + case 0, tid: + m.impl.recursion += 1 + // inside the lock + return true + } + return false + } +} else { + _Recursive_Mutex :: struct { + owner: int, + recursion: int, + mutex: Mutex, + } + + _recursive_mutex_lock :: proc(m: ^Recursive_Mutex) { + tid := 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 := 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 := 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 + } +} + + +when ODIN_OS != .Windows { + _RW_Mutex :: struct { + mutex: Atomic_RW_Mutex, + } + + _rw_mutex_lock :: proc(rw: ^RW_Mutex) { + atomic_rw_mutex_lock(&rw.impl.mutex) + } + + _rw_mutex_unlock :: proc(rw: ^RW_Mutex) { + atomic_rw_mutex_unlock(&rw.impl.mutex) + } + + _rw_mutex_try_lock :: proc(rw: ^RW_Mutex) -> bool { + return atomic_rw_mutex_try_lock(&rw.impl.mutex) + } + + _rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) { + atomic_rw_mutex_shared_lock(&rw.impl.mutex) + } + + _rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) { + atomic_rw_mutex_shared_unlock(&rw.impl.mutex) + } + + _rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool { + return atomic_rw_mutex_try_shared_lock(&rw.impl.mutex) + } + +} \ No newline at end of file diff --git a/core/sync/primitives_linux.odin b/core/sync/primitives_linux.odin new file mode 100644 index 000000000..0a9f0cc33 --- /dev/null +++ b/core/sync/primitives_linux.odin @@ -0,0 +1,47 @@ +//+build linux +//+private +package sync + +import "core:sys/unix" +import "core:time" + +_current_thread_id :: proc "contextless" () -> int { + return unix.sys_gettid() +} + + +_Mutex :: struct { + mutex: Atomic_Mutex, +} + +_mutex_lock :: proc(m: ^Mutex) { + atomic_mutex_lock(&m.impl.mutex) +} + +_mutex_unlock :: proc(m: ^Mutex) { + atomic_mutex_unlock(&m.impl.mutex) +} + +_mutex_try_lock :: proc(m: ^Mutex) -> bool { + return atomic_mutex_try_lock(&m.impl.mutex) +} + +_Cond :: struct { + cond: Atomic_Cond, +} + +_cond_wait :: proc(c: ^Cond, m: ^Mutex) { + atomic_cond_wait(&c.impl.cond, &m.impl.mutex) +} + +_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, duration: time.Duration) -> bool { + return atomic_cond_wait_with_timeout(&c.impl.cond, &m.impl.mutex, duration) +} + +_cond_signal :: proc(c: ^Cond) { + atomic_cond_signal(&c.impl.cond) +} + +_cond_broadcast :: proc(c: ^Cond) { + atomic_cond_broadcast(&c.impl.cond) +} diff --git a/core/sync/primitives_openbsd.odin b/core/sync/primitives_openbsd.odin new file mode 100644 index 000000000..7794016f8 --- /dev/null +++ b/core/sync/primitives_openbsd.odin @@ -0,0 +1,46 @@ +//+build openbsd +//+private +package sync + +import "core:os" +import "core:time" + +_current_thread_id :: proc "contextless" () -> int { + return os.current_thread_id() +} + +_Mutex :: struct { + mutex: Atomic_Mutex, +} + +_mutex_lock :: proc(m: ^Mutex) { + atomic_mutex_lock(&m.impl.mutex) +} + +_mutex_unlock :: proc(m: ^Mutex) { + atomic_mutex_unlock(&m.impl.mutex) +} + +_mutex_try_lock :: proc(m: ^Mutex) -> bool { + return atomic_mutex_try_lock(&m.impl.mutex) +} + +_Cond :: struct { + cond: Atomic_Cond, +} + +_cond_wait :: proc(c: ^Cond, m: ^Mutex) { + atomic_cond_wait(&c.impl.cond, &m.impl.mutex) +} + +_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, duration: time.Duration) -> bool { + return atomic_cond_wait_with_timeout(&c.impl.cond, &m.impl.mutex, duration) +} + +_cond_signal :: proc(c: ^Cond) { + atomic_cond_signal(&c.impl.cond) +} + +_cond_broadcast :: proc(c: ^Cond) { + atomic_cond_broadcast(&c.impl.cond) +} diff --git a/core/sync/sync2/primitives_windows.odin b/core/sync/primitives_windows.odin similarity index 99% rename from core/sync/sync2/primitives_windows.odin rename to core/sync/primitives_windows.odin index ca7a5c9ee..055167892 100644 --- a/core/sync/sync2/primitives_windows.odin +++ b/core/sync/primitives_windows.odin @@ -1,6 +1,6 @@ //+build windows //+private -package sync2 +package sync import "core:time" import win32 "core:sys/windows" diff --git a/core/sync/sync2/sema_internal.odin b/core/sync/sema_internal.odin similarity index 99% rename from core/sync/sync2/sema_internal.odin rename to core/sync/sema_internal.odin index f4027e908..8cf157708 100644 --- a/core/sync/sync2/sema_internal.odin +++ b/core/sync/sema_internal.odin @@ -1,5 +1,5 @@ //+private -package sync2 +package sync import "core:time" diff --git a/core/sync/sync.odin b/core/sync/sync.odin deleted file mode 100644 index 05c86a868..000000000 --- a/core/sync/sync.odin +++ /dev/null @@ -1,123 +0,0 @@ -package sync - -import "core:intrinsics" - -cpu_relax :: #force_inline proc "contextless" () { - intrinsics.cpu_relax() -} - -Condition_Mutex_Ptr :: union{^Mutex, ^Blocking_Mutex} - - -Ticket_Mutex :: struct { - ticket: u64, - serving: u64, -} - -ticket_mutex_init :: proc(m: ^Ticket_Mutex) { - atomic_store(&m.ticket, 0, .Relaxed) - atomic_store(&m.serving, 0, .Relaxed) -} - -ticket_mutex_lock :: #force_inline proc(m: ^Ticket_Mutex) { - ticket := atomic_add(&m.ticket, 1, .Relaxed) - for ticket != atomic_load(&m.serving, .Acquire) { - intrinsics.cpu_relax() - } -} - -ticket_mutex_unlock :: #force_inline proc(m: ^Ticket_Mutex) { - atomic_add(&m.serving, 1, .Relaxed) -} - - -Benaphore :: struct { - counter: int, - sema: Semaphore, -} - -benaphore_init :: proc(b: ^Benaphore) { - intrinsics.atomic_store(&b.counter, 0) - semaphore_init(&b.sema) -} - -benaphore_destroy :: proc(b: ^Benaphore) { - semaphore_destroy(&b.sema) -} - -benaphore_lock :: proc(b: ^Benaphore) { - if intrinsics.atomic_add_acq(&b.counter, 1) > 1 { - semaphore_wait_for(&b.sema) - } -} - -benaphore_try_lock :: proc(b: ^Benaphore) -> bool { - v, _ := intrinsics.atomic_cxchg_acq(&b.counter, 1, 0) - return v == 0 -} - -benaphore_unlock :: proc(b: ^Benaphore) { - if intrinsics.atomic_sub_rel(&b.counter, 1) > 0 { - semaphore_post(&b.sema) - } -} - -Recursive_Benaphore :: struct { - counter: int, - owner: int, - recursion: int, - sema: Semaphore, -} - -recursive_benaphore_init :: proc(b: ^Recursive_Benaphore) { - intrinsics.atomic_store(&b.counter, 0) - semaphore_init(&b.sema) -} - -recursive_benaphore_destroy :: proc(b: ^Recursive_Benaphore) { - semaphore_destroy(&b.sema) -} - -recursive_benaphore_lock :: proc(b: ^Recursive_Benaphore) { - tid := current_thread_id() - if intrinsics.atomic_add_acq(&b.counter, 1) > 1 { - if tid != b.owner { - semaphore_wait_for(&b.sema) - } - } - // inside the lock - b.owner = tid - b.recursion += 1 -} - -recursive_benaphore_try_lock :: proc(b: ^Recursive_Benaphore) -> bool { - tid := current_thread_id() - if b.owner == tid { - intrinsics.atomic_add_acq(&b.counter, 1) - } else { - v, _ := intrinsics.atomic_cxchg_acq(&b.counter, 1, 0) - if v != 0 { - return false - } - // inside the lock - b.owner = tid - } - b.recursion += 1 - return true -} - -recursive_benaphore_unlock :: proc(b: ^Recursive_Benaphore) { - tid := current_thread_id() - assert(tid == b.owner) - b.recursion -= 1 - recursion := b.recursion - if recursion == 0 { - b.owner = 0 - } - if intrinsics.atomic_sub_rel(&b.counter, 1) > 0 { - if recursion == 0 { - semaphore_post(&b.sema) - } - } - // outside the lock -} diff --git a/core/sync/sync2/atomic.odin b/core/sync/sync2/atomic.odin deleted file mode 100644 index fe19f17c8..000000000 --- a/core/sync/sync2/atomic.odin +++ /dev/null @@ -1,79 +0,0 @@ -package sync2 - -import "core:intrinsics" - -cpu_relax :: intrinsics.cpu_relax - -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_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_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_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_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_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_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_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_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_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 - -// Returns value and optional ok boolean -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 - -// Returns value and optional ok boolean -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/primitives_freebsd.odin b/core/sync/sync2/primitives_freebsd.odin deleted file mode 100644 index 2a25a18a4..000000000 --- a/core/sync/sync2/primitives_freebsd.odin +++ /dev/null @@ -1,9 +0,0 @@ -//+build freebsd -//+private -package sync2 - -import "core:os" - -_current_thread_id :: proc "contextless" () -> int { - return os.current_thread_id() -} diff --git a/core/sync/sync2/primitives_internal.odin b/core/sync/sync2/primitives_internal.odin deleted file mode 100644 index eb692c6ae..000000000 --- a/core/sync/sync2/primitives_internal.odin +++ /dev/null @@ -1,184 +0,0 @@ -//+private -package sync2 - -when #config(ODIN_SYNC_RECURSIVE_MUTEX_USE_FUTEX, true) { - _Recursive_Mutex :: struct { - owner: Futex, - recursion: i32, - } - - _recursive_mutex_lock :: proc(m: ^Recursive_Mutex) { - tid := Futex(current_thread_id()) - for { - prev_owner := atomic_compare_exchange_strong_acquire(&m.impl.owner, tid, 0) - switch prev_owner { - case 0, tid: - m.impl.recursion += 1 - // inside the lock - return - } - - futex_wait(&m.impl.owner, u32(prev_owner)) - } - } - - _recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) { - m.impl.recursion -= 1 - if m.impl.recursion != 0 { - return - } - atomic_exchange_release(&m.impl.owner, 0) - - futex_signal(&m.impl.owner) - // outside the lock - - } - - _recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool { - tid := Futex(current_thread_id()) - prev_owner := atomic_compare_exchange_strong_acquire(&m.impl.owner, tid, 0) - switch prev_owner { - case 0, tid: - m.impl.recursion += 1 - // inside the lock - return true - } - return false - } -} else { - _Recursive_Mutex :: struct { - owner: int, - recursion: int, - mutex: Mutex, - } - - _recursive_mutex_lock :: proc(m: ^Recursive_Mutex) { - tid := 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 := 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 := 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 - } -} - - -when ODIN_OS != .Windows { - RW_Mutex_State :: distinct uint - RW_Mutex_State_Half_Width :: size_of(RW_Mutex_State)*8/2 - RW_Mutex_State_Is_Writing :: RW_Mutex_State(1) - RW_Mutex_State_Writer :: RW_Mutex_State(1)<<1 - RW_Mutex_State_Reader :: RW_Mutex_State(1)< bool { - if mutex_try_lock(&rw.impl.mutex) { - state := atomic_load(&rw.impl.state) - if state & RW_Mutex_State_Reader_Mask == 0 { - _ = atomic_or(&rw.impl.state, RW_Mutex_State_Is_Writing) - return true - } - - mutex_unlock(&rw.impl.mutex) - } - return false - } - - _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_compare_exchange_weak(&rw.impl.state, state, state + RW_Mutex_State_Reader) - if ok { - return - } - } - - mutex_lock(&rw.impl.mutex) - _ = atomic_add(&rw.impl.state, RW_Mutex_State_Reader) - mutex_unlock(&rw.impl.mutex) - } - - _rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) { - state := atomic_sub(&rw.impl.state, RW_Mutex_State_Reader) - - if (state & RW_Mutex_State_Reader_Mask == RW_Mutex_State_Reader) && - (state & RW_Mutex_State_Is_Writing != 0) { - sema_post(&rw.impl.sema) - } - } - - _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_compare_exchange_strong(&rw.impl.state, state, state + RW_Mutex_State_Reader) - if ok { - return true - } - } - if mutex_try_lock(&rw.impl.mutex) { - _ = atomic_add(&rw.impl.state, RW_Mutex_State_Reader) - mutex_unlock(&rw.impl.mutex) - return true - } - - return false - } - -} \ No newline at end of file diff --git a/core/sync/sync2/primitives_linux.odin b/core/sync/sync2/primitives_linux.odin deleted file mode 100644 index 89ed97985..000000000 --- a/core/sync/sync2/primitives_linux.odin +++ /dev/null @@ -1,9 +0,0 @@ -//+build linux -//+private -package sync2 - -import "core:sys/unix" - -_current_thread_id :: proc "contextless" () -> int { - return unix.sys_gettid() -} diff --git a/core/sync/sync2/primitives_openbsd.odin b/core/sync/sync2/primitives_openbsd.odin deleted file mode 100644 index ef122b02e..000000000 --- a/core/sync/sync2/primitives_openbsd.odin +++ /dev/null @@ -1,9 +0,0 @@ -//+build openbsd -//+private -package sync2 - -import "core:os" - -_current_thread_id :: proc "contextless" () -> int { - return os.current_thread_id() -} diff --git a/core/sync/sync2/primitives_pthreads.odin b/core/sync/sync2/primitives_pthreads.odin deleted file mode 100644 index 28053f9cc..000000000 --- a/core/sync/sync2/primitives_pthreads.odin +++ /dev/null @@ -1,58 +0,0 @@ -//+build linux, freebsd, openbsd -//+private -package sync2 - -import "core:time" -import "core:sys/unix" - -_Mutex_State :: enum i32 { - Unlocked = 0, - Locked = 1, - Waiting = 2, -} -_Mutex :: struct { - pthread_mutex: unix.pthread_mutex_t, -} - -_mutex_lock :: proc(m: ^Mutex) { - err := unix.pthread_mutex_lock(&m.impl.pthread_mutex) - assert(err == 0) -} - -_mutex_unlock :: proc(m: ^Mutex) { - err := unix.pthread_mutex_unlock(&m.impl.pthread_mutex) - assert(err == 0) -} - -_mutex_try_lock :: proc(m: ^Mutex) -> bool { - err := unix.pthread_mutex_trylock(&m.impl.pthread_mutex) - return err == 0 -} - -_Cond :: struct { - pthread_cond: unix.pthread_cond_t, -} - -_cond_wait :: proc(c: ^Cond, m: ^Mutex) { - err := unix.pthread_cond_wait(&c.impl.pthread_cond, &m.impl.pthread_mutex) - assert(err == 0) -} - - -_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, duration: time.Duration) -> bool { - tv_sec := i64(duration/1e9) - tv_nsec := i64(duration%1e9) - err := unix.pthread_cond_timedwait(&c.impl.pthread_cond, &m.impl.pthread_mutex, &{tv_sec, tv_nsec}) - return err == 0 -} - - -_cond_signal :: proc(c: ^Cond) { - err := unix.pthread_cond_signal(&c.impl.pthread_cond) - assert(err == 0) -} - -_cond_broadcast :: proc(c: ^Cond) { - err := unix.pthread_cond_broadcast(&c.impl.pthread_cond) - assert(err == 0) -} diff --git a/core/sync/sync_darwin.odin b/core/sync/sync_darwin.odin deleted file mode 100644 index f3bb4d5a3..000000000 --- a/core/sync/sync_darwin.odin +++ /dev/null @@ -1,54 +0,0 @@ -package sync - -import "core:sys/darwin" - -import "core:c" - -foreign import pthread "System.framework" - -current_thread_id :: proc "contextless" () -> int { - tid: u64 - // NOTE(Oskar): available from OSX 10.6 and iOS 3.2. - // For older versions there is `syscall(SYS_thread_selfid)`, but not really - // the same thing apparently. - foreign pthread { pthread_threadid_np :: proc "c" (rawptr, ^u64) -> c.int --- } - pthread_threadid_np(nil, &tid) - return int(tid) -} - - -// The Darwin docs say it best: -// A semaphore is much like a lock, except that a finite number of threads can hold it simultaneously. -// Semaphores can be thought of as being much like piles of tokens; multiple threads can take these tokens, -// but when there are none left, a thread must wait until another thread returns one. -Semaphore :: struct #align 16 { - handle: darwin.semaphore_t, -} -// TODO(tetra): Only marked with alignment because we cannot mark distinct integers with alignments. -// See core/sys/unix/pthread_linux.odin/pthread_t. - -semaphore_init :: proc(s: ^Semaphore, initial_count := 0) { - ct := darwin.mach_task_self() - res := darwin.semaphore_create(ct, &s.handle, 0, c.int(initial_count)) - assert(res == 0) -} - -semaphore_destroy :: proc(s: ^Semaphore) { - ct := darwin.mach_task_self() - res := darwin.semaphore_destroy(ct, s.handle) - assert(res == 0) - s.handle = {} -} - -semaphore_post :: proc(s: ^Semaphore, count := 1) { - // NOTE: SPEED: If there's one syscall to do this, we should use it instead of the loop. - for in 0.. int { - SYS_GETTID :: 186 - return int(intrinsics.syscall(SYS_GETTID)) -} - - -// The Darwin docs say it best: -// A semaphore is much like a lock, except that a finite number of threads can hold it simultaneously. -// Semaphores can be thought of as being much like piles of tokens; multiple threads can take these tokens, -// but when there are none left, a thread must wait until another thread returns one. -Semaphore :: struct #align 16 { - handle: unix.sem_t, -} - -semaphore_init :: proc(s: ^Semaphore, initial_count := 0) { - assert(unix.sem_init(&s.handle, 0, u32(initial_count)) == 0) -} - -semaphore_destroy :: proc(s: ^Semaphore) { - assert(unix.sem_destroy(&s.handle) == 0) - s.handle = {} -} - -semaphore_post :: proc(s: ^Semaphore, count := 1) { - // NOTE: SPEED: If there's one syscall to do this, we should use it instead of the loop. - for in 0.. int { - return unix.sys_gettid() -} - - -// The Darwin docs say it best: -// A semaphore is much like a lock, except that a finite number of threads can hold it simultaneously. -// Semaphores can be thought of as being much like piles of tokens; multiple threads can take these tokens, -// but when there are none left, a thread must wait until another thread returns one. -Semaphore :: struct #align 16 { - handle: unix.sem_t, -} - -semaphore_init :: proc(s: ^Semaphore, initial_count := 0) { - assert(unix.sem_init(&s.handle, 0, u32(initial_count)) == 0) -} - -semaphore_destroy :: proc(s: ^Semaphore) { - assert(unix.sem_destroy(&s.handle) == 0) - s.handle = {} -} - -semaphore_post :: proc(s: ^Semaphore, count := 1) { - // NOTE: SPEED: If there's one syscall to do this, we should use it instead of the loop. - for in 0.. int { - return os.current_thread_id() -} - -// The Darwin docs say it best: -// A semaphore is much like a lock, except that a finite number of threads can hold it simultaneously. -// Semaphores can be thought of as being much like piles of tokens; multiple threads can take these tokens, -// but when there are none left, a thread must wait until another thread returns one. -Semaphore :: struct #align 16 { - handle: unix.sem_t, -} - -semaphore_init :: proc(s: ^Semaphore, initial_count := 0) { - assert(unix.sem_init(&s.handle, 0, u32(initial_count)) == 0) -} - -semaphore_destroy :: proc(s: ^Semaphore) { - assert(unix.sem_destroy(&s.handle) == 0) - s.handle = {} -} - -semaphore_post :: proc(s: ^Semaphore, count := 1) { - // NOTE: SPEED: If there's one syscall to do this, we should use it instead of the loop. - for in 0.. bool { - return unix.pthread_mutex_trylock(&m.handle) == 0 -} - -mutex_unlock :: proc(m: ^Mutex) { - assert(unix.pthread_mutex_unlock(&m.handle) == 0) -} - - -Blocking_Mutex :: struct { - handle: unix.pthread_mutex_t, -} - - -blocking_mutex_init :: proc(m: ^Blocking_Mutex) { - // NOTE(tetra, 2019-11-01): POSIX OOM if we cannot init the attrs or the mutex. - attrs: unix.pthread_mutexattr_t - assert(unix.pthread_mutexattr_init(&attrs) == 0) - defer unix.pthread_mutexattr_destroy(&attrs) // ignores destruction error - - assert(unix.pthread_mutex_init(&m.handle, &attrs) == 0) -} - -blocking_mutex_destroy :: proc(m: ^Blocking_Mutex) { - assert(unix.pthread_mutex_destroy(&m.handle) == 0) - m.handle = {} -} - -blocking_mutex_lock :: proc(m: ^Blocking_Mutex) { - assert(unix.pthread_mutex_lock(&m.handle) == 0) -} - -// Returns false if someone else holds the lock. -blocking_mutex_try_lock :: proc(m: ^Blocking_Mutex) -> bool { - return unix.pthread_mutex_trylock(&m.handle) == 0 -} - -blocking_mutex_unlock :: proc(m: ^Blocking_Mutex) { - assert(unix.pthread_mutex_unlock(&m.handle) == 0) -} - - - -// Blocks until signalled, and then lets past exactly -// one thread. -Condition :: struct { - handle: unix.pthread_cond_t, - mutex: Condition_Mutex_Ptr, - - // NOTE(tetra, 2019-11-11): Used to mimic the more sane behavior of Windows' AutoResetEvent. - // This means that you may signal the condition before anyone is waiting to cause the - // next thread that tries to wait to just pass by uninterrupted, without sleeping. - // Without this, signalling a condition will only wake up a thread which is already waiting, - // but not one that is about to wait, which can cause your program to become out of sync in - // ways that are hard to debug or fix. - flag: bool, // atomically mutated -} - -condition_init :: proc(c: ^Condition, mutex: Condition_Mutex_Ptr) -> bool { - // NOTE(tetra, 2019-11-01): POSIX OOM if we cannot init the attrs or the condition. - attrs: unix.pthread_condattr_t - if unix.pthread_condattr_init(&attrs) != 0 { - return false - } - defer unix.pthread_condattr_destroy(&attrs) // ignores destruction error - - c.flag = false - c.mutex = mutex - return unix.pthread_cond_init(&c.handle, &attrs) == 0 -} - -condition_destroy :: proc(c: ^Condition) { - assert(unix.pthread_cond_destroy(&c.handle) == 0) - c.handle = {} -} - -// Awaken exactly one thread who is waiting on the condition -condition_signal :: proc(c: ^Condition) -> bool { - switch m in c.mutex { - case ^Mutex: - mutex_lock(m) - defer mutex_unlock(m) - atomic_swap(&c.flag, true, .Sequentially_Consistent) - return unix.pthread_cond_signal(&c.handle) == 0 - case ^Blocking_Mutex: - blocking_mutex_lock(m) - defer blocking_mutex_unlock(m) - atomic_swap(&c.flag, true, .Sequentially_Consistent) - return unix.pthread_cond_signal(&c.handle) == 0 - } - return false -} - -// Awaken all threads who are waiting on the condition -condition_broadcast :: proc(c: ^Condition) -> bool { - return unix.pthread_cond_broadcast(&c.handle) == 0 -} - -// Wait for the condition to be signalled. -// Does not block if the condition has been signalled and no one -// has waited on it yet. -condition_wait_for :: proc(c: ^Condition) -> bool { - switch m in c.mutex { - case ^Mutex: - mutex_lock(m) - defer mutex_unlock(m) - // NOTE(tetra): If a thread comes by and steals the flag immediately after the signal occurs, - // the thread that gets signalled and wakes up, discovers that the flag was taken and goes - // back to sleep. - // Though this overall behavior is the most sane, there may be a better way to do this that means that - // the first thread to wait, gets the flag first. - if atomic_swap(&c.flag, false, .Sequentially_Consistent) { - return true - } - for { - if unix.pthread_cond_wait(&c.handle, &m.handle) != 0 { - return false - } - if atomic_swap(&c.flag, false, .Sequentially_Consistent) { - return true - } - } - return false - - case ^Blocking_Mutex: - blocking_mutex_lock(m) - defer blocking_mutex_unlock(m) - // NOTE(tetra): If a thread comes by and steals the flag immediately after the signal occurs, - // the thread that gets signalled and wakes up, discovers that the flag was taken and goes - // back to sleep. - // Though this overall behavior is the most sane, there may be a better way to do this that means that - // the first thread to wait, gets the flag first. - if atomic_swap(&c.flag, false, .Sequentially_Consistent) { - return true - } - for { - if unix.pthread_cond_wait(&c.handle, &m.handle) != 0 { - return false - } - if atomic_swap(&c.flag, false, .Sequentially_Consistent) { - return true - } - } - return false - } - return false -} - -// Wait for the condition to be signalled. -// Does not block if the condition has been signalled and no one -// has waited on it yet. -condition_wait_for_timeout :: proc(c: ^Condition, duration: time.Duration) -> bool { - switch m in c.mutex { - case ^Mutex: - mutex_lock(m) - defer mutex_unlock(m) - // NOTE(tetra): If a thread comes by and steals the flag immediately after the signal occurs, - // the thread that gets signalled and wakes up, discovers that the flag was taken and goes - // back to sleep. - // Though this overall behavior is the most sane, there may be a better way to do this that means that - // the first thread to wait, gets the flag first. - if atomic_swap(&c.flag, false, .Sequentially_Consistent) { - return true - } - - ns := time.duration_nanoseconds(duration) - timeout: time.TimeSpec - timeout.tv_sec = ns / 1e9 - timeout.tv_nsec = ns % 1e9 - - for { - if unix.pthread_cond_timedwait(&c.handle, &m.handle, &timeout) != 0 { - return false - } - if atomic_swap(&c.flag, false, .Sequentially_Consistent) { - return true - } - } - return false - - case ^Blocking_Mutex: - blocking_mutex_lock(m) - defer blocking_mutex_unlock(m) - // NOTE(tetra): If a thread comes by and steals the flag immediately after the signal occurs, - // the thread that gets signalled and wakes up, discovers that the flag was taken and goes - // back to sleep. - // Though this overall behavior is the most sane, there may be a better way to do this that means that - // the first thread to wait, gets the flag first. - if atomic_swap(&c.flag, false, .Sequentially_Consistent) { - return true - } - - ns := time.duration_nanoseconds(duration) - - timeout: time.TimeSpec - timeout.tv_sec = ns / 1e9 - timeout.tv_nsec = ns % 1e9 - - for { - if unix.pthread_cond_timedwait(&c.handle, &m.handle, &timeout) != 0 { - return false - } - if atomic_swap(&c.flag, false, .Sequentially_Consistent) { - return true - } - } - return false - } - return false -} - - - -thread_yield :: proc() { - unix.sched_yield() -} diff --git a/core/sync/sync2/sync_util.odin b/core/sync/sync_util.odin similarity index 99% rename from core/sync/sync2/sync_util.odin rename to core/sync/sync_util.odin index 013bf511a..4add9064d 100644 --- a/core/sync/sync2/sync_util.odin +++ b/core/sync/sync_util.odin @@ -1,4 +1,4 @@ -package sync2 +package sync /* Example: diff --git a/core/sync/sync_windows.odin b/core/sync/sync_windows.odin deleted file mode 100644 index 0a7cf71b2..000000000 --- a/core/sync/sync_windows.odin +++ /dev/null @@ -1,180 +0,0 @@ -// +build windows -package sync - -import win32 "core:sys/windows" -import "core:time" - -current_thread_id :: proc "contextless" () -> int { - return int(win32.GetCurrentThreadId()) -} - - -// When waited upon, blocks until the internal count is greater than zero, then subtracts one. -// Posting to the semaphore increases the count by one, or the provided amount. -Semaphore :: struct { - _handle: win32.HANDLE, -} - -semaphore_init :: proc(s: ^Semaphore, initial_count := 0) { - s._handle = win32.CreateSemaphoreW(nil, i32(initial_count), 1<<31-1, nil) -} - -semaphore_destroy :: proc(s: ^Semaphore) { - win32.CloseHandle(s._handle) -} - -semaphore_post :: proc(s: ^Semaphore, count := 1) { - win32.ReleaseSemaphore(s._handle, i32(count), nil) -} - -semaphore_wait_for :: proc(s: ^Semaphore) { - // NOTE(tetra, 2019-10-30): wait_for_single_object decrements the count before it returns. - result := win32.WaitForSingleObject(s._handle, win32.INFINITE) - assert(result != win32.WAIT_FAILED) -} - - -Mutex :: struct { - _critical_section: win32.CRITICAL_SECTION, -} - - -mutex_init :: proc(m: ^Mutex, spin_count := 0) { - win32.InitializeCriticalSectionAndSpinCount(&m._critical_section, u32(spin_count)) -} - -mutex_destroy :: proc(m: ^Mutex) { - win32.DeleteCriticalSection(&m._critical_section) -} - -mutex_lock :: proc(m: ^Mutex) { - win32.EnterCriticalSection(&m._critical_section) -} - -mutex_try_lock :: proc(m: ^Mutex) -> bool { - return bool(win32.TryEnterCriticalSection(&m._critical_section)) -} - -mutex_unlock :: proc(m: ^Mutex) { - win32.LeaveCriticalSection(&m._critical_section) -} - -Blocking_Mutex :: struct { - _handle: win32.SRWLOCK, -} - - -blocking_mutex_init :: proc(m: ^Blocking_Mutex) { - win32.InitializeSRWLock(&m._handle) -} - -blocking_mutex_destroy :: proc(m: ^Blocking_Mutex) { - // -} - -blocking_mutex_lock :: proc(m: ^Blocking_Mutex) { - win32.AcquireSRWLockExclusive(&m._handle) -} - -blocking_mutex_try_lock :: proc(m: ^Blocking_Mutex) -> bool { - return bool(win32.TryAcquireSRWLockExclusive(&m._handle)) -} - -blocking_mutex_unlock :: proc(m: ^Blocking_Mutex) { - win32.ReleaseSRWLockExclusive(&m._handle) -} - - -// Blocks until signalled. -// When signalled, awakens exactly one waiting thread. -Condition :: struct { - _handle: win32.CONDITION_VARIABLE, - - mutex: Condition_Mutex_Ptr, -} - - -condition_init :: proc(c: ^Condition, mutex: Condition_Mutex_Ptr) -> bool { - assert(mutex != nil) - win32.InitializeConditionVariable(&c._handle) - c.mutex = mutex - return true -} - -condition_destroy :: proc(c: ^Condition) { - // -} - -condition_signal :: proc(c: ^Condition) -> bool { - if c._handle.ptr == nil { - return false - } - win32.WakeConditionVariable(&c._handle) - return true -} - -condition_broadcast :: proc(c: ^Condition) -> bool { - if c._handle.ptr == nil { - return false - } - win32.WakeAllConditionVariable(&c._handle) - return true -} - -condition_wait_for :: proc(c: ^Condition) -> bool { - switch m in &c.mutex { - case ^Mutex: - return cast(bool)win32.SleepConditionVariableCS(&c._handle, &m._critical_section, win32.INFINITE) - case ^Blocking_Mutex: - return cast(bool)win32.SleepConditionVariableSRW(&c._handle, &m._handle, win32.INFINITE, 0) - } - return false -} -condition_wait_for_timeout :: proc(c: ^Condition, duration: time.Duration) -> bool { - ms := win32.DWORD((max(time.duration_nanoseconds(duration), 0) + 999999)/1000000) - switch m in &c.mutex { - case ^Mutex: - return cast(bool)win32.SleepConditionVariableCS(&c._handle, &m._critical_section, ms) - case ^Blocking_Mutex: - return cast(bool)win32.SleepConditionVariableSRW(&c._handle, &m._handle, ms, 0) - } - return false -} - - - - -RW_Lock :: struct { - _handle: win32.SRWLOCK, -} - -rw_lock_init :: proc(l: ^RW_Lock) { - l._handle = win32.SRWLOCK_INIT -} -rw_lock_destroy :: proc(l: ^RW_Lock) { - // -} -rw_lock_read :: proc(l: ^RW_Lock) { - win32.AcquireSRWLockShared(&l._handle) -} -rw_lock_try_read :: proc(l: ^RW_Lock) -> bool { - return bool(win32.TryAcquireSRWLockShared(&l._handle)) -} -rw_lock_write :: proc(l: ^RW_Lock) { - win32.AcquireSRWLockExclusive(&l._handle) -} -rw_lock_try_write :: proc(l: ^RW_Lock) -> bool { - return bool(win32.TryAcquireSRWLockExclusive(&l._handle)) -} -rw_lock_read_unlock :: proc(l: ^RW_Lock) { - win32.ReleaseSRWLockShared(&l._handle) -} -rw_lock_write_unlock :: proc(l: ^RW_Lock) { - win32.ReleaseSRWLockExclusive(&l._handle) -} - - -thread_yield :: proc() { - win32.SwitchToThread() -} - diff --git a/core/sync/wait_group.odin b/core/sync/wait_group.odin deleted file mode 100644 index 63d882ed1..000000000 --- a/core/sync/wait_group.odin +++ /dev/null @@ -1,58 +0,0 @@ -package sync - -import "core:intrinsics" - -Wait_Group :: struct { - counter: int, - mutex: Blocking_Mutex, - cond: Condition, -} - -wait_group_init :: proc(wg: ^Wait_Group) { - wg.counter = 0 - blocking_mutex_init(&wg.mutex) - condition_init(&wg.cond, &wg.mutex) -} - - -wait_group_destroy :: proc(wg: ^Wait_Group) { - condition_destroy(&wg.cond) - blocking_mutex_destroy(&wg.mutex) -} - -wait_group_add :: proc(wg: ^Wait_Group, delta: int) { - if delta == 0 { - return - } - - blocking_mutex_lock(&wg.mutex) - defer blocking_mutex_unlock(&wg.mutex) - - intrinsics.atomic_add(&wg.counter, delta) - if wg.counter < 0 { - panic("sync.Wait_Group negative counter") - } - if wg.counter == 0 { - condition_broadcast(&wg.cond) - if wg.counter != 0 { - panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") - } - } -} - -wait_group_done :: proc(wg: ^Wait_Group) { - wait_group_add(wg, -1) -} - -wait_group_wait :: proc(wg: ^Wait_Group) { - blocking_mutex_lock(&wg.mutex) - defer blocking_mutex_unlock(&wg.mutex) - - if wg.counter != 0 { - condition_wait_for(&wg.cond) - if wg.counter != 0 { - panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") - } - } -} - diff --git a/core/sys/win32/user32.odin b/core/sys/win32/user32.odin index fdf6a91f6..44d1f7004 100644 --- a/core/sys/win32/user32.odin +++ b/core/sys/win32/user32.odin @@ -111,6 +111,8 @@ foreign user32 { @(link_name="SetWindowTextW") set_window_text_w :: proc(hwnd: Hwnd, c_string: Wstring) -> Bool --- @(link_name="RegisterClassA") register_class_a :: proc(wc: ^Wnd_Class_A) -> i16 --- @(link_name="RegisterClassW") register_class_w :: proc(wc: ^Wnd_Class_W) -> i16 --- + @(link_name="UnregisterClassA") unregister_class_a :: proc(class_name: cstring, instance: Hinstance) -> Bool --- + @(link_name="UnregisterClassW") unregister_class_w :: proc(class_name: Wstring, instance: Hinstance) -> Bool --- @(link_name="RegisterClassExA") register_class_ex_a :: proc(wc: ^Wnd_Class_Ex_A) -> i16 --- @(link_name="RegisterClassExW") register_class_ex_w :: proc(wc: ^Wnd_Class_Ex_W) -> i16 --- diff --git a/core/sys/windows/gdi32.odin b/core/sys/windows/gdi32.odin new file mode 100644 index 000000000..15d5567c7 --- /dev/null +++ b/core/sys/windows/gdi32.odin @@ -0,0 +1,66 @@ +// +build windows +package sys_windows + +foreign import gdi32 "system:Gdi32.lib" + +@(default_calling_convention="stdcall") +foreign gdi32 { + GetStockObject :: proc(i: c_int) -> HGDIOBJ --- + SelectObject :: proc(hdc: HDC, h: HGDIOBJ) -> HGDIOBJ --- + + CreateDIBPatternBrush :: proc(h: HGLOBAL, iUsage: UINT) -> HBRUSH --- + + CreateDIBitmap :: proc( + hdc: HDC, + pbmih: ^BITMAPINFOHEADER, + flInit: DWORD, + pjBits: VOID, + pbmi: ^BITMAPINFO, + iUsage: UINT, + ) -> HBITMAP --- + + CreateDIBSection :: proc( + hdc: HDC, + pbmi: ^BITMAPINFO, + usage: UINT, + ppvBits: VOID, + hSection: HANDLE, + offset: DWORD, + ) -> HBITMAP --- + + StretchDIBits :: proc( + hdc: HDC, + xDest: c_int, + yDest: c_int, + DestWidth: c_int, + DestHeight: c_int, + xSrc: c_int, + ySrc: c_int, + SrcWidth: c_int, + SrcHeight: c_int, + lpBits: VOID, + lpbmi: ^BITMAPINFO, + iUsage: UINT, + rop: DWORD, + ) -> c_int --- + + StretchBlt :: proc( + hdcDest: HDC, + xDest: c_int, + yDest: c_int, + wDest: c_int, + hDest: c_int, + hdcSrc: HDC, + xSrc: c_int, + ySrc: c_int, + wSrc: c_int, + hSrc: c_int, + rop: DWORD, + ) -> BOOL --- + + SetPixelFormat :: proc(hdc: HDC, format: c_int, ppfd: ^PIXELFORMATDESCRIPTOR) -> BOOL --- + ChoosePixelFormat :: proc(hdc: HDC, ppfd: ^PIXELFORMATDESCRIPTOR) -> c_int --- + SwapBuffers :: proc(HDC) -> BOOL --- + + PatBlt :: proc(hdc: HDC, x, y, w, h: c_int, rop: DWORD) -> BOOL --- +} diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 8c58fbd52..f81c51311 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -341,6 +341,7 @@ MEM_TOP_DOWN :: 0x100000 MEM_LARGE_PAGES :: 0x20000000 MEM_4MB_PAGES :: 0x80000000 +@(default_calling_convention="stdcall") foreign kernel32 { VirtualAlloc :: proc( lpAddress: LPVOID, @@ -483,6 +484,7 @@ LowMemoryResourceNotification :: MEMORY_RESOURCE_NOTIFICATION_TYPE.LowMemoryRes HighMemoryResourceNotification :: MEMORY_RESOURCE_NOTIFICATION_TYPE.HighMemoryResourceNotification +@(default_calling_convention="stdcall") foreign kernel32 { CreateMemoryResourceNotification :: proc( NotificationType: MEMORY_RESOURCE_NOTIFICATION_TYPE, @@ -498,6 +500,7 @@ FILE_CACHE_MAX_HARD_DISABLE :: DWORD(0x00000002) FILE_CACHE_MIN_HARD_ENABLE :: DWORD(0x00000004) FILE_CACHE_MIN_HARD_DISABLE :: DWORD(0x00000008) +@(default_calling_convention="stdcall") foreign kernel32 { GetSystemFileCacheSize :: proc( lpMinimumFileCacheSize: PSIZE_T, @@ -527,6 +530,7 @@ WIN32_MEMORY_RANGE_ENTRY :: struct { PWIN32_MEMORY_RANGE_ENTRY :: ^WIN32_MEMORY_RANGE_ENTRY +@(default_calling_convention="stdcall") foreign kernel32 { PrefetchVirtualMemory :: proc( hProcess: HANDLE, @@ -584,6 +588,7 @@ foreign kernel32 { MEHC_PATROL_SCRUBBER_PRESENT :: ULONG(0x1) +@(default_calling_convention="stdcall") foreign kernel32 { GetMemoryErrorHandlingCapabilities :: proc( Capabilities: PULONG, @@ -592,6 +597,7 @@ foreign kernel32 { PBAD_MEMORY_CALLBACK_ROUTINE :: #type proc "stdcall" () +@(default_calling_convention="stdcall") foreign kernel32 { RegisterBadMemoryNotification :: proc( Callback: PBAD_MEMORY_CALLBACK_ROUTINE, @@ -612,6 +618,7 @@ VmOfferPriorityLow :: OFFER_PRIORITY.VmOfferPriorityLow VmOfferPriorityBelowNormal :: OFFER_PRIORITY.VmOfferPriorityBelowNormal VmOfferPriorityNormal :: OFFER_PRIORITY.VmOfferPriorityNormal +@(default_calling_convention="stdcall") foreign kernel32 { OfferVirtualMemory :: proc( VirtualAddress: PVOID, @@ -676,6 +683,7 @@ WIN32_MEMORY_REGION_INFORMATION_u_s_Bitfield :: distinct ULONG Reserved : 32-6, }*/ +@(default_calling_convention="stdcall") foreign kernel32 { QueryVirtualMemoryInformation :: proc( Process: HANDLE, @@ -700,7 +708,7 @@ foreign kernel32 { NUMA_NO_PREFERRED_NODE :: 0xffffffff -MapViewOfFile2 :: #force_inline proc( +MapViewOfFile2 :: #force_inline proc "stdcall" ( FileMappingHandle: HANDLE, ProcessHandle: HANDLE, Offset: ULONG64, @@ -721,6 +729,7 @@ MapViewOfFile2 :: #force_inline proc( ) } +@(default_calling_convention="stdcall") foreign kernel32 { UnmapViewOfFile2 :: proc( ProcessHandle: HANDLE, diff --git a/core/sys/windows/key_codes.odin b/core/sys/windows/key_codes.odin new file mode 100644 index 000000000..2f6e01116 --- /dev/null +++ b/core/sys/windows/key_codes.odin @@ -0,0 +1,252 @@ +// +build windows +package sys_windows + +// https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes +// Virtual Keys, Standard Set +VK_LBUTTON :: 0x01 +VK_RBUTTON :: 0x02 +VK_CANCEL :: 0x03 +VK_MBUTTON :: 0x04 // NOT contiguous with L & RBUTTON +VK_XBUTTON1 :: 0x05 // NOT contiguous with L & RBUTTON +VK_XBUTTON2 :: 0x06 // NOT contiguous with L & RBUTTON + +// 0x07 : reserved + +VK_BACK :: 0x08 +VK_TAB :: 0x09 + +// 0x0A - 0x0B : reserved + +VK_CLEAR :: 0x0C +VK_RETURN :: 0x0D + +// 0x0E - 0x0F : unassigned + +VK_SHIFT :: 0x10 +VK_CONTROL :: 0x11 +VK_MENU :: 0x12 +VK_PAUSE :: 0x13 +VK_CAPITAL :: 0x14 +VK_KANA :: 0x15 +VK_HANGEUL :: 0x15 // old name - should be here for compatibility +VK_HANGUL :: 0x15 +VK_IME_ON :: 0x16 +VK_JUNJA :: 0x17 +VK_FINAL :: 0x18 +VK_HANJA :: 0x19 +VK_KANJI :: 0x19 +VK_IME_OFF :: 0x1A +VK_ESCAPE :: 0x1B +VK_CONVERT :: 0x1C +VK_NONCONVERT :: 0x1D +VK_ACCEPT :: 0x1E +VK_MODECHANGE :: 0x1F +VK_SPACE :: 0x20 +VK_PRIOR :: 0x21 +VK_NEXT :: 0x22 +VK_END :: 0x23 +VK_HOME :: 0x24 +VK_LEFT :: 0x25 +VK_UP :: 0x26 +VK_RIGHT :: 0x27 +VK_DOWN :: 0x28 +VK_SELECT :: 0x29 +VK_PRINT :: 0x2A +VK_EXECUTE :: 0x2B +VK_SNAPSHOT :: 0x2C +VK_INSERT :: 0x2D +VK_DELETE :: 0x2E +VK_HELP :: 0x2F + +VK_0 :: '0' +VK_1 :: '1' +VK_2 :: '2' +VK_3 :: '3' +VK_4 :: '4' +VK_5 :: '5' +VK_6 :: '6' +VK_7 :: '7' +VK_8 :: '8' +VK_9 :: '9' + +// 0x3A - 0x40 : unassigned + +VK_A :: 'A' +VK_B :: 'B' +VK_C :: 'C' +VK_D :: 'D' +VK_E :: 'E' +VK_F :: 'F' +VK_G :: 'G' +VK_H :: 'H' +VK_I :: 'I' +VK_J :: 'J' +VK_K :: 'K' +VK_L :: 'L' +VK_M :: 'M' +VK_N :: 'N' +VK_O :: 'O' +VK_P :: 'P' +VK_Q :: 'Q' +VK_R :: 'R' +VK_S :: 'S' +VK_T :: 'T' +VK_U :: 'U' +VK_V :: 'V' +VK_W :: 'W' +VK_X :: 'X' +VK_Y :: 'Y' +VK_Z :: 'Z' + +VK_LWIN :: 0x5B +VK_RWIN :: 0x5C +VK_APPS :: 0x5D + +// 0x5E : reserved + +VK_SLEEP :: 0x5F +VK_NUMPAD0 :: 0x60 +VK_NUMPAD1 :: 0x61 +VK_NUMPAD2 :: 0x62 +VK_NUMPAD3 :: 0x63 +VK_NUMPAD4 :: 0x64 +VK_NUMPAD5 :: 0x65 +VK_NUMPAD6 :: 0x66 +VK_NUMPAD7 :: 0x67 +VK_NUMPAD8 :: 0x68 +VK_NUMPAD9 :: 0x69 +VK_MULTIPLY :: 0x6A +VK_ADD :: 0x6B +VK_SEPARATOR :: 0x6C +VK_SUBTRACT :: 0x6D +VK_DECIMAL :: 0x6E +VK_DIVIDE :: 0x6F +VK_F1 :: 0x70 +VK_F2 :: 0x71 +VK_F3 :: 0x72 +VK_F4 :: 0x73 +VK_F5 :: 0x74 +VK_F6 :: 0x75 +VK_F7 :: 0x76 +VK_F8 :: 0x77 +VK_F9 :: 0x78 +VK_F10 :: 0x79 +VK_F11 :: 0x7A +VK_F12 :: 0x7B +VK_F13 :: 0x7C +VK_F14 :: 0x7D +VK_F15 :: 0x7E +VK_F16 :: 0x7F +VK_F17 :: 0x80 +VK_F18 :: 0x81 +VK_F19 :: 0x82 +VK_F20 :: 0x83 +VK_F21 :: 0x84 +VK_F22 :: 0x85 +VK_F23 :: 0x86 +VK_F24 :: 0x87 + +// 0x88 - 0x8F : reserved + +VK_NUMLOCK :: 0x90 +VK_SCROLL :: 0x91 + +// NEC PC-9800 kbd definitions +VK_OEM_NEC_EQUAL :: 0x92 // '=' key on numpad + +// Fujitsu/OASYS kbd definitions +VK_OEM_FJ_JISHO :: 0x92 // 'Dictionary' key +VK_OEM_FJ_MASSHOU :: 0x93 // 'Unregister word' key +VK_OEM_FJ_TOUROKU :: 0x94 // 'Register word' key +VK_OEM_FJ_LOYA :: 0x95 // 'Left OYAYUBI' key +VK_OEM_FJ_ROYA :: 0x96 // 'Right OYAYUBI' key + +// 0x97 - 0x9F : unassigned + +// VK_L* & VK_R* - left and right Alt, Ctrl and Shift virtual keys. +// Used only as parameters to GetAsyncKeyState() and GetKeyState(). +// No other API or message will distinguish left and right keys in this way. +VK_LSHIFT :: 0xA0 +VK_RSHIFT :: 0xA1 +VK_LCONTROL :: 0xA2 +VK_RCONTROL :: 0xA3 +VK_LMENU :: 0xA4 +VK_RMENU :: 0xA5 + +VK_BROWSER_BACK :: 0xA6 +VK_BROWSER_FORWARD :: 0xA7 +VK_BROWSER_REFRESH :: 0xA8 +VK_BROWSER_STOP :: 0xA9 +VK_BROWSER_SEARCH :: 0xAA +VK_BROWSER_FAVORITES :: 0xAB +VK_BROWSER_HOME :: 0xAC +VK_VOLUME_MUTE :: 0xAD +VK_VOLUME_DOWN :: 0xAE +VK_VOLUME_UP :: 0xAF +VK_MEDIA_NEXT_TRACK :: 0xB0 +VK_MEDIA_PREV_TRACK :: 0xB1 +VK_MEDIA_STOP :: 0xB2 +VK_MEDIA_PLAY_PAUSE :: 0xB3 +VK_LAUNCH_MAIL :: 0xB4 +VK_LAUNCH_MEDIA_SELECT :: 0xB5 +VK_LAUNCH_APP1 :: 0xB6 +VK_LAUNCH_APP2 :: 0xB7 + +// 0xB8 - 0xB9 : reserved + +VK_OEM_1 :: 0xBA // ';:' for US +VK_OEM_PLUS :: 0xBB // '+' any country +VK_OEM_COMMA :: 0xBC // ',' any country +VK_OEM_MINUS :: 0xBD // '-' any country +VK_OEM_PERIOD :: 0xBE // '.' any country +VK_OEM_2 :: 0xBF // '/?' for US +VK_OEM_3 :: 0xC0 // '`~' for US + +// 0xC1 - 0xDA : reserved + +VK_OEM_4 :: 0xDB // '[{' for US +VK_OEM_5 :: 0xDC // '\|' for US +VK_OEM_6 :: 0xDD // ']}' for US +VK_OEM_7 :: 0xDE // ''"' for US +VK_OEM_8 :: 0xDF + +// 0xE0 : reserved + +// Various extended or enhanced keyboards +VK_OEM_AX :: 0xE1 // 'AX' key on Japanese AX kbd +VK_OEM_102 :: 0xE2 // "<>" or "\|" on RT 102-key kbd. +VK_ICO_HELP :: 0xE3 // Help key on ICO +VK_ICO_00 :: 0xE4 // 00 key on ICO + +VK_PROCESSKEY :: 0xE5 +VK_ICO_CLEAR :: 0xE6 +VK_PACKET :: 0xE7 + +// 0xE8 : unassigned + +// Nokia/Ericsson definitions +VK_OEM_RESET :: 0xE9 +VK_OEM_JUMP :: 0xEA +VK_OEM_PA1 :: 0xEB +VK_OEM_PA2 :: 0xEC +VK_OEM_PA3 :: 0xED +VK_OEM_WSCTRL :: 0xEE +VK_OEM_CUSEL :: 0xEF +VK_OEM_ATTN :: 0xF0 +VK_OEM_FINISH :: 0xF1 +VK_OEM_COPY :: 0xF2 +VK_OEM_AUTO :: 0xF3 +VK_OEM_ENLW :: 0xF4 +VK_OEM_BACKTAB :: 0xF5 + +VK_ATTN :: 0xF6 +VK_CRSEL :: 0xF7 +VK_EXSEL :: 0xF8 +VK_EREOF :: 0xF9 +VK_PLAY :: 0xFA +VK_ZOOM :: 0xFB +VK_NONAME :: 0xFC +VK_PA1 :: 0xFD +VK_OEM_CLEAR :: 0xFE + +// 0xFF : reserved diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index 1407201d6..50098a59c 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -21,7 +21,15 @@ HINSTANCE :: HANDLE HMODULE :: distinct HINSTANCE HRESULT :: distinct LONG HWND :: distinct HANDLE +HDC :: distinct HANDLE HMONITOR :: distinct HANDLE +HICON :: distinct HANDLE +HCURSOR :: distinct HANDLE +HMENU :: distinct HANDLE +HBRUSH :: distinct HANDLE +HGDIOBJ :: distinct HANDLE +HBITMAP :: distinct HANDLE +HGLOBAL :: distinct HANDLE BOOL :: distinct b32 BYTE :: distinct u8 BOOLEAN :: distinct b8 @@ -42,9 +50,15 @@ PULONG_PTR :: ^ULONG_PTR LPULONG_PTR :: ^ULONG_PTR DWORD_PTR :: ULONG_PTR LONG_PTR :: int +UINT_PTR :: uintptr ULONG :: c_ulong UCHAR :: BYTE NTSTATUS :: c.long +LPARAM :: LONG_PTR +WPARAM :: UINT_PTR +LRESULT :: LONG_PTR +LPRECT :: ^RECT +LPPOINT :: ^POINT UINT8 :: u8 UINT16 :: u16 @@ -71,6 +85,7 @@ PBOOL :: ^BOOL LPBOOL :: ^BOOL LPCSTR :: cstring LPCWSTR :: wstring +LPCTSTR :: wstring LPDWORD :: ^DWORD PCSTR :: cstring PCWSTR :: wstring @@ -81,7 +96,8 @@ LPPROCESS_INFORMATION :: ^PROCESS_INFORMATION PSECURITY_ATTRIBUTES :: ^SECURITY_ATTRIBUTES LPSECURITY_ATTRIBUTES :: ^SECURITY_ATTRIBUTES LPSTARTUPINFO :: ^STARTUPINFO -PVOID :: rawptr +VOID :: rawptr +PVOID :: rawptr LPVOID :: rawptr PINT :: ^INT LPINT :: ^INT @@ -178,6 +194,84 @@ GetFileExInfoStandard: GET_FILEEX_INFO_LEVELS : 0 GetFileExMaxInfoLevel: GET_FILEEX_INFO_LEVELS : 1 +TIMERPROC :: #type proc "stdcall" (HWND, UINT, UINT_PTR, DWORD) + +WNDPROC :: #type proc "stdcall" (HWND, UINT, WPARAM, LPARAM) -> LRESULT + +WNDCLASSA :: struct { + style: UINT, + lpfnWndProc: WNDPROC, + cbClsExtra: c_int, + cbWndExtra: c_int, + hInstance: HINSTANCE, + hIcon: HICON, + hCursor: HCURSOR, + hbrBackground: HBRUSH, + lpszMenuName: LPCSTR, + lpszClassName: LPCSTR, +} + +WNDCLASSW :: struct { + style: UINT, + lpfnWndProc: WNDPROC, + cbClsExtra: c_int, + cbWndExtra: c_int, + hInstance: HINSTANCE, + hIcon: HICON, + hCursor: HCURSOR, + hbrBackground: HBRUSH, + lpszMenuName: LPCWSTR, + lpszClassName: LPCWSTR, +} + +WNDCLASSEXA :: struct { + cbSize: UINT, + style: UINT, + lpfnWndProc: WNDPROC, + cbClsExtra: c_int, + cbWndExtra: c_int, + hInstance: HINSTANCE, + hIcon: HICON, + hCursor: HCURSOR, + hbrBackground: HBRUSH, + lpszMenuName: LPCSTR, + lpszClassName: LPCSTR, + hIconSm: HICON, +} + +WNDCLASSEXW :: struct { + cbSize: UINT, + style: UINT, + lpfnWndProc: WNDPROC, + cbClsExtra: c_int, + cbWndExtra: c_int, + hInstance: HINSTANCE, + hIcon: HICON, + hCursor: HCURSOR, + hbrBackground: HBRUSH, + lpszMenuName: LPCWSTR, + lpszClassName: LPCWSTR, + hIconSm: HICON, +} + +MSG :: struct { + hwnd: HWND, + message: UINT, + wParam: WPARAM, + lParam: LPARAM, + time: DWORD, + pt: POINT, +} + +PAINTSTRUCT :: struct { + hdc: HDC, + fErase: BOOL, + rcPaint: RECT, + fRestore: BOOL, + fIncUpdate: BOOL, + rgbReserved: [32]BYTE, +} + WIN32_FIND_DATAW :: struct { dwFileAttributes: DWORD, ftCreationTime: FILETIME, @@ -191,6 +285,602 @@ WIN32_FIND_DATAW :: struct { cAlternateFileName: [14]wchar_t, } +CREATESTRUCTA :: struct { + lpCreateParams: LPVOID, + hInstance: HINSTANCE, + hMenu: HMENU, + hwndParent: HWND, + cy: c_int, + cx: c_int, + y: c_int, + x: c_int, + style: LONG, + lpszName: LPCSTR, + lpszClass: LPCSTR, + dwExStyle: DWORD, +} + +CREATESTRUCTW:: struct { + lpCreateParams: LPVOID, + hInstance: HINSTANCE, + hMenu: HMENU, + hwndParent: HWND, + cy: c_int, + cx: c_int, + y: c_int, + x: c_int, + style: LONG, + lpszName: LPCWSTR, + lpszClass: LPCWSTR, + dwExStyle: DWORD, +} + +// MessageBox() Flags +MB_OK :: 0x00000000 +MB_OKCANCEL :: 0x00000001 +MB_ABORTRETRYIGNORE :: 0x00000002 +MB_YESNOCANCEL :: 0x00000003 +MB_YESNO :: 0x00000004 +MB_RETRYCANCEL :: 0x00000005 +MB_CANCELTRYCONTINUE :: 0x00000006 + +MB_ICONHAND :: 0x00000010 +MB_ICONQUESTION :: 0x00000020 +MB_ICONEXCLAMATION :: 0x00000030 +MB_ICONASTERISK :: 0x00000040 +MB_USERICON :: 0x00000080 +MB_ICONWARNING :: MB_ICONEXCLAMATION +MB_ICONERROR :: MB_ICONHAND +MB_ICONINFORMATION :: MB_ICONASTERISK +MB_ICONSTOP :: MB_ICONHAND + +MB_DEFBUTTON1 :: 0x00000000 +MB_DEFBUTTON2 :: 0x00000100 +MB_DEFBUTTON3 :: 0x00000200 +MB_DEFBUTTON4 :: 0x00000300 + +MB_APPLMODAL :: 0x00000000 +MB_SYSTEMMODAL :: 0x00001000 +MB_TASKMODAL :: 0x00002000 +MB_HELP :: 0x00004000 // Help Button + +MB_NOFOCUS :: 0x00008000 +MB_SETFOREGROUND :: 0x00010000 +MB_DEFAULT_DESKTOP_ONLY :: 0x00020000 +MB_TOPMOST :: 0x00040000 +MB_RIGHT :: 0x00080000 +MB_RTLREADING :: 0x00100000 + +MB_SERVICE_NOTIFICATION :: 0x00200000 +MB_SERVICE_NOTIFICATION_NT3X :: 0x00040000 + +MB_TYPEMASK :: 0x0000000F +MB_ICONMASK :: 0x000000F0 +MB_DEFMASK :: 0x00000F00 +MB_MODEMASK :: 0x00003000 +MB_MISCMASK :: 0x0000C000 + +// Dialog Box Command IDs +IDOK :: 1 +IDCANCEL :: 2 +IDABORT :: 3 +IDRETRY :: 4 +IDIGNORE :: 5 +IDYES :: 6 +IDNO :: 7 +IDCLOSE :: 8 +IDHELP :: 9 +IDTRYAGAIN :: 10 +IDCONTINUE :: 11 +IDTIMEOUT :: 32000 + +CS_VREDRAW : UINT : 0x0001 +CS_HREDRAW : UINT : 0x0002 +CS_DBLCLKS : UINT : 0x0008 +CS_OWNDC : UINT : 0x0020 +CS_CLASSDC : UINT : 0x0040 +CS_PARENTDC : UINT : 0x0080 +CS_NOCLOSE : UINT : 0x0200 +CS_SAVEBITS : UINT : 0x0800 +CS_BYTEALIGNCLIENT : UINT : 0x1000 +CS_BYTEALIGNWINDOW : UINT : 0x2000 +CS_GLOBALCLASS : UINT : 0x4000 +CS_DROPSHADOW : UINT : 0x0002_0000 + +GWL_EXSTYLE : c_int : -20 +GWLP_HINSTANCE : c_int : -6 +GWLP_ID : c_int : -12 +GWL_STYLE : c_int : -16 +GWLP_USERDATA : c_int : -21 +GWLP_WNDPROC : c_int : -4 + +WS_BORDER : UINT : 0x0080_0000 +WS_CAPTION : UINT : 0x00C0_0000 +WS_CHILD : UINT : 0x4000_0000 +WS_CHILDWINDOW : UINT : WS_CHILD +WS_CLIPCHILDREN : UINT : 0x0200_0000 +WS_CLIPSIBLINGS : UINT : 0x0400_0000 +WS_DISABLED : UINT : 0x0800_0000 +WS_DLGFRAME : UINT : 0x0040_0000 +WS_GROUP : UINT : 0x0002_0000 +WS_HSCROLL : UINT : 0x0010_0000 +WS_ICONIC : UINT : 0x2000_0000 +WS_MAXIMIZE : UINT : 0x0100_0000 +WS_MAXIMIZEBOX : UINT : 0x0001_0000 +WS_MINIMIZE : UINT : 0x2000_0000 +WS_MINIMIZEBOX : UINT : 0x0002_0000 +WS_OVERLAPPED : UINT : 0x0000_0000 +WS_OVERLAPPEDWINDOW : UINT : WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX +WS_POPUP : UINT : 0x8000_0000 +WS_POPUPWINDOW : UINT : WS_POPUP | WS_BORDER | WS_SYSMENU +WS_SIZEBOX : UINT : 0x0004_0000 +WS_SYSMENU : UINT : 0x0008_0000 +WS_TABSTOP : UINT : 0x0001_0000 +WS_THICKFRAME : UINT : 0x0004_0000 +WS_TILED : UINT : 0x0000_0000 +WS_TILEDWINDOW : UINT : WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZE | WS_MAXIMIZE +WS_VISIBLE : UINT : 0x1000_0000 +WS_VSCROLL : UINT : 0x0020_0000 + +QS_ALLEVENTS : UINT : QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY +QS_ALLINPUT : UINT : QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY | QS_SENDMESSAGE +QS_ALLPOSTMESSAGE : UINT : 0x0100 +QS_HOTKEY : UINT : 0x0080 +QS_INPUT : UINT : QS_MOUSE | QS_KEY | QS_RAWINPUT +QS_KEY : UINT : 0x0001 +QS_MOUSE : UINT : QS_MOUSEMOVE | QS_MOUSEBUTTON +QS_MOUSEBUTTON : UINT : 0x0004 +QS_MOUSEMOVE : UINT : 0x0002 +QS_PAINT : UINT : 0x0020 +QS_POSTMESSAGE : UINT : 0x0008 +QS_RAWINPUT : UINT : 0x0400 +QS_SENDMESSAGE : UINT : 0x0040 +QS_TIMER : UINT : 0x0010 + +PM_NOREMOVE : UINT : 0x0000 +PM_REMOVE : UINT : 0x0001 +PM_NOYIELD : UINT : 0x0002 + +PM_QS_INPUT : UINT : QS_INPUT << 16 +PM_QS_PAINT : UINT : QS_PAINT << 16 +PM_QS_POSTMESSAGE : UINT : (QS_POSTMESSAGE | QS_HOTKEY | QS_TIMER) << 16 +PM_QS_SENDMESSAGE : UINT : QS_SENDMESSAGE << 16 + +SW_HIDE : c_int : 0 +SW_SHOWNORMAL : c_int : SW_NORMAL +SW_NORMAL : c_int : 1 +SW_SHOWMINIMIZED : c_int : 2 +SW_SHOWMAXIMIZED : c_int : SW_MAXIMIZE +SW_MAXIMIZE : c_int : 3 +SW_SHOWNOACTIVATE : c_int : 4 +SW_SHOW : c_int : 5 +SW_MINIMIZE : c_int : 6 +SW_SHOWMINNOACTIVE : c_int : 7 +SW_SHOWNA : c_int : 8 +SW_RESTORE : c_int : 9 +SW_SHOWDEFAULT : c_int : 10 +SW_FORCEMINIMIZE : c_int : 11 + +// SetWindowPos Flags +SWP_NOSIZE :: 0x0001 +SWP_NOMOVE :: 0x0002 +SWP_NOZORDER :: 0x0004 +SWP_NOREDRAW :: 0x0008 +SWP_NOACTIVATE :: 0x0010 +SWP_FRAMECHANGED :: 0x0020 // The frame changed: send WM_NCCALCSIZE +SWP_SHOWWINDOW :: 0x0040 +SWP_HIDEWINDOW :: 0x0080 +SWP_NOCOPYBITS :: 0x0100 +SWP_NOOWNERZORDER :: 0x0200 // Don't do owner Z ordering +SWP_NOSENDCHANGING :: 0x0400 // Don't send WM_WINDOWPOSCHANGING + +SWP_DRAWFRAME :: SWP_FRAMECHANGED +SWP_NOREPOSITION :: SWP_NOOWNERZORDER + +SWP_DEFERERASE :: 0x2000 // same as SWP_DEFERDRAWING +SWP_ASYNCWINDOWPOS :: 0x4000 // same as SWP_CREATESPB + +HWND_TOP :: HWND( uintptr(0)) // 0 +HWND_BOTTOM :: HWND( uintptr(1)) // 1 +HWND_TOPMOST :: HWND(~uintptr(0)) // -1 +HWND_NOTOPMOST :: HWND(~uintptr(0) - 1) // -2 + +// GetSystemMetrics() codes +SM_CXSCREEN :: 0 +SM_CYSCREEN :: 1 +SM_CXVSCROLL :: 2 +SM_CYHSCROLL :: 3 +SM_CYCAPTION :: 4 +SM_CXBORDER :: 5 +SM_CYBORDER :: 6 +SM_CXDLGFRAME :: 7 +SM_CYDLGFRAME :: 8 +SM_CYVTHUMB :: 9 +SM_CXHTHUMB :: 10 +SM_CXICON :: 11 +SM_CYICON :: 12 +SM_CXCURSOR :: 13 +SM_CYCURSOR :: 14 +SM_CYMENU :: 15 +SM_CXFULLSCREEN :: 16 +SM_CYFULLSCREEN :: 17 +SM_CYKANJIWINDOW :: 18 +SM_MOUSEPRESENT :: 19 +SM_CYVSCROLL :: 20 +SM_CXHSCROLL :: 21 +SM_DEBUG :: 22 +SM_SWAPBUTTON :: 23 +SM_RESERVED1 :: 24 +SM_RESERVED2 :: 25 +SM_RESERVED3 :: 26 +SM_RESERVED4 :: 27 +SM_CXMIN :: 28 +SM_CYMIN :: 29 +SM_CXSIZE :: 30 +SM_CYSIZE :: 31 +SM_CXFRAME :: 32 +SM_CYFRAME :: 33 +SM_CXMINTRACK :: 34 +SM_CYMINTRACK :: 35 +SM_CXDOUBLECLK :: 36 +SM_CYDOUBLECLK :: 37 +SM_CXICONSPACING :: 38 +SM_CYICONSPACING :: 39 +SM_MENUDROPALIGNMENT :: 40 +SM_PENWINDOWS :: 41 +SM_DBCSENABLED :: 42 +SM_CMOUSEBUTTONS :: 43 + +SM_CXFIXEDFRAME :: SM_CXDLGFRAME // ;win40 name change +SM_CYFIXEDFRAME :: SM_CYDLGFRAME // ;win40 name change +SM_CXSIZEFRAME :: SM_CXFRAME // ;win40 name change +SM_CYSIZEFRAME :: SM_CYFRAME // ;win40 name change + +SM_SECURE :: 44 +SM_CXEDGE :: 45 +SM_CYEDGE :: 46 +SM_CXMINSPACING :: 47 +SM_CYMINSPACING :: 48 +SM_CXSMICON :: 49 +SM_CYSMICON :: 50 +SM_CYSMCAPTION :: 51 +SM_CXSMSIZE :: 52 +SM_CYSMSIZE :: 53 +SM_CXMENUSIZE :: 54 +SM_CYMENUSIZE :: 55 +SM_ARRANGE :: 56 +SM_CXMINIMIZED :: 57 +SM_CYMINIMIZED :: 58 +SM_CXMAXTRACK :: 59 +SM_CYMAXTRACK :: 60 +SM_CXMAXIMIZED :: 61 +SM_CYMAXIMIZED :: 62 +SM_NETWORK :: 63 +SM_CLEANBOOT :: 67 +SM_CXDRAG :: 68 +SM_CYDRAG :: 69 + +SM_SHOWSOUNDS :: 70 +SM_CXMENUCHECK :: 71 // Use instead of GetMenuCheckMarkDimensions()! +SM_CYMENUCHECK :: 72 +SM_SLOWMACHINE :: 73 +SM_MIDEASTENABLED :: 74 +SM_MOUSEWHEELPRESENT :: 75 +SM_XVIRTUALSCREEN :: 76 +SM_YVIRTUALSCREEN :: 77 +SM_CXVIRTUALSCREEN :: 78 +SM_CYVIRTUALSCREEN :: 79 +SM_CMONITORS :: 80 +SM_SAMEDISPLAYFORMAT :: 81 +SM_IMMENABLED :: 82 +SM_CXFOCUSBORDER :: 83 +SM_CYFOCUSBORDER :: 84 +SM_TABLETPC :: 86 +SM_MEDIACENTER :: 87 +SM_STARTER :: 88 +SM_SERVERR2 :: 89 + +SM_MOUSEHORIZONTALWHEELPRESENT :: 91 + +SM_CXPADDEDBORDER :: 92 +SM_DIGITIZER :: 94 +SM_MAXIMUMTOUCHES :: 95 +SM_CMETRICS :: 97 + +SM_REMOTESESSION :: 0x1000 +SM_SHUTTINGDOWN :: 0x2000 +SM_REMOTECONTROL :: 0x2001 +SM_CARETBLINKINGENABLED :: 0x2002 +SM_CONVERTIBLESLATEMODE :: 0x2003 +SM_SYSTEMDOCKED :: 0x2004 + +// System Menu Command Values +SC_SIZE :: 0xF000 +SC_MOVE :: 0xF010 +SC_MINIMIZE :: 0xF020 +SC_MAXIMIZE :: 0xF030 +SC_NEXTWINDOW :: 0xF040 +SC_PREVWINDOW :: 0xF050 +SC_CLOSE :: 0xF060 +SC_VSCROLL :: 0xF070 +SC_HSCROLL :: 0xF080 +SC_MOUSEMENU :: 0xF090 +SC_KEYMENU :: 0xF100 +SC_ARRANGE :: 0xF110 +SC_RESTORE :: 0xF120 +SC_TASKLIST :: 0xF130 +SC_SCREENSAVE :: 0xF140 +SC_HOTKEY :: 0xF150 +SC_DEFAULT :: 0xF160 +SC_MONITORPOWER :: 0xF170 +SC_CONTEXTHELP :: 0xF180 +SC_SEPARATOR :: 0xF00F +SCF_ISSECURE :: 0x00000001 +SC_ICON :: SC_MINIMIZE +SC_ZOOM :: SC_MAXIMIZE + +CW_USEDEFAULT : c_int : -2147483648 + +SIZE_RESTORED :: 0 +SIZE_MINIMIZED :: 1 +SIZE_MAXIMIZED :: 2 +SIZE_MAXSHOW :: 3 +SIZE_MAXHIDE :: 4 + +WMSZ_LEFT :: 1 +WMSZ_RIGHT :: 2 +WMSZ_TOP :: 3 +WMSZ_TOPLEFT :: 4 +WMSZ_TOPRIGHT :: 5 +WMSZ_BOTTOM :: 6 +WMSZ_BOTTOMLEFT :: 7 +WMSZ_BOTTOMRIGHT :: 8 + +// Key State Masks for Mouse Messages +MK_LBUTTON :: 0x0001 +MK_RBUTTON :: 0x0002 +MK_SHIFT :: 0x0004 +MK_CONTROL :: 0x0008 +MK_MBUTTON :: 0x0010 +MK_XBUTTON1 :: 0x0020 +MK_XBUTTON2 :: 0x0040 + +USER_TIMER_MAXIMUM :: 0x7FFFFFFF +USER_TIMER_MINIMUM :: 0x0000000A + +_IDC_APPSTARTING := rawptr(uintptr(32650)) +_IDC_ARROW := rawptr(uintptr(32512)) +_IDC_CROSS := rawptr(uintptr(32515)) +_IDC_HAND := rawptr(uintptr(32649)) +_IDC_HELP := rawptr(uintptr(32651)) +_IDC_IBEAM := rawptr(uintptr(32513)) +_IDC_ICON := rawptr(uintptr(32641)) +_IDC_NO := rawptr(uintptr(32648)) +_IDC_SIZE := rawptr(uintptr(32640)) +_IDC_SIZEALL := rawptr(uintptr(32646)) +_IDC_SIZENESW := rawptr(uintptr(32643)) +_IDC_SIZENS := rawptr(uintptr(32645)) +_IDC_SIZENWSE := rawptr(uintptr(32642)) +_IDC_SIZEWE := rawptr(uintptr(32644)) +_IDC_UPARROW := rawptr(uintptr(32516)) +_IDC_WAIT := rawptr(uintptr(32514)) + +IDC_APPSTARTING := cstring(_IDC_APPSTARTING) +IDC_ARROW := cstring(_IDC_ARROW) +IDC_CROSS := cstring(_IDC_CROSS) +IDC_HAND := cstring(_IDC_HAND) +IDC_HELP := cstring(_IDC_HELP) +IDC_IBEAM := cstring(_IDC_IBEAM) +IDC_ICON := cstring(_IDC_ICON) +IDC_NO := cstring(_IDC_NO) +IDC_SIZE := cstring(_IDC_SIZE) +IDC_SIZEALL := cstring(_IDC_SIZEALL) +IDC_SIZENESW := cstring(_IDC_SIZENESW) +IDC_SIZENS := cstring(_IDC_SIZENS) +IDC_SIZENWSE := cstring(_IDC_SIZENWSE) +IDC_SIZEWE := cstring(_IDC_SIZEWE) +IDC_UPARROW := cstring(_IDC_UPARROW) +IDC_WAIT := cstring(_IDC_WAIT) + + +_IDI_APPLICATION := rawptr(uintptr(32512)) +_IDI_HAND := rawptr(uintptr(32513)) +_IDI_QUESTION := rawptr(uintptr(32514)) +_IDI_EXCLAMATION := rawptr(uintptr(32515)) +_IDI_ASTERISK := rawptr(uintptr(32516)) +_IDI_WINLOGO := rawptr(uintptr(32517)) +_IDI_SHIELD := rawptr(uintptr(32518)) +IDI_APPLICATION := cstring(_IDI_APPLICATION) +IDI_HAND := cstring(_IDI_HAND) +IDI_QUESTION := cstring(_IDI_QUESTION) +IDI_EXCLAMATION := cstring(_IDI_EXCLAMATION) +IDI_ASTERISK := cstring(_IDI_ASTERISK) +IDI_WINLOGO := cstring(_IDI_WINLOGO) +IDI_SHIELD := cstring(_IDI_SHIELD) +IDI_WARNING := IDI_EXCLAMATION +IDI_ERROR := IDI_HAND +IDI_INFORMATION := IDI_ASTERISK + + +// DIB color table identifiers +DIB_RGB_COLORS :: 0 +DIB_PAL_COLORS :: 1 + +// constants for CreateDIBitmap +CBM_INIT :: 0x04 // initialize bitmap + +// Region Flags +ERROR :: 0 +NULLREGION :: 1 +SIMPLEREGION :: 2 +COMPLEXREGION :: 3 +RGN_ERROR :: ERROR + +// StretchBlt() Modes +BLACKONWHITE :: 1 +WHITEONBLACK :: 2 +COLORONCOLOR :: 3 +HALFTONE :: 4 +MAXSTRETCHBLTMODE :: 4 + +// Binary raster ops +R2_BLACK :: 1 // 0 +R2_NOTMERGEPEN :: 2 // DPon +R2_MASKNOTPEN :: 3 // DPna +R2_NOTCOPYPEN :: 4 // PN +R2_MASKPENNOT :: 5 // PDna +R2_NOT :: 6 // Dn +R2_XORPEN :: 7 // DPx +R2_NOTMASKPEN :: 8 // DPan +R2_MASKPEN :: 9 // DPa +R2_NOTXORPEN :: 10 // DPxn +R2_NOP :: 11 // D +R2_MERGENOTPEN :: 12 // DPno +R2_COPYPEN :: 13 // P +R2_MERGEPENNOT :: 14 // PDno +R2_MERGEPEN :: 15 // DPo +R2_WHITE :: 16 // 1 +R2_LAST :: 16 + +// Ternary raster operations +SRCCOPY : DWORD : 0x00CC0020 // dest = source +SRCPAINT : DWORD : 0x00EE0086 // dest = source OR dest +SRCAND : DWORD : 0x008800C6 // dest = source AND dest +SRCINVERT : DWORD : 0x00660046 // dest = source XOR dest +SRCERASE : DWORD : 0x00440328 // dest = source AND (NOT dest) +NOTSRCCOPY : DWORD : 0x00330008 // dest = (NOT source) +NOTSRCERASE : DWORD : 0x001100A6 // dest = (NOT src) AND (NOT dest) +MERGECOPY : DWORD : 0x00C000CA // dest = (source AND pattern +MERGEPAINT : DWORD : 0x00BB0226 // dest = (NOT source) OR dest +PATCOPY : DWORD : 0x00F00021 // dest = pattern +PATPAINT : DWORD : 0x00FB0A09 // dest = DPSnoo +PATINVERT : DWORD : 0x005A0049 // dest = pattern XOR dest +DSTINVERT : DWORD : 0x00550009 // dest = (NOT dest) +BLACKNESS : DWORD : 0x00000042 // dest = BLACK +WHITENESS : DWORD : 0x00FF0062 // dest = WHITE +NOMIRRORBITMAP : DWORD : 0x80000000 // Do not Mirror the bitmap in this call +CAPTUREBLT : DWORD : 0x40000000 // Include layered windows + +// Stock Logical Objects +WHITE_BRUSH :: 0 +LTGRAY_BRUSH :: 1 +GRAY_BRUSH :: 2 +DKGRAY_BRUSH :: 3 +BLACK_BRUSH :: 4 +NULL_BRUSH :: 5 +HOLLOW_BRUSH :: NULL_BRUSH +WHITE_PEN :: 6 +BLACK_PEN :: 7 +NULL_PEN :: 8 +OEM_FIXED_FONT :: 10 +ANSI_FIXED_FONT :: 11 +ANSI_VAR_FONT :: 12 +SYSTEM_FONT :: 13 +DEVICE_DEFAULT_FONT :: 14 +DEFAULT_PALETTE :: 15 +SYSTEM_FIXED_FONT :: 16 +DEFAULT_GUI_FONT :: 17 +DC_BRUSH :: 18 +DC_PEN :: 19 +STOCK_LAST :: 19 + +CLR_INVALID :: 0xFFFFFFFF + +RGBQUAD :: struct { + rgbBlue: BYTE, + rgbGreen: BYTE, + rgbRed: BYTE, + rgbReserved: BYTE, +} + +PIXELFORMATDESCRIPTOR :: struct { + nSize: WORD, + nVersion: WORD, + dwFlags: DWORD, + iPixelType: BYTE, + cColorBits: BYTE, + cRedBits: BYTE, + cRedShift: BYTE, + cGreenBits: BYTE, + cGreenShift: BYTE, + cBlueBits: BYTE, + cBlueShift: BYTE, + cAlphaBits: BYTE, + cAlphaShift: BYTE, + cAccumBits: BYTE, + cAccumRedBits: BYTE, + cAccumGreenBits: BYTE, + cAccumBlueBits: BYTE, + cAccumAlphaBits: BYTE, + cDepthBits: BYTE, + cStencilBits: BYTE, + cAuxBuffers: BYTE, + iLayerType: BYTE, + bReserved: BYTE, + dwLayerMask: DWORD, + dwVisibleMask: DWORD, + dwDamageMask: DWORD, +} + +BITMAPINFOHEADER :: struct { + biSize: DWORD, + biWidth: LONG, + biHeight: LONG, + biPlanes: WORD, + biBitCount: WORD, + biCompression: DWORD, + biSizeImage: DWORD, + biXPelsPerMeter: LONG, + biYPelsPerMeter: LONG, + biClrUsed: DWORD, + biClrImportant: DWORD, +} + +BITMAPINFO :: struct { + bmiHeader: BITMAPINFOHEADER, + bmiColors: [1]RGBQUAD, +} + +// pixel types +PFD_TYPE_RGBA :: 0 +PFD_TYPE_COLORINDEX :: 1 + +// layer types +PFD_MAIN_PLANE :: 0 +PFD_OVERLAY_PLANE :: 1 +PFD_UNDERLAY_PLANE :: -1 + +// PIXELFORMATDESCRIPTOR flags +PFD_DOUBLEBUFFER :: 0x00000001 +PFD_STEREO :: 0x00000002 +PFD_DRAW_TO_WINDOW :: 0x00000004 +PFD_DRAW_TO_BITMAP :: 0x00000008 +PFD_SUPPORT_GDI :: 0x00000010 +PFD_SUPPORT_OPENGL :: 0x00000020 +PFD_GENERIC_FORMAT :: 0x00000040 +PFD_NEED_PALETTE :: 0x00000080 +PFD_NEED_SYSTEM_PALETTE :: 0x00000100 +PFD_SWAP_EXCHANGE :: 0x00000200 +PFD_SWAP_COPY :: 0x00000400 +PFD_SWAP_LAYER_BUFFERS :: 0x00000800 +PFD_GENERIC_ACCELERATED :: 0x00001000 +PFD_SUPPORT_DIRECTDRAW :: 0x00002000 +PFD_DIRECT3D_ACCELERATED :: 0x00004000 +PFD_SUPPORT_COMPOSITION :: 0x00008000 + +// PIXELFORMATDESCRIPTOR flags for use in ChoosePixelFormat only +PFD_DEPTH_DONTCARE :: 0x20000000 +PFD_DOUBLEBUFFER_DONTCARE :: 0x40000000 +PFD_STEREO_DONTCARE :: 0x80000000 + +// constants for the biCompression field +BI_RGB :: 0 +BI_RLE8 :: 1 +BI_RLE4 :: 2 +BI_BITFIELDS :: 3 +BI_JPEG :: 4 +BI_PNG :: 5 + WSA_FLAG_OVERLAPPED: DWORD : 0x01 WSA_FLAG_NO_HANDLE_INHERIT: DWORD : 0x80 @@ -379,6 +1069,15 @@ FILE_TYPE_PIPE :: 0x0003 RECT :: struct {left, top, right, bottom: LONG} POINT :: struct {x, y: LONG} +WINDOWPOS :: struct { + hwnd: HWND, + hwndInsertAfter: HWND, + x: c_int, + y: c_int, + cx: c_int, + cy: c_int, + flags: UINT, +} when size_of(uintptr) == 4 { WSADATA :: struct { @@ -578,7 +1277,7 @@ PROCESS_INFORMATION :: struct { } // FYI: This is STARTUPINFOW, not STARTUPINFOA -STARTUPINFO :: struct #packed { +STARTUPINFO :: struct { cb: DWORD, lpReserved: LPWSTR, lpDesktop: LPWSTR, @@ -784,17 +1483,17 @@ SYSTEM_INFO :: struct { // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw OSVERSIONINFOEXW :: struct { - dwOSVersionInfoSize: ULONG, - dwMajorVersion: ULONG, - dwMinorVersion: ULONG, - dwBuildNumber: ULONG, - dwPlatformId: ULONG, - szCSDVersion: [128]WCHAR, - wServicePackMajor: USHORT, - wServicePackMinor: USHORT, - wSuiteMask: USHORT, - wProductType: UCHAR, - wReserved: UCHAR, + dwOSVersionInfoSize: ULONG, + dwMajorVersion: ULONG, + dwMinorVersion: ULONG, + dwBuildNumber: ULONG, + dwPlatformId: ULONG, + szCSDVersion: [128]WCHAR, + wServicePackMajor: USHORT, + wServicePackMinor: USHORT, + wSuiteMask: USHORT, + wProductType: UCHAR, + wReserved: UCHAR, } // https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-quota_limits @@ -837,24 +1536,24 @@ PROFILEINFOW :: struct { lpDefaultPath: LPWSTR, lpServerName: LPWSTR, lpPolicyPath: LPWSTR, - hProfile: HANDLE, + hProfile: HANDLE, } // Used in LookupAccountNameW SID_NAME_USE :: distinct DWORD SID_TYPE :: enum SID_NAME_USE { - User = 1, - Group, - Domain, - Alias, - WellKnownGroup, - DeletedAccount, - Invalid, - Unknown, - Computer, - Label, - LogonSession, + User = 1, + Group, + Domain, + Alias, + WellKnownGroup, + DeletedAccount, + Invalid, + Unknown, + Computer, + Label, + LogonSession, } SECURITY_MAX_SID_SIZE :: 68 @@ -869,7 +1568,7 @@ SID :: struct #packed { #assert(size_of(SID) == SECURITY_MAX_SID_SIZE) SID_IDENTIFIER_AUTHORITY :: struct #packed { - Value: [6]u8, + Value: [6]u8, } // For NetAPI32 @@ -901,11 +1600,11 @@ USER_INFO_FLAG :: enum DWORD { Passwd_Cant_Change = 6, // 1 << 6: 0x0040, Encrypted_Text_Password_Allowed = 7, // 1 << 7: 0x0080, - Temp_Duplicate_Account = 8, // 1 << 8: 0x0100, - Normal_Account = 9, // 1 << 9: 0x0200, - InterDomain_Trust_Account = 11, // 1 << 11: 0x0800, - Workstation_Trust_Account = 12, // 1 << 12: 0x1000, - Server_Trust_Account = 13, // 1 << 13: 0x2000, + Temp_Duplicate_Account = 8, // 1 << 8: 0x0100, + Normal_Account = 9, // 1 << 9: 0x0200, + InterDomain_Trust_Account = 11, // 1 << 11: 0x0800, + Workstation_Trust_Account = 12, // 1 << 12: 0x1000, + Server_Trust_Account = 13, // 1 << 13: 0x2000, } USER_INFO_FLAGS :: distinct bit_set[USER_INFO_FLAG] @@ -1252,4 +1951,4 @@ SYSTEMTIME :: struct { minute: WORD, second: WORD, milliseconds: WORD, -} \ No newline at end of file +} diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin new file mode 100644 index 000000000..bdab77e27 --- /dev/null +++ b/core/sys/windows/user32.odin @@ -0,0 +1,198 @@ +// +build windows +package sys_windows + +foreign import user32 "system:User32.lib" + +@(default_calling_convention="stdcall") +foreign user32 { + GetClassInfoA :: proc(hInstance: HINSTANCE, lpClassNAme: LPCSTR, lpWndClass: ^WNDCLASSA) -> BOOL --- + GetClassInfoW :: proc(hInstance: HINSTANCE, lpClassNAme: LPCWSTR, lpWndClass: ^WNDCLASSW) -> BOOL --- + GetClassInfoExA :: proc(hInsatnce: HINSTANCE, lpszClass: LPCSTR, lpwcx: ^WNDCLASSEXA) -> BOOL --- + GetClassInfoExW :: proc(hInsatnce: HINSTANCE, lpszClass: LPCWSTR, lpwcx: ^WNDCLASSEXW) -> BOOL --- + + GetClassLongPtrA :: proc(hWnd: HWND, nIndex: c_int) -> DWORD --- + GetClassLongPtrW :: proc(hWnd: HWND, nIndex: c_int) -> DWORD --- + SetClassLongPtrA :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> ULONG_PTR --- + SetClassLongPtrW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> ULONG_PTR --- + + GetClassNameA :: proc(hWnd: HWND, lpClassName: LPSTR, nMaxCount: c_int) -> c_int --- + GetClassNameW :: proc(hWnd: HWND, lpClassName: LPWSTR, nMaxCount: c_int) -> c_int --- + + GetWindowLongPtrA :: proc(hWnd: HWND, nIndex: c_int) -> LONG_PTR --- + GetWindowLongPtrW :: proc(hWnd: HWND, nIndex: c_int) -> LONG_PTR --- + SetWindowLongPtrA :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> LONG_PTR --- + SetWindowLongPtrW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> LONG_PTR --- + + RegisterClassA :: proc(lpWndClass: ^WNDCLASSA) -> ATOM --- + RegisterClassW :: proc(lpWndClass: ^WNDCLASSW) -> ATOM --- + RegisterClassExA :: proc(^WNDCLASSEXA) -> ATOM --- + RegisterClassExW :: proc(^WNDCLASSEXW) -> ATOM --- + + CreateWindowExA :: proc( + dwExStyle: DWORD, + lpClassName: LPCSTR, + lpWindowName: LPCSTR, + dwStyle: DWORD, + X: c_int, + Y: c_int, + nWidth: c_int, + nHeight: c_int, + hWndParent: HWND, + hMenu: HMENU, + hInstance: HINSTANCE, + lpParam: LPVOID, + ) -> HWND --- + CreateWindowExW :: proc( + dwExStyle: DWORD, + lpClassName: LPCWSTR, + lpWindowName: LPCWSTR, + dwStyle: DWORD, + X: c_int, + Y: c_int, + nWidth: c_int, + nHeight: c_int, + hWndParent: HWND, + hMenu: HMENU, + hInstance: HINSTANCE, + lpParam: LPVOID, + ) -> HWND --- + + DestroyWindow :: proc(hWnd: HWND) -> BOOL --- + + ShowWindow :: proc(hWnd: HWND, nCmdShow: c_int) -> BOOL --- + + GetMessageA :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> BOOL --- + GetMessageW :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> BOOL --- + + TranslateMessage :: proc(lpMsg: ^MSG) -> BOOL --- + DispatchMessageA :: proc(lpMsg: ^MSG) -> LRESULT --- + DispatchMessageW :: proc(lpMsg: ^MSG) -> LRESULT --- + + PeekMessageA :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT, wRemoveMsg: UINT) -> BOOL --- + PeekMessageW :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT, wRemoveMsg: UINT) -> BOOL --- + + PostMessageA :: proc(hWnd: HWND, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> BOOL --- + PostMessageW :: proc(hWnd: HWND, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> BOOL --- + SendMessageA :: proc(hWnd: HWND, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> LRESULT --- + SendMessageW :: proc(hWnd: HWND, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> LRESULT --- + + PostThreadMessageA :: proc(idThread: DWORD, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> BOOL --- + PostThreadMessageW :: proc(idThread: DWORD, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> BOOL --- + + PostQuitMessage :: proc(nExitCode: c_int) --- + + GetQueueStatus :: proc(flags: UINT) -> DWORD --- + + DefWindowProcA :: proc(hWnd: HWND, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> LRESULT --- + DefWindowProcW :: proc(hWnd: HWND, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> LRESULT --- + + FindWindowA :: proc(lpClassName: LPCSTR, lpWindowName: LPCSTR) -> HWND --- + FindWindowW :: proc(lpClassName: LPCWSTR, lpWindowName: LPCWSTR) -> HWND --- + FindWindowExA :: proc(hWndParent: HWND, hWndChildAfter: HWND, lpszClass: LPCSTR, lpszWindow: LPCSTR) -> HWND --- + FindWindowExW :: proc(hWndParent: HWND, hWndChildAfter: HWND, lpszClass: LPCWSTR, lpszWindow: LPCWSTR) -> HWND --- + + LoadIconA :: proc(hInstance: HINSTANCE, lpIconName: LPCSTR) -> HICON --- + LoadIconW :: proc(hInstance: HINSTANCE, lpIconName: LPCWSTR) -> HICON --- + LoadCursorA :: proc(hInstance: HINSTANCE, lpCursorName: LPCSTR) -> HCURSOR --- + LoadCursorW :: proc(hInstance: HINSTANCE, lpCursorName: LPCWSTR) -> HCURSOR --- + + GetWindowRect :: proc(hWnd: HWND, lpRect: LPRECT) -> BOOL --- + GetClientRect :: proc(hWnd: HWND, lpRect: LPRECT) -> BOOL --- + ClientToScreen :: proc(hWnd: HWND, lpPoint: LPPOINT) -> BOOL --- + SetWindowPos :: proc( + hWnd: HWND, + hWndInsertAfter: HWND, + X: c_int, + Y: c_int, + cx: c_int, + cy: c_int, + uFlags: UINT, + ) -> BOOL --- + GetSystemMetrics :: proc(nIndex: c_int) -> c_int --- + AdjustWindowRect :: proc(lpRect: LPRECT, dwStyle: DWORD, bMenu: BOOL) -> BOOL --- + AdjustWindowRectEx :: proc(lpRect: LPRECT, dwStyle: DWORD, bMenu: BOOL, dwExStyle: DWORD) -> BOOL --- + + GetWindowDC :: proc(hWnd: HWND) -> HDC --- + GetDC :: proc(hWnd: HWND) -> HDC --- + ReleaseDC :: proc(hWnd: HWND, hDC: HDC) -> c_int --- + + GetUpdateRect :: proc(hWnd: HWND, lpRect: LPRECT, bErase: BOOL) -> BOOL --- + ValidateRect :: proc(hWnd: HWND, lpRect: ^RECT) -> BOOL --- + InvalidateRect :: proc(hWnd: HWND, lpRect: ^RECT, bErase: BOOL) -> BOOL --- + + BeginPaint :: proc(hWnd: HWND, lpPaint: ^PAINTSTRUCT) -> HDC --- + EndPaint :: proc(hWnd: HWND, lpPaint: ^PAINTSTRUCT) -> BOOL --- + + GetKeyState :: proc(nVirtKey: c_int) -> SHORT --- + GetAsyncKeyState :: proc(vKey: c_int) -> SHORT --- + + SetTimer :: proc(hWnd: HWND, nIDEvent: UINT_PTR, uElapse: UINT, lpTimerFunc: TIMERPROC) -> UINT_PTR --- + KillTimer :: proc(hWnd: HWND, uIDEvent: UINT_PTR) -> BOOL --- + + MessageBoxA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT) -> c_int --- + MessageBoxW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT) -> c_int --- + MessageBoxExA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT, wLanguageId: WORD) -> c_int --- + MessageBoxExW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT, wLanguageId: WORD) -> c_int --- +} + +CreateWindowA :: #force_inline proc "stdcall" ( + lpClassName: LPCSTR, + lpWindowName: LPCSTR, + dwStyle: DWORD, + X: c_int, + Y: c_int, + nWidth: c_int, + nHeight: c_int, + hWndParent: HWND, + hMenu: HMENU, + hInstance: HINSTANCE, + lpParam: LPVOID, +) -> HWND { + return CreateWindowExA( + 0, + lpClassName, + lpWindowName, + dwStyle, + X, + Y, + nWidth, + nHeight, + hWndParent, + hMenu, + hInstance, + lpParam, + ) +} + +CreateWindowW :: #force_inline proc "stdcall" ( + lpClassName: LPCTSTR, + lpWindowName: LPCTSTR, + dwStyle: DWORD, + X: c_int, + Y: c_int, + nWidth: c_int, + nHeight: c_int, + hWndParent: HWND, + hMenu: HMENU, + hInstance: HINSTANCE, + lpParam: LPVOID, +) -> HWND { + return CreateWindowExW( + 0, + lpClassName, + lpWindowName, + dwStyle, + X, + Y, + nWidth, + nHeight, + hWndParent, + hMenu, + hInstance, + lpParam, + ) +} + +GET_SC_WPARAM :: #force_inline proc(wparam: WPARAM) -> i32 { + return i32(wparam) & 0xFFF0 +} diff --git a/core/sys/windows/util.odin b/core/sys/windows/util.odin index efb37dbc0..5797216c3 100644 --- a/core/sys/windows/util.odin +++ b/core/sys/windows/util.odin @@ -45,7 +45,9 @@ utf8_to_wstring :: proc(s: string, allocator := context.temp_allocator) -> wstri return nil } -wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator) -> string { +wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator) -> (res: string) { + context.allocator = allocator + if N <= 0 { return "" } @@ -60,7 +62,7 @@ wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator) // also null terminated. // If N != -1 it assumes the wide string is not null terminated and the resulting string // will not be null terminated, we therefore have to force it to be null terminated manually. - text := make([]byte, n+1 if N != -1 else n, allocator) + text := make([]byte, n+1 if N != -1 else n) n1 := WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s, i32(N), raw_data(text), n, nil, nil) if n1 == 0 { @@ -74,7 +76,6 @@ wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator) break } } - return string(text[:n]) } diff --git a/core/sys/windows/window_messages.odin b/core/sys/windows/window_messages.odin new file mode 100644 index 000000000..9c3a30088 --- /dev/null +++ b/core/sys/windows/window_messages.odin @@ -0,0 +1,1048 @@ +// +build windows +package sys_windows + +WM_NULL :: 0x0000 +WM_CREATE :: 0x0001 +WM_DESTROY :: 0x0002 +WM_MOVE :: 0x0003 +WM_SIZE :: 0x0005 +WM_ACTIVATE :: 0x0006 +WM_SETFOCUS :: 0x0007 +WM_KILLFOCUS :: 0x0008 +WM_ENABLE :: 0x000a +WM_SETREDRAW :: 0x000b +WM_SETTEXT :: 0x000c +WM_GETTEXT :: 0x000d +WM_GETTEXTLENGTH :: 0x000e +WM_PAINT :: 0x000f +WM_CLOSE :: 0x0010 +WM_QUERYENDSESSION :: 0x0011 +WM_QUIT :: 0x0012 +WM_QUERYOPEN :: 0x0013 +WM_ERASEBKGND :: 0x0014 +WM_SYSCOLORCHANGE :: 0x0015 +WM_ENDSESSION :: 0x0016 +WM_SHOWWINDOW :: 0x0018 +WM_CTLCOLOR :: 0x0019 +WM_WININICHANGE :: 0x001a +WM_DEVMODECHANGE :: 0x001b +WM_ACTIVATEAPP :: 0x001c +WM_FONTCHANGE :: 0x001d +WM_TIMECHANGE :: 0x001e +WM_CANCELMODE :: 0x001f +WM_SETCURSOR :: 0x0020 +WM_MOUSEACTIVATE :: 0x0021 +WM_CHILDACTIVATE :: 0x0022 +WM_QUEUESYNC :: 0x0023 +WM_GETMINMAXINFO :: 0x0024 +WM_PAINTICON :: 0x0026 +WM_ICONERASEBKGND :: 0x0027 +WM_NEXTDLGCTL :: 0x0028 +WM_SPOOLERSTATUS :: 0x002a +WM_DRAWITEM :: 0x002b +WM_MEASUREITEM :: 0x002c +WM_DELETEITEM :: 0x002d +WM_VKEYTOITEM :: 0x002e +WM_CHARTOITEM :: 0x002f +WM_SETFONT :: 0x0030 +WM_GETFONT :: 0x0031 +WM_SETHOTKEY :: 0x0032 +WM_GETHOTKEY :: 0x0033 +WM_QUERYDRAGICON :: 0x0037 +WM_COMPAREITEM :: 0x0039 +WM_GETOBJECT :: 0x003d +WM_COMPACTING :: 0x0041 +WM_COMMNOTIFY :: 0x0044 +WM_WINDOWPOSCHANGING :: 0x0046 +WM_WINDOWPOSCHANGED :: 0x0047 +WM_POWER :: 0x0048 +WM_COPYGLOBALDATA :: 0x0049 +WM_COPYDATA :: 0x004a +WM_CANCELJOURNAL :: 0x004b +WM_NOTIFY :: 0x004e +WM_INPUTLANGCHANGEREQUEST :: 0x0050 +WM_INPUTLANGCHANGE :: 0x0051 +WM_TCARD :: 0x0052 +WM_HELP :: 0x0053 +WM_USERCHANGED :: 0x0054 +WM_NOTIFYFORMAT :: 0x0055 +WM_CONTEXTMENU :: 0x007b +WM_STYLECHANGING :: 0x007c +WM_STYLECHANGED :: 0x007d +WM_DISPLAYCHANGE :: 0x007e +WM_GETICON :: 0x007f +WM_SETICON :: 0x0080 +WM_NCCREATE :: 0x0081 +WM_NCDESTROY :: 0x0082 +WM_NCCALCSIZE :: 0x0083 +WM_NCHITTEST :: 0x0084 +WM_NCPAINT :: 0x0085 +WM_NCACTIVATE :: 0x0086 +WM_GETDLGCODE :: 0x0087 +WM_SYNCPAINT :: 0x0088 +WM_NCMOUSEMOVE :: 0x00a0 +WM_NCLBUTTONDOWN :: 0x00a1 +WM_NCLBUTTONUP :: 0x00a2 +WM_NCLBUTTONDBLCLK :: 0x00a3 +WM_NCRBUTTONDOWN :: 0x00a4 +WM_NCRBUTTONUP :: 0x00a5 +WM_NCRBUTTONDBLCLK :: 0x00a6 +WM_NCMBUTTONDOWN :: 0x00a7 +WM_NCMBUTTONUP :: 0x00a8 +WM_NCMBUTTONDBLCLK :: 0x00a9 +WM_NCXBUTTONDOWN :: 0x00ab +WM_NCXBUTTONUP :: 0x00ac +WM_NCXBUTTONDBLCLK :: 0x00ad +EM_GETSEL :: 0x00b0 +EM_SETSEL :: 0x00b1 +EM_GETRECT :: 0x00b2 +EM_SETRECT :: 0x00b3 +EM_SETRECTNP :: 0x00b4 +EM_SCROLL :: 0x00b5 +EM_LINESCROLL :: 0x00b6 +EM_SCROLLCARET :: 0x00b7 +EM_GETMODIFY :: 0x00b8 +EM_SETMODIFY :: 0x00b9 +EM_GETLINECOUNT :: 0x00ba +EM_LINEINDEX :: 0x00bb +EM_SETHANDLE :: 0x00bc +EM_GETHANDLE :: 0x00bd +EM_GETTHUMB :: 0x00be +EM_LINELENGTH :: 0x00c1 +EM_REPLACESEL :: 0x00c2 +EM_SETFONT :: 0x00c3 +EM_GETLINE :: 0x00c4 +EM_LIMITTEXT :: 0x00c5 +EM_SETLIMITTEXT :: 0x00c5 +EM_CANUNDO :: 0x00c6 +EM_UNDO :: 0x00c7 +EM_FMTLINES :: 0x00c8 +EM_LINEFROMCHAR :: 0x00c9 +EM_SETWORDBREAK :: 0x00ca +EM_SETTABSTOPS :: 0x00cb +EM_SETPASSWORDCHAR :: 0x00cc +EM_EMPTYUNDOBUFFER :: 0x00cd +EM_GETFIRSTVISIBLELINE :: 0x00ce +EM_SETREADONLY :: 0x00cf +EM_SETWORDBREAKPROC :: 0x00d0 +EM_GETWORDBREAKPROC :: 0x00d1 +EM_GETPASSWORDCHAR :: 0x00d2 +EM_SETMARGINS :: 0x00d3 +EM_GETMARGINS :: 0x00d4 +EM_GETLIMITTEXT :: 0x00d5 +EM_POSFROMCHAR :: 0x00d6 +EM_CHARFROMPOS :: 0x00d7 +EM_SETIMESTATUS :: 0x00d8 +EM_GETIMESTATUS :: 0x00d9 +SBM_SETPOS :: 0x00e0 +SBM_GETPOS :: 0x00e1 +SBM_SETRANGE :: 0x00e2 +SBM_GETRANGE :: 0x00e3 +SBM_ENABLE_ARROWS :: 0x00e4 +SBM_SETRANGEREDRAW :: 0x00e6 +SBM_SETSCROLLINFO :: 0x00e9 +SBM_GETSCROLLINFO :: 0x00ea +SBM_GETSCROLLBARINFO :: 0x00eb +BM_GETCHECK :: 0x00f0 +BM_SETCHECK :: 0x00f1 +BM_GETSTATE :: 0x00f2 +BM_SETSTATE :: 0x00f3 +BM_SETSTYLE :: 0x00f4 +BM_CLICK :: 0x00f5 +BM_GETIMAGE :: 0x00f6 +BM_SETIMAGE :: 0x00f7 +BM_SETDONTCLICK :: 0x00f8 +WM_INPUT :: 0x00ff +WM_KEYDOWN :: 0x0100 +WM_KEYFIRST :: 0x0100 +WM_KEYUP :: 0x0101 +WM_CHAR :: 0x0102 +WM_DEADCHAR :: 0x0103 +WM_SYSKEYDOWN :: 0x0104 +WM_SYSKEYUP :: 0x0105 +WM_SYSCHAR :: 0x0106 +WM_SYSDEADCHAR :: 0x0107 +WM_UNICHAR :: 0x0109 +WM_KEYLAST :: 0x0109 +WM_WNT_CONVERTREQUESTEX :: 0x0109 +WM_CONVERTREQUEST :: 0x010a +WM_CONVERTRESULT :: 0x010b +WM_INTERIM :: 0x010c +WM_IME_STARTCOMPOSITION :: 0x010d +WM_IME_ENDCOMPOSITION :: 0x010e +WM_IME_COMPOSITION :: 0x010f +WM_IME_KEYLAST :: 0x010f +WM_INITDIALOG :: 0x0110 +WM_COMMAND :: 0x0111 +WM_SYSCOMMAND :: 0x0112 +WM_TIMER :: 0x0113 +WM_HSCROLL :: 0x0114 +WM_VSCROLL :: 0x0115 +WM_INITMENU :: 0x0116 +WM_INITMENUPOPUP :: 0x0117 +WM_SYSTIMER :: 0x0118 +WM_MENUSELECT :: 0x011f +WM_MENUCHAR :: 0x0120 +WM_ENTERIDLE :: 0x0121 +WM_MENURBUTTONUP :: 0x0122 +WM_MENUDRAG :: 0x0123 +WM_MENUGETOBJECT :: 0x0124 +WM_UNINITMENUPOPUP :: 0x0125 +WM_MENUCOMMAND :: 0x0126 +WM_CHANGEUISTATE :: 0x0127 +WM_UPDATEUISTATE :: 0x0128 +WM_QUERYUISTATE :: 0x0129 +WM_LBTRACKPOINT :: 0x0131 +WM_CTLCOLORMSGBOX :: 0x0132 +WM_CTLCOLOREDIT :: 0x0133 +WM_CTLCOLORLISTBOX :: 0x0134 +WM_CTLCOLORBTN :: 0x0135 +WM_CTLCOLORDLG :: 0x0136 +WM_CTLCOLORSCROLLBAR :: 0x0137 +WM_CTLCOLORSTATIC :: 0x0138 +CB_GETEDITSEL :: 0x0140 +CB_LIMITTEXT :: 0x0141 +CB_SETEDITSEL :: 0x0142 +CB_ADDSTRING :: 0x0143 +CB_DELETESTRING :: 0x0144 +CB_DIR :: 0x0145 +CB_GETCOUNT :: 0x0146 +CB_GETCURSEL :: 0x0147 +CB_GETLBTEXT :: 0x0148 +CB_GETLBTEXTLEN :: 0x0149 +CB_INSERTSTRING :: 0x014a +CB_RESETCONTENT :: 0x014b +CB_FINDSTRING :: 0x014c +CB_SELECTSTRING :: 0x014d +CB_SETCURSEL :: 0x014e +CB_SHOWDROPDOWN :: 0x014f +CB_GETITEMDATA :: 0x0150 +CB_SETITEMDATA :: 0x0151 +CB_GETDROPPEDCONTROLRECT :: 0x0152 +CB_SETITEMHEIGHT :: 0x0153 +CB_GETITEMHEIGHT :: 0x0154 +CB_SETEXTENDEDUI :: 0x0155 +CB_GETEXTENDEDUI :: 0x0156 +CB_GETDROPPEDSTATE :: 0x0157 +CB_FINDSTRINGEXACT :: 0x0158 +CB_SETLOCALE :: 0x0159 +CB_GETLOCALE :: 0x015a +CB_GETTOPINDEX :: 0x015b +CB_SETTOPINDEX :: 0x015c +CB_GETHORIZONTALEXTENT :: 0x015d +CB_SETHORIZONTALEXTENT :: 0x015e +CB_GETDROPPEDWIDTH :: 0x015f +CB_SETDROPPEDWIDTH :: 0x0160 +CB_INITSTORAGE :: 0x0161 +CB_MULTIPLEADDSTRING :: 0x0163 +CB_GETCOMBOBOXINFO :: 0x0164 +CB_MSGMAX :: 0x0165 +WM_MOUSEFIRST :: 0x0200 +WM_MOUSEMOVE :: 0x0200 +WM_LBUTTONDOWN :: 0x0201 +WM_LBUTTONUP :: 0x0202 +WM_LBUTTONDBLCLK :: 0x0203 +WM_RBUTTONDOWN :: 0x0204 +WM_RBUTTONUP :: 0x0205 +WM_RBUTTONDBLCLK :: 0x0206 +WM_MBUTTONDOWN :: 0x0207 +WM_MBUTTONUP :: 0x0208 +WM_MBUTTONDBLCLK :: 0x0209 +WM_MOUSELAST :: 0x0209 +WM_MOUSEWHEEL :: 0x020a +WM_XBUTTONDOWN :: 0x020b +WM_XBUTTONUP :: 0x020c +WM_XBUTTONDBLCLK :: 0x020d +WM_MOUSEHWHEEL :: 0x020e +WM_PARENTNOTIFY :: 0x0210 +WM_ENTERMENULOOP :: 0x0211 +WM_EXITMENULOOP :: 0x0212 +WM_NEXTMENU :: 0x0213 +WM_SIZING :: 0x0214 +WM_CAPTURECHANGED :: 0x0215 +WM_MOVING :: 0x0216 +WM_POWERBROADCAST :: 0x0218 +WM_DEVICECHANGE :: 0x0219 +WM_MDICREATE :: 0x0220 +WM_MDIDESTROY :: 0x0221 +WM_MDIACTIVATE :: 0x0222 +WM_MDIRESTORE :: 0x0223 +WM_MDINEXT :: 0x0224 +WM_MDIMAXIMIZE :: 0x0225 +WM_MDITILE :: 0x0226 +WM_MDICASCADE :: 0x0227 +WM_MDIICONARRANGE :: 0x0228 +WM_MDIGETACTIVE :: 0x0229 +WM_MDISETMENU :: 0x0230 +WM_ENTERSIZEMOVE :: 0x0231 +WM_EXITSIZEMOVE :: 0x0232 +WM_DROPFILES :: 0x0233 +WM_MDIREFRESHMENU :: 0x0234 +WM_IME_REPORT :: 0x0280 +WM_IME_SETCONTEXT :: 0x0281 +WM_IME_NOTIFY :: 0x0282 +WM_IME_CONTROL :: 0x0283 +WM_IME_COMPOSITIONFULL :: 0x0284 +WM_IME_SELECT :: 0x0285 +WM_IME_CHAR :: 0x0286 +WM_IME_REQUEST :: 0x0288 +WM_IMEKEYDOWN :: 0x0290 +WM_IME_KEYDOWN :: 0x0290 +WM_IMEKEYUP :: 0x0291 +WM_IME_KEYUP :: 0x0291 +WM_NCMOUSEHOVER :: 0x02a0 +WM_MOUSEHOVER :: 0x02a1 +WM_NCMOUSELEAVE :: 0x02a2 +WM_MOUSELEAVE :: 0x02a3 +WM_CUT :: 0x0300 +WM_COPY :: 0x0301 +WM_PASTE :: 0x0302 +WM_CLEAR :: 0x0303 +WM_UNDO :: 0x0304 +WM_RENDERFORMAT :: 0x0305 +WM_RENDERALLFORMATS :: 0x0306 +WM_DESTROYCLIPBOARD :: 0x0307 +WM_DRAWCLIPBOARD :: 0x0308 +WM_PAINTCLIPBOARD :: 0x0309 +WM_VSCROLLCLIPBOARD :: 0x030a +WM_SIZECLIPBOARD :: 0x030b +WM_ASKCBFORMATNAME :: 0x030c +WM_CHANGECBCHAIN :: 0x030d +WM_HSCROLLCLIPBOARD :: 0x030e +WM_QUERYNEWPALETTE :: 0x030f +WM_PALETTEISCHANGING :: 0x0310 +WM_PALETTECHANGED :: 0x0311 +WM_HOTKEY :: 0x0312 +WM_PRINT :: 0x0317 +WM_PRINTCLIENT :: 0x0318 +WM_APPCOMMAND :: 0x0319 +WM_HANDHELDFIRST :: 0x0358 +WM_HANDHELDLAST :: 0x035f +WM_AFXFIRST :: 0x0360 +WM_AFXLAST :: 0x037f +WM_PENWINFIRST :: 0x0380 +WM_RCRESULT :: 0x0381 +WM_HOOKRCRESULT :: 0x0382 +WM_GLOBALRCCHANGE :: 0x0383 +WM_PENMISCINFO :: 0x0383 +WM_SKB :: 0x0384 +WM_HEDITCTL :: 0x0385 +WM_PENCTL :: 0x0385 +WM_PENMISC :: 0x0386 +WM_CTLINIT :: 0x0387 +WM_PENEVENT :: 0x0388 +WM_PENWINLAST :: 0x038f +DDM_SETFMT :: 0x0400 +DM_GETDEFID :: 0x0400 +NIN_SELECT :: 0x0400 +TBM_GETPOS :: 0x0400 +WM_PSD_PAGESETUPDLG :: 0x0400 +WM_USER :: 0x0400 +CBEM_INSERTITEMA :: 0x0401 +DDM_DRAW :: 0x0401 +DM_SETDEFID :: 0x0401 +HKM_SETHOTKEY :: 0x0401 +PBM_SETRANGE :: 0x0401 +RB_INSERTBANDA :: 0x0401 +SB_SETTEXTA :: 0x0401 +TB_ENABLEBUTTON :: 0x0401 +TBM_GETRANGEMIN :: 0x0401 +TTM_ACTIVATE :: 0x0401 +WM_CHOOSEFONT_GETLOGFONT :: 0x0401 +WM_PSD_FULLPAGERECT :: 0x0401 +CBEM_SETIMAGELIST :: 0x0402 +DDM_CLOSE :: 0x0402 +DM_REPOSITION :: 0x0402 +HKM_GETHOTKEY :: 0x0402 +PBM_SETPOS :: 0x0402 +RB_DELETEBAND :: 0x0402 +SB_GETTEXTA :: 0x0402 +TB_CHECKBUTTON :: 0x0402 +TBM_GETRANGEMAX :: 0x0402 +WM_PSD_MINMARGINRECT :: 0x0402 +CBEM_GETIMAGELIST :: 0x0403 +DDM_BEGIN :: 0x0403 +HKM_SETRULES :: 0x0403 +PBM_DELTAPOS :: 0x0403 +RB_GETBARINFO :: 0x0403 +SB_GETTEXTLENGTHA :: 0x0403 +TBM_GETTIC :: 0x0403 +TB_PRESSBUTTON :: 0x0403 +TTM_SETDELAYTIME :: 0x0403 +WM_PSD_MARGINRECT :: 0x0403 +CBEM_GETITEMA :: 0x0404 +DDM_END :: 0x0404 +PBM_SETSTEP :: 0x0404 +RB_SETBARINFO :: 0x0404 +SB_SETPARTS :: 0x0404 +TB_HIDEBUTTON :: 0x0404 +TBM_SETTIC :: 0x0404 +TTM_ADDTOOLA :: 0x0404 +WM_PSD_GREEKTEXTRECT :: 0x0404 +CBEM_SETITEMA :: 0x0405 +PBM_STEPIT :: 0x0405 +TB_INDETERMINATE :: 0x0405 +TBM_SETPOS :: 0x0405 +TTM_DELTOOLA :: 0x0405 +WM_PSD_ENVSTAMPRECT :: 0x0405 +CBEM_GETCOMBOCONTROL :: 0x0406 +PBM_SETRANGE32 :: 0x0406 +RB_SETBANDINFOA :: 0x0406 +SB_GETPARTS :: 0x0406 +TB_MARKBUTTON :: 0x0406 +TBM_SETRANGE :: 0x0406 +TTM_NEWTOOLRECTA :: 0x0406 +WM_PSD_YAFULLPAGERECT :: 0x0406 +CBEM_GETEDITCONTROL :: 0x0407 +PBM_GETRANGE :: 0x0407 +RB_SETPARENT :: 0x0407 +SB_GETBORDERS :: 0x0407 +TBM_SETRANGEMIN :: 0x0407 +TTM_RELAYEVENT :: 0x0407 +CBEM_SETEXSTYLE :: 0x0408 +PBM_GETPOS :: 0x0408 +RB_HITTEST :: 0x0408 +SB_SETMINHEIGHT :: 0x0408 +TBM_SETRANGEMAX :: 0x0408 +TTM_GETTOOLINFOA :: 0x0408 +CBEM_GETEXSTYLE :: 0x0409 +CBEM_GETEXTENDEDSTYLE :: 0x0409 +PBM_SETBARCOLOR :: 0x0409 +RB_GETRECT :: 0x0409 +SB_SIMPLE :: 0x0409 +TB_ISBUTTONENABLED :: 0x0409 +TBM_CLEARTICS :: 0x0409 +TTM_SETTOOLINFOA :: 0x0409 +CBEM_HASEDITCHANGED :: 0x040a +RB_INSERTBANDW :: 0x040a +SB_GETRECT :: 0x040a +TB_ISBUTTONCHECKED :: 0x040a +TBM_SETSEL :: 0x040a +TTM_HITTESTA :: 0x040a +WIZ_QUERYNUMPAGES :: 0x040a +CBEM_INSERTITEMW :: 0x040b +RB_SETBANDINFOW :: 0x040b +SB_SETTEXTW :: 0x040b +TB_ISBUTTONPRESSED :: 0x040b +TBM_SETSELSTART :: 0x040b +TTM_GETTEXTA :: 0x040b +WIZ_NEXT :: 0x040b +CBEM_SETITEMW :: 0x040c +RB_GETBANDCOUNT :: 0x040c +SB_GETTEXTLENGTHW :: 0x040c +TB_ISBUTTONHIDDEN :: 0x040c +TBM_SETSELEND :: 0x040c +TTM_UPDATETIPTEXTA :: 0x040c +WIZ_PREV :: 0x040c +CBEM_GETITEMW :: 0x040d +RB_GETROWCOUNT :: 0x040d +SB_GETTEXTW :: 0x040d +TB_ISBUTTONINDETERMINATE :: 0x040d +TTM_GETTOOLCOUNT :: 0x040d +CBEM_SETEXTENDEDSTYLE :: 0x040e +RB_GETROWHEIGHT :: 0x040e +SB_ISSIMPLE :: 0x040e +TB_ISBUTTONHIGHLIGHTED :: 0x040e +TBM_GETPTICS :: 0x040e +TTM_ENUMTOOLSA :: 0x040e +SB_SETICON :: 0x040f +TBM_GETTICPOS :: 0x040f +TTM_GETCURRENTTOOLA :: 0x040f +RB_IDTOINDEX :: 0x0410 +SB_SETTIPTEXTA :: 0x0410 +TBM_GETNUMTICS :: 0x0410 +TTM_WINDOWFROMPOINT :: 0x0410 +RB_GETTOOLTIPS :: 0x0411 +SB_SETTIPTEXTW :: 0x0411 +TBM_GETSELSTART :: 0x0411 +TB_SETSTATE :: 0x0411 +TTM_TRACKACTIVATE :: 0x0411 +RB_SETTOOLTIPS :: 0x0412 +SB_GETTIPTEXTA :: 0x0412 +TB_GETSTATE :: 0x0412 +TBM_GETSELEND :: 0x0412 +TTM_TRACKPOSITION :: 0x0412 +RB_SETBKCOLOR :: 0x0413 +SB_GETTIPTEXTW :: 0x0413 +TB_ADDBITMAP :: 0x0413 +TBM_CLEARSEL :: 0x0413 +TTM_SETTIPBKCOLOR :: 0x0413 +RB_GETBKCOLOR :: 0x0414 +SB_GETICON :: 0x0414 +TB_ADDBUTTONSA :: 0x0414 +TBM_SETTICFREQ :: 0x0414 +TTM_SETTIPTEXTCOLOR :: 0x0414 +RB_SETTEXTCOLOR :: 0x0415 +TB_INSERTBUTTONA :: 0x0415 +TBM_SETPAGESIZE :: 0x0415 +TTM_GETDELAYTIME :: 0x0415 +RB_GETTEXTCOLOR :: 0x0416 +TB_DELETEBUTTON :: 0x0416 +TBM_GETPAGESIZE :: 0x0416 +TTM_GETTIPBKCOLOR :: 0x0416 +RB_SIZETORECT :: 0x0417 +TB_GETBUTTON :: 0x0417 +TBM_SETLINESIZE :: 0x0417 +TTM_GETTIPTEXTCOLOR :: 0x0417 +RB_BEGINDRAG :: 0x0418 +TB_BUTTONCOUNT :: 0x0418 +TBM_GETLINESIZE :: 0x0418 +TTM_SETMAXTIPWIDTH :: 0x0418 +RB_ENDDRAG :: 0x0419 +TB_COMMANDTOINDEX :: 0x0419 +TBM_GETTHUMBRECT :: 0x0419 +TTM_GETMAXTIPWIDTH :: 0x0419 +RB_DRAGMOVE :: 0x041a +TBM_GETCHANNELRECT :: 0x041a +TB_SAVERESTOREA :: 0x041a +TTM_SETMARGIN :: 0x041a +RB_GETBARHEIGHT :: 0x041b +TB_CUSTOMIZE :: 0x041b +TBM_SETTHUMBLENGTH :: 0x041b +TTM_GETMARGIN :: 0x041b +RB_GETBANDINFOW :: 0x041c +TB_ADDSTRINGA :: 0x041c +TBM_GETTHUMBLENGTH :: 0x041c +TTM_POP :: 0x041c +RB_GETBANDINFOA :: 0x041d +TB_GETITEMRECT :: 0x041d +TBM_SETTOOLTIPS :: 0x041d +TTM_UPDATE :: 0x041d +RB_MINIMIZEBAND :: 0x041e +TB_BUTTONSTRUCTSIZE :: 0x041e +TBM_GETTOOLTIPS :: 0x041e +TTM_GETBUBBLESIZE :: 0x041e +RB_MAXIMIZEBAND :: 0x041f +TBM_SETTIPSIDE :: 0x041f +TB_SETBUTTONSIZE :: 0x041f +TTM_ADJUSTRECT :: 0x041f +TBM_SETBUDDY :: 0x0420 +TB_SETBITMAPSIZE :: 0x0420 +TTM_SETTITLEA :: 0x0420 +MSG_FTS_JUMP_VA :: 0x0421 +TB_AUTOSIZE :: 0x0421 +TBM_GETBUDDY :: 0x0421 +TTM_SETTITLEW :: 0x0421 +RB_GETBANDBORDERS :: 0x0422 +MSG_FTS_JUMP_QWORD :: 0x0423 +RB_SHOWBAND :: 0x0423 +TB_GETTOOLTIPS :: 0x0423 +MSG_REINDEX_REQUEST :: 0x0424 +TB_SETTOOLTIPS :: 0x0424 +MSG_FTS_WHERE_IS_IT :: 0x0425 +RB_SETPALETTE :: 0x0425 +TB_SETPARENT :: 0x0425 +RB_GETPALETTE :: 0x0426 +RB_MOVEBAND :: 0x0427 +TB_SETROWS :: 0x0427 +TB_GETROWS :: 0x0428 +TB_GETBITMAPFLAGS :: 0x0429 +TB_SETCMDID :: 0x042a +RB_PUSHCHEVRON :: 0x042b +TB_CHANGEBITMAP :: 0x042b +TB_GETBITMAP :: 0x042c +MSG_GET_DEFFONT :: 0x042d +TB_GETBUTTONTEXTA :: 0x042d +TB_REPLACEBITMAP :: 0x042e +TB_SETINDENT :: 0x042f +TB_SETIMAGELIST :: 0x0430 +TB_GETIMAGELIST :: 0x0431 +TB_LOADIMAGES :: 0x0432 +EM_CANPASTE :: 0x0432 +TTM_ADDTOOLW :: 0x0432 +EM_DISPLAYBAND :: 0x0433 +TB_GETRECT :: 0x0433 +TTM_DELTOOLW :: 0x0433 +EM_EXGETSEL :: 0x0434 +TB_SETHOTIMAGELIST :: 0x0434 +TTM_NEWTOOLRECTW :: 0x0434 +EM_EXLIMITTEXT :: 0x0435 +TB_GETHOTIMAGELIST :: 0x0435 +TTM_GETTOOLINFOW :: 0x0435 +EM_EXLINEFROMCHAR :: 0x0436 +TB_SETDISABLEDIMAGELIST :: 0x0436 +TTM_SETTOOLINFOW :: 0x0436 +EM_EXSETSEL :: 0x0437 +TB_GETDISABLEDIMAGELIST :: 0x0437 +TTM_HITTESTW :: 0x0437 +EM_FINDTEXT :: 0x0438 +TB_SETSTYLE :: 0x0438 +TTM_GETTEXTW :: 0x0438 +EM_FORMATRANGE :: 0x0439 +TB_GETSTYLE :: 0x0439 +TTM_UPDATETIPTEXTW :: 0x0439 +EM_GETCHARFORMAT :: 0x043a +TB_GETBUTTONSIZE :: 0x043a +TTM_ENUMTOOLSW :: 0x043a +EM_GETEVENTMASK :: 0x043b +TB_SETBUTTONWIDTH :: 0x043b +TTM_GETCURRENTTOOLW :: 0x043b +EM_GETOLEINTERFACE :: 0x043c +TB_SETMAXTEXTROWS :: 0x043c +EM_GETPARAFORMAT :: 0x043d +TB_GETTEXTROWS :: 0x043d +EM_GETSELTEXT :: 0x043e +TB_GETOBJECT :: 0x043e +EM_HIDESELECTION :: 0x043f +TB_GETBUTTONINFOW :: 0x043f +EM_PASTESPECIAL :: 0x0440 +TB_SETBUTTONINFOW :: 0x0440 +EM_REQUESTRESIZE :: 0x0441 +TB_GETBUTTONINFOA :: 0x0441 +EM_SELECTIONTYPE :: 0x0442 +TB_SETBUTTONINFOA :: 0x0442 +EM_SETBKGNDCOLOR :: 0x0443 +TB_INSERTBUTTONW :: 0x0443 +EM_SETCHARFORMAT :: 0x0444 +TB_ADDBUTTONSW :: 0x0444 +EM_SETEVENTMASK :: 0x0445 +TB_HITTEST :: 0x0445 +EM_SETOLECALLBACK :: 0x0446 +TB_SETDRAWTEXTFLAGS :: 0x0446 +EM_SETPARAFORMAT :: 0x0447 +TB_GETHOTITEM :: 0x0447 +EM_SETTARGETDEVICE :: 0x0448 +TB_SETHOTITEM :: 0x0448 +EM_STREAMIN :: 0x0449 +TB_SETANCHORHIGHLIGHT :: 0x0449 +EM_STREAMOUT :: 0x044a +TB_GETANCHORHIGHLIGHT :: 0x044a +EM_GETTEXTRANGE :: 0x044b +TB_GETBUTTONTEXTW :: 0x044b +EM_FINDWORDBREAK :: 0x044c +TB_SAVERESTOREW :: 0x044c +EM_SETOPTIONS :: 0x044d +TB_ADDSTRINGW :: 0x044d +EM_GETOPTIONS :: 0x044e +TB_MAPACCELERATORA :: 0x044e +EM_FINDTEXTEX :: 0x044f +TB_GETINSERTMARK :: 0x044f +EM_GETWORDBREAKPROCEX :: 0x0450 +TB_SETINSERTMARK :: 0x0450 +EM_SETWORDBREAKPROCEX :: 0x0451 +TB_INSERTMARKHITTEST :: 0x0451 +EM_SETUNDOLIMIT :: 0x0452 +TB_MOVEBUTTON :: 0x0452 +TB_GETMAXSIZE :: 0x0453 +EM_REDO :: 0x0454 +TB_SETEXTENDEDSTYLE :: 0x0454 +EM_CANREDO :: 0x0455 +TB_GETEXTENDEDSTYLE :: 0x0455 +EM_GETUNDONAME :: 0x0456 +TB_GETPADDING :: 0x0456 +EM_GETREDONAME :: 0x0457 +TB_SETPADDING :: 0x0457 +EM_STOPGROUPTYPING :: 0x0458 +TB_SETINSERTMARKCOLOR :: 0x0458 +EM_SETTEXTMODE :: 0x0459 +TB_GETINSERTMARKCOLOR :: 0x0459 +EM_GETTEXTMODE :: 0x045a +TB_MAPACCELERATORW :: 0x045a +EM_AUTOURLDETECT :: 0x045b +TB_GETSTRINGW :: 0x045b +EM_GETAUTOURLDETECT :: 0x045c +TB_GETSTRINGA :: 0x045c +EM_SETPALETTE :: 0x045d +EM_GETTEXTEX :: 0x045e +EM_GETTEXTLENGTHEX :: 0x045f +EM_SHOWSCROLLBAR :: 0x0460 +EM_SETTEXTEX :: 0x0461 +TAPI_REPLY :: 0x0463 +ACM_OPENA :: 0x0464 +BFFM_SETSTATUSTEXTA :: 0x0464 +CDM_FIRST :: 0x0464 +CDM_GETSPEC :: 0x0464 +EM_SETPUNCTUATION :: 0x0464 +IPM_CLEARADDRESS :: 0x0464 +WM_CAP_UNICODE_START :: 0x0464 +ACM_PLAY :: 0x0465 +BFFM_ENABLEOK :: 0x0465 +CDM_GETFILEPATH :: 0x0465 +EM_GETPUNCTUATION :: 0x0465 +IPM_SETADDRESS :: 0x0465 +PSM_SETCURSEL :: 0x0465 +UDM_SETRANGE :: 0x0465 +WM_CHOOSEFONT_SETLOGFONT :: 0x0465 +ACM_STOP :: 0x0466 +BFFM_SETSELECTIONA :: 0x0466 +CDM_GETFOLDERPATH :: 0x0466 +EM_SETWORDWRAPMODE :: 0x0466 +IPM_GETADDRESS :: 0x0466 +PSM_REMOVEPAGE :: 0x0466 +UDM_GETRANGE :: 0x0466 +WM_CAP_SET_CALLBACK_ERRORW :: 0x0466 +WM_CHOOSEFONT_SETFLAGS :: 0x0466 +ACM_OPENW :: 0x0467 +BFFM_SETSELECTIONW :: 0x0467 +CDM_GETFOLDERIDLIST :: 0x0467 +EM_GETWORDWRAPMODE :: 0x0467 +IPM_SETRANGE :: 0x0467 +PSM_ADDPAGE :: 0x0467 +UDM_SETPOS :: 0x0467 +WM_CAP_SET_CALLBACK_STATUSW :: 0x0467 +BFFM_SETSTATUSTEXTW :: 0x0468 +CDM_SETCONTROLTEXT :: 0x0468 +EM_SETIMECOLOR :: 0x0468 +IPM_SETFOCUS :: 0x0468 +PSM_CHANGED :: 0x0468 +UDM_GETPOS :: 0x0468 +CDM_HIDECONTROL :: 0x0469 +EM_GETIMECOLOR :: 0x0469 +IPM_ISBLANK :: 0x0469 +PSM_RESTARTWINDOWS :: 0x0469 +UDM_SETBUDDY :: 0x0469 +CDM_SETDEFEXT :: 0x046a +EM_SETIMEOPTIONS :: 0x046a +PSM_REBOOTSYSTEM :: 0x046a +UDM_GETBUDDY :: 0x046a +EM_GETIMEOPTIONS :: 0x046b +PSM_CANCELTOCLOSE :: 0x046b +UDM_SETACCEL :: 0x046b +EM_CONVPOSITION :: 0x046c +PSM_QUERYSIBLINGS :: 0x046c +UDM_GETACCEL :: 0x046c +MCIWNDM_GETZOOM :: 0x046d +PSM_UNCHANGED :: 0x046d +UDM_SETBASE :: 0x046d +PSM_APPLY :: 0x046e +UDM_GETBASE :: 0x046e +PSM_SETTITLEA :: 0x046f +UDM_SETRANGE32 :: 0x046f +PSM_SETWIZBUTTONS :: 0x0470 +UDM_GETRANGE32 :: 0x0470 +WM_CAP_DRIVER_GET_NAMEW :: 0x0470 +PSM_PRESSBUTTON :: 0x0471 +UDM_SETPOS32 :: 0x0471 +WM_CAP_DRIVER_GET_VERSIONW :: 0x0471 +PSM_SETCURSELID :: 0x0472 +UDM_GETPOS32 :: 0x0472 +PSM_SETFINISHTEXTA :: 0x0473 +PSM_GETTABCONTROL :: 0x0474 +PSM_ISDIALOGMESSAGE :: 0x0475 +MCIWNDM_REALIZE :: 0x0476 +PSM_GETCURRENTPAGEHWND :: 0x0476 +MCIWNDM_SETTIMEFORMATA :: 0x0477 +PSM_INSERTPAGE :: 0x0477 +EM_SETLANGOPTIONS :: 0x0478 +MCIWNDM_GETTIMEFORMATA :: 0x0478 +PSM_SETTITLEW :: 0x0478 +WM_CAP_FILE_SET_CAPTURE_FILEW :: 0x0478 +EM_GETLANGOPTIONS :: 0x0479 +MCIWNDM_VALIDATEMEDIA :: 0x0479 +PSM_SETFINISHTEXTW :: 0x0479 +WM_CAP_FILE_GET_CAPTURE_FILEW :: 0x0479 +EM_GETIMECOMPMODE :: 0x047a +EM_FINDTEXTW :: 0x047b +MCIWNDM_PLAYTO :: 0x047b +WM_CAP_FILE_SAVEASW :: 0x047b +EM_FINDTEXTEXW :: 0x047c +MCIWNDM_GETFILENAMEA :: 0x047c +EM_RECONVERSION :: 0x047d +MCIWNDM_GETDEVICEA :: 0x047d +PSM_SETHEADERTITLEA :: 0x047d +WM_CAP_FILE_SAVEDIBW :: 0x047d +EM_SETIMEMODEBIAS :: 0x047e +MCIWNDM_GETPALETTE :: 0x047e +PSM_SETHEADERTITLEW :: 0x047e +EM_GETIMEMODEBIAS :: 0x047f +MCIWNDM_SETPALETTE :: 0x047f +PSM_SETHEADERSUBTITLEA :: 0x047f +MCIWNDM_GETERRORA :: 0x0480 +PSM_SETHEADERSUBTITLEW :: 0x0480 +PSM_HWNDTOINDEX :: 0x0481 +PSM_INDEXTOHWND :: 0x0482 +MCIWNDM_SETINACTIVETIMER :: 0x0483 +PSM_PAGETOINDEX :: 0x0483 +PSM_INDEXTOPAGE :: 0x0484 +DL_BEGINDRAG :: 0x0485 +MCIWNDM_GETINACTIVETIMER :: 0x0485 +PSM_IDTOINDEX :: 0x0485 +DL_DRAGGING :: 0x0486 +PSM_INDEXTOID :: 0x0486 +DL_DROPPED :: 0x0487 +PSM_GETRESULT :: 0x0487 +DL_CANCELDRAG :: 0x0488 +PSM_RECALCPAGESIZES :: 0x0488 +MCIWNDM_GET_SOURCE :: 0x048c +MCIWNDM_PUT_SOURCE :: 0x048d +MCIWNDM_GET_DEST :: 0x048e +MCIWNDM_PUT_DEST :: 0x048f +MCIWNDM_CAN_PLAY :: 0x0490 +MCIWNDM_CAN_WINDOW :: 0x0491 +MCIWNDM_CAN_RECORD :: 0x0492 +MCIWNDM_CAN_SAVE :: 0x0493 +MCIWNDM_CAN_EJECT :: 0x0494 +MCIWNDM_CAN_CONFIG :: 0x0495 +IE_GETINK :: 0x0496 +IE_MSGFIRST :: 0x0496 +MCIWNDM_PALETTEKICK :: 0x0496 +IE_SETINK :: 0x0497 +IE_GETPENTIP :: 0x0498 +IE_SETPENTIP :: 0x0499 +IE_GETERASERTIP :: 0x049a +IE_SETERASERTIP :: 0x049b +IE_GETBKGND :: 0x049c +IE_SETBKGND :: 0x049d +IE_GETGRIDORIGIN :: 0x049e +IE_SETGRIDORIGIN :: 0x049f +IE_GETGRIDPEN :: 0x04a0 +IE_SETGRIDPEN :: 0x04a1 +IE_GETGRIDSIZE :: 0x04a2 +IE_SETGRIDSIZE :: 0x04a3 +IE_GETMODE :: 0x04a4 +IE_SETMODE :: 0x04a5 +IE_GETINKRECT :: 0x04a6 +WM_CAP_SET_MCI_DEVICEW :: 0x04a6 +WM_CAP_GET_MCI_DEVICEW :: 0x04a7 +WM_CAP_PAL_OPENW :: 0x04b4 +WM_CAP_PAL_SAVEW :: 0x04b5 +IE_GETAPPDATA :: 0x04b8 +IE_SETAPPDATA :: 0x04b9 +IE_GETDRAWOPTS :: 0x04ba +IE_SETDRAWOPTS :: 0x04bb +IE_GETFORMAT :: 0x04bc +IE_SETFORMAT :: 0x04bd +IE_GETINKINPUT :: 0x04be +IE_SETINKINPUT :: 0x04bf +IE_GETNOTIFY :: 0x04c0 +IE_SETNOTIFY :: 0x04c1 +IE_GETRECOG :: 0x04c2 +IE_SETRECOG :: 0x04c3 +IE_GETSECURITY :: 0x04c4 +IE_SETSECURITY :: 0x04c5 +IE_GETSEL :: 0x04c6 +IE_SETSEL :: 0x04c7 +CDM_LAST :: 0x04c8 +EM_SETBIDIOPTIONS :: 0x04c8 +IE_DOCOMMAND :: 0x04c8 +MCIWNDM_NOTIFYMODE :: 0x04c8 +EM_GETBIDIOPTIONS :: 0x04c9 +IE_GETCOMMAND :: 0x04c9 +EM_SETTYPOGRAPHYOPTIONS :: 0x04ca +IE_GETCOUNT :: 0x04ca +EM_GETTYPOGRAPHYOPTIONS :: 0x04cb +IE_GETGESTURE :: 0x04cb +MCIWNDM_NOTIFYMEDIA :: 0x04cb +EM_SETEDITSTYLE :: 0x04cc +IE_GETMENU :: 0x04cc +EM_GETEDITSTYLE :: 0x04cd +IE_GETPAINTDC :: 0x04cd +MCIWNDM_NOTIFYERROR :: 0x04cd +IE_GETPDEVENT :: 0x04ce +IE_GETSELCOUNT :: 0x04cf +IE_GETSELITEMS :: 0x04d0 +IE_GETSTYLE :: 0x04d1 +MCIWNDM_SETTIMEFORMATW :: 0x04db +EM_OUTLINE :: 0x04dc +MCIWNDM_GETTIMEFORMATW :: 0x04dc +EM_GETSCROLLPOS :: 0x04dd +EM_SETSCROLLPOS :: 0x04de +EM_SETFONTSIZE :: 0x04df +EM_GETZOOM :: 0x04e0 +MCIWNDM_GETFILENAMEW :: 0x04e0 +EM_SETZOOM :: 0x04e1 +MCIWNDM_GETDEVICEW :: 0x04e1 +EM_GETVIEWKIND :: 0x04e2 +EM_SETVIEWKIND :: 0x04e3 +EM_GETPAGE :: 0x04e4 +MCIWNDM_GETERRORW :: 0x04e4 +EM_SETPAGE :: 0x04e5 +EM_GETHYPHENATEINFO :: 0x04e6 +EM_SETHYPHENATEINFO :: 0x04e7 +EM_GETPAGEROTATE :: 0x04eb +EM_SETPAGEROTATE :: 0x04ec +EM_GETCTFMODEBIAS :: 0x04ed +EM_SETCTFMODEBIAS :: 0x04ee +EM_GETCTFOPENSTATUS :: 0x04f0 +EM_SETCTFOPENSTATUS :: 0x04f1 +EM_GETIMECOMPTEXT :: 0x04f2 +EM_ISIME :: 0x04f3 +EM_GETIMEPROPERTY :: 0x04f4 +EM_GETQUERYRTFOBJ :: 0x050d +EM_SETQUERYRTFOBJ :: 0x050e +FM_GETFOCUS :: 0x0600 +FM_GETDRIVEINFOA :: 0x0601 +FM_GETSELCOUNT :: 0x0602 +FM_GETSELCOUNTLFN :: 0x0603 +FM_GETFILESELA :: 0x0604 +FM_GETFILESELLFNA :: 0x0605 +FM_REFRESH_WINDOWS :: 0x0606 +FM_RELOAD_EXTENSIONS :: 0x0607 +FM_GETDRIVEINFOW :: 0x0611 +FM_GETFILESELW :: 0x0614 +FM_GETFILESELLFNW :: 0x0615 +WLX_WM_SAS :: 0x0659 +SM_GETSELCOUNT :: 0x07e8 +UM_GETSELCOUNT :: 0x07e8 +WM_CPL_LAUNCH :: 0x07e8 +SM_GETSERVERSELA :: 0x07e9 +UM_GETUSERSELA :: 0x07e9 +WM_CPL_LAUNCHED :: 0x07e9 +SM_GETSERVERSELW :: 0x07ea +UM_GETUSERSELW :: 0x07ea +SM_GETCURFOCUSA :: 0x07eb +UM_GETGROUPSELA :: 0x07eb +SM_GETCURFOCUSW :: 0x07ec +UM_GETGROUPSELW :: 0x07ec +SM_GETOPTIONS :: 0x07ed +UM_GETCURFOCUSA :: 0x07ed +UM_GETCURFOCUSW :: 0x07ee +UM_GETOPTIONS :: 0x07ef +UM_GETOPTIONS2 :: 0x07f0 +LVM_FIRST :: 0x1000 +LVM_GETBKCOLOR :: 0x1000 +LVM_SETBKCOLOR :: 0x1001 +LVM_GETIMAGELIST :: 0x1002 +LVM_SETIMAGELIST :: 0x1003 +LVM_GETITEMCOUNT :: 0x1004 +LVM_GETITEMA :: 0x1005 +LVM_SETITEMA :: 0x1006 +LVM_INSERTITEMA :: 0x1007 +LVM_DELETEITEM :: 0x1008 +LVM_DELETEALLITEMS :: 0x1009 +LVM_GETCALLBACKMASK :: 0x100a +LVM_SETCALLBACKMASK :: 0x100b +LVM_GETNEXTITEM :: 0x100c +LVM_FINDITEMA :: 0x100d +LVM_GETITEMRECT :: 0x100e +LVM_SETITEMPOSITION :: 0x100f +LVM_GETITEMPOSITION :: 0x1010 +LVM_GETSTRINGWIDTHA :: 0x1011 +LVM_HITTEST :: 0x1012 +LVM_ENSUREVISIBLE :: 0x1013 +LVM_SCROLL :: 0x1014 +LVM_REDRAWITEMS :: 0x1015 +LVM_ARRANGE :: 0x1016 +LVM_EDITLABELA :: 0x1017 +LVM_GETEDITCONTROL :: 0x1018 +LVM_GETCOLUMNA :: 0x1019 +LVM_SETCOLUMNA :: 0x101a +LVM_INSERTCOLUMNA :: 0x101b +LVM_DELETECOLUMN :: 0x101c +LVM_GETCOLUMNWIDTH :: 0x101d +LVM_SETCOLUMNWIDTH :: 0x101e +LVM_GETHEADER :: 0x101f +LVM_CREATEDRAGIMAGE :: 0x1021 +LVM_GETVIEWRECT :: 0x1022 +LVM_GETTEXTCOLOR :: 0x1023 +LVM_SETTEXTCOLOR :: 0x1024 +LVM_GETTEXTBKCOLOR :: 0x1025 +LVM_SETTEXTBKCOLOR :: 0x1026 +LVM_GETTOPINDEX :: 0x1027 +LVM_GETCOUNTPERPAGE :: 0x1028 +LVM_GETORIGIN :: 0x1029 +LVM_UPDATE :: 0x102a +LVM_SETITEMSTATE :: 0x102b +LVM_GETITEMSTATE :: 0x102c +LVM_GETITEMTEXTA :: 0x102d +LVM_SETITEMTEXTA :: 0x102e +LVM_SETITEMCOUNT :: 0x102f +LVM_SORTITEMS :: 0x1030 +LVM_SETITEMPOSITION32 :: 0x1031 +LVM_GETSELECTEDCOUNT :: 0x1032 +LVM_GETITEMSPACING :: 0x1033 +LVM_GETISEARCHSTRINGA :: 0x1034 +LVM_SETICONSPACING :: 0x1035 +LVM_SETEXTENDEDLISTVIEWSTYLE :: 0x1036 +LVM_GETEXTENDEDLISTVIEWSTYLE :: 0x1037 +LVM_GETSUBITEMRECT :: 0x1038 +LVM_SUBITEMHITTEST :: 0x1039 +LVM_SETCOLUMNORDERARRAY :: 0x103a +LVM_GETCOLUMNORDERARRAY :: 0x103b +LVM_SETHOTITEM :: 0x103c +LVM_GETHOTITEM :: 0x103d +LVM_SETHOTCURSOR :: 0x103e +LVM_GETHOTCURSOR :: 0x103f +LVM_APPROXIMATEVIEWRECT :: 0x1040 +LVM_SETWORKAREAS :: 0x1041 +LVM_GETSELECTIONMARK :: 0x1042 +LVM_SETSELECTIONMARK :: 0x1043 +LVM_SETBKIMAGEA :: 0x1044 +LVM_GETBKIMAGEA :: 0x1045 +LVM_GETWORKAREAS :: 0x1046 +LVM_SETHOVERTIME :: 0x1047 +LVM_GETHOVERTIME :: 0x1048 +LVM_GETNUMBEROFWORKAREAS :: 0x1049 +LVM_SETTOOLTIPS :: 0x104a +LVM_GETITEMW :: 0x104b +LVM_SETITEMW :: 0x104c +LVM_INSERTITEMW :: 0x104d +LVM_GETTOOLTIPS :: 0x104e +LVM_FINDITEMW :: 0x1053 +LVM_GETSTRINGWIDTHW :: 0x1057 +LVM_GETCOLUMNW :: 0x105f +LVM_SETCOLUMNW :: 0x1060 +LVM_INSERTCOLUMNW :: 0x1061 +LVM_GETITEMTEXTW :: 0x1073 +LVM_SETITEMTEXTW :: 0x1074 +LVM_GETISEARCHSTRINGW :: 0x1075 +LVM_EDITLABELW :: 0x1076 +LVM_GETBKIMAGEW :: 0x108b +LVM_SETSELECTEDCOLUMN :: 0x108c +LVM_SETTILEWIDTH :: 0x108d +LVM_SETVIEW :: 0x108e +LVM_GETVIEW :: 0x108f +LVM_INSERTGROUP :: 0x1091 +LVM_SETGROUPINFO :: 0x1093 +LVM_GETGROUPINFO :: 0x1095 +LVM_REMOVEGROUP :: 0x1096 +LVM_MOVEGROUP :: 0x1097 +LVM_MOVEITEMTOGROUP :: 0x109a +LVM_SETGROUPMETRICS :: 0x109b +LVM_GETGROUPMETRICS :: 0x109c +LVM_ENABLEGROUPVIEW :: 0x109d +LVM_SORTGROUPS :: 0x109e +LVM_INSERTGROUPSORTED :: 0x109f +LVM_REMOVEALLGROUPS :: 0x10a0 +LVM_HASGROUP :: 0x10a1 +LVM_SETTILEVIEWINFO :: 0x10a2 +LVM_GETTILEVIEWINFO :: 0x10a3 +LVM_SETTILEINFO :: 0x10a4 +LVM_GETTILEINFO :: 0x10a5 +LVM_SETINSERTMARK :: 0x10a6 +LVM_GETINSERTMARK :: 0x10a7 +LVM_INSERTMARKHITTEST :: 0x10a8 +LVM_GETINSERTMARKRECT :: 0x10a9 +LVM_SETINSERTMARKCOLOR :: 0x10aa +LVM_GETINSERTMARKCOLOR :: 0x10ab +LVM_SETINFOTIP :: 0x10ad +LVM_GETSELECTEDCOLUMN :: 0x10ae +LVM_ISGROUPVIEWENABLED :: 0x10af +LVM_GETOUTLINECOLOR :: 0x10b0 +LVM_SETOUTLINECOLOR :: 0x10b1 +LVM_CANCELEDITLABEL :: 0x10b3 +LVM_MAPINDEXTOID :: 0x10b4 +LVM_MAPIDTOINDEX :: 0x10b5 +LVM_ISITEMVISIBLE :: 0x10b6 +LVM_GETEMPTYTEXT :: 0x10cc +LVM_GETFOOTERRECT :: 0x10cd +LVM_GETFOOTERINFO :: 0x10ce +LVM_GETFOOTERITEMRECT :: 0x10cf +LVM_GETFOOTERITEM :: 0x10d0 +LVM_GETITEMINDEXRECT :: 0x10d1 +LVM_SETITEMINDEXSTATE :: 0x10d2 +LVM_GETNEXTITEMINDEX :: 0x10d3 +OCM__BASE :: 0x2000 +LVM_SETUNICODEFORMAT :: 0x2005 +LVM_GETUNICODEFORMAT :: 0x2006 +OCM_CTLCOLOR :: 0x2019 +OCM_DRAWITEM :: 0x202b +OCM_MEASUREITEM :: 0x202c +OCM_DELETEITEM :: 0x202d +OCM_VKEYTOITEM :: 0x202e +OCM_CHARTOITEM :: 0x202f +OCM_COMPAREITEM :: 0x2039 +OCM_NOTIFY :: 0x204e +OCM_COMMAND :: 0x2111 +OCM_HSCROLL :: 0x2114 +OCM_VSCROLL :: 0x2115 +OCM_CTLCOLORMSGBOX :: 0x2132 +OCM_CTLCOLOREDIT :: 0x2133 +OCM_CTLCOLORLISTBOX :: 0x2134 +OCM_CTLCOLORBTN :: 0x2135 +OCM_CTLCOLORDLG :: 0x2136 +OCM_CTLCOLORSCROLLBAR :: 0x2137 +OCM_CTLCOLORSTATIC :: 0x2138 +OCM_PARENTNOTIFY :: 0x2210 +WM_APP :: 0x8000 +WM_RASDIALEVENT :: 0xcccd diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin index 560f23956..e812c410a 100644 --- a/core/testing/runner_windows.odin +++ b/core/testing/runner_windows.odin @@ -21,7 +21,7 @@ sema_wait :: proc "contextless" (s: ^Sema) { 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) { + if original_count == intrinsics.atomic_compare_exchange_strong(&s.count, original_count-1, original_count) { return } } @@ -46,7 +46,7 @@ sema_wait_with_timeout :: proc "contextless" (s: ^Sema, duration: time.Duration) } original_count = s.count } - if original_count == intrinsics.atomic_cxchg(&s.count, original_count-1, original_count) { + if original_count == intrinsics.atomic_compare_exchange_strong(&s.count, original_count-1, original_count) { return true } } diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 37ee4fa98..3f782cf73 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -1,67 +1,75 @@ package thread +/* + thread.Pool + Copyright 2022 eisbehr + Made available under Odin's BSD-3 license. +*/ + import "core:intrinsics" import "core:sync" import "core:mem" -Task_Status :: enum i32 { - Ready, - Busy, - Waiting, - Term, -} - -Task_Proc :: #type proc(task: ^Task) +Task_Proc :: #type proc(task: Task) Task :: struct { - procedure: Task_Proc, - data: rawptr, + procedure: Task_Proc, + data: rawptr, user_index: int, + allocator: mem.Allocator, } -Task_Id :: distinct i32 -INVALID_TASK_ID :: Task_Id(-1) - - +// Do not access the pool's members directly while the pool threads are running, +// since they use different kinds of locking and mutual exclusion devices. +// Careless access can and will lead to nasty bugs. Once initialized, the +// pool's memory address is not allowed to change until it is destroyed. Pool :: struct { - allocator: mem.Allocator, - mutex: sync.Mutex, - sem_available: sync.Semaphore, - processing_task_count: int, // atomic - is_running: bool, + allocator: mem.Allocator, + mutex: sync.Mutex, + sem_available: sync.Sema, + + // the following values are atomic + num_waiting: int, + num_in_processing: int, + num_outstanding: int, // num_waiting + num_in_processing + num_done: int, + // end of atomics + + is_running: bool, threads: []^Thread, - tasks: [dynamic]Task, + tasks: [dynamic]Task, + tasks_done: [dynamic]Task, } -pool_init :: proc(pool: ^Pool, thread_count: int, allocator := context.allocator) { - worker_thread_internal :: proc(t: ^Thread) { - pool := (^Pool)(t.data) - - for pool.is_running { - sync.semaphore_wait_for(&pool.sem_available) - - if task, ok := pool_try_and_pop_task(pool); ok { - pool_do_work(pool, &task) - } - } - - sync.semaphore_post(&pool.sem_available, 1) - } - - +// Once initialized, the pool's memory address is not allowed to change until +// it is destroyed. If thread_count < 1, thread count 1 will be used. +// +// The thread pool requires an allocator which it either owns, or which is thread safe. +pool_init :: proc(pool: ^Pool, thread_count: int, allocator: mem.Allocator) { context.allocator = allocator pool.allocator = allocator - pool.tasks = make([dynamic]Task) - pool.threads = make([]^Thread, thread_count) + pool.tasks = make([dynamic]Task) + pool.tasks_done = make([dynamic]Task) + pool.threads = make([]^Thread, max(thread_count, 1)) - sync.mutex_init(&pool.mutex) - sync.semaphore_init(&pool.sem_available) pool.is_running = true for _, i in pool.threads { - t := create(worker_thread_internal) + t := create(proc(t: ^Thread) { + pool := (^Pool)(t.data) + + for intrinsics.atomic_load(&pool.is_running) { + sync.wait(&pool.sem_available) + + if task, ok := pool_pop_waiting(pool); ok { + pool_do_work(pool, task) + } + } + + sync.post(&pool.sem_available, 1) + }) t.user_index = i t.data = pool pool.threads[i] = t @@ -70,15 +78,13 @@ pool_init :: proc(pool: ^Pool, thread_count: int, allocator := context.allocator pool_destroy :: proc(pool: ^Pool) { delete(pool.tasks) + delete(pool.tasks_done) - for thread in &pool.threads { - destroy(thread) + for t in &pool.threads { + destroy(t) } delete(pool.threads, pool.allocator) - - sync.mutex_destroy(&pool.mutex) - sync.semaphore_destroy(&pool.sem_available) } pool_start :: proc(pool: ^Pool) { @@ -87,10 +93,12 @@ pool_start :: proc(pool: ^Pool) { } } +// Finish tasks that have already started processing, then shut down all pool +// threads. Might leave over waiting tasks, any memory allocated for the +// user data of those tasks will not be freed. pool_join :: proc(pool: ^Pool) { - pool.is_running = false - - sync.semaphore_post(&pool.sem_available, len(pool.threads)) + intrinsics.atomic_store(&pool.is_running, false) + sync.post(&pool.sem_available, len(pool.threads)) yield() @@ -99,53 +107,112 @@ pool_join :: proc(pool: ^Pool) { } } -pool_add_task :: proc(pool: ^Pool, procedure: Task_Proc, data: rawptr, user_index: int = 0) { - sync.mutex_lock(&pool.mutex) - defer sync.mutex_unlock(&pool.mutex) +// Add a task to the thread pool. +// +// Tasks can be added from any thread, not just the thread that created +// the thread pool. You can even add tasks from inside other tasks. +// +// Each task also needs an allocator which it either owns, or which is thread +// safe. By default, allocations in the task are disabled by use of the +// nil_allocator. +pool_add_task :: proc(pool: ^Pool, procedure: Task_Proc, data: rawptr, user_index: int = 0, allocator := context.allocator) { + sync.guard(&pool.mutex) - task: Task - task.procedure = procedure - task.data = data - task.user_index = user_index - - append(&pool.tasks, task) - sync.semaphore_post(&pool.sem_available, 1) + append(&pool.tasks, Task{ + procedure = procedure, + data = data, + user_index = user_index, + allocator = allocator, + }) + intrinsics.atomic_add(&pool.num_waiting, 1) + intrinsics.atomic_add(&pool.num_outstanding, 1) + sync.post(&pool.sem_available, 1) } -pool_try_and_pop_task :: proc(pool: ^Pool) -> (task: Task, got_task: bool = false) { - if sync.mutex_try_lock(&pool.mutex) { - if len(pool.tasks) != 0 { - intrinsics.atomic_add(&pool.processing_task_count, 1) - task = pop_front(&pool.tasks) - got_task = true - } - sync.mutex_unlock(&pool.mutex) +// Number of tasks waiting to be processed. Only informational, mostly for +// debugging. Don't rely on this value being consistent with other num_* +// values. +pool_num_waiting :: #force_inline proc(pool: ^Pool) -> int { + return intrinsics.atomic_load(&pool.num_waiting) +} + +// Number of tasks currently being processed. Only informational, mostly for +// debugging. Don't rely on this value being consistent with other num_* +// values. +pool_num_in_processing :: #force_inline proc(pool: ^Pool) -> int { + return intrinsics.atomic_load(&pool.num_in_processing) +} + +// Outstanding tasks are all tasks that are not done, that is, tasks that are +// waiting, as well as tasks that are currently being processed. Only +// informational, mostly for debugging. Don't rely on this value being +// consistent with other num_* values. +pool_num_outstanding :: #force_inline proc(pool: ^Pool) -> int { + return intrinsics.atomic_load(&pool.num_outstanding) +} + +// Number of tasks which are done processing. Only informational, mostly for +// debugging. Don't rely on this value being consistent with other num_* +// values. +pool_num_done :: #force_inline proc(pool: ^Pool) -> int { + return intrinsics.atomic_load(&pool.num_done) +} + +// If tasks are only being added from one thread, and this procedure is being +// called from that same thread, it will reliably tell if the thread pool is +// empty or not. Empty in this case means there are no tasks waiting, being +// processed, or _done_. +pool_is_empty :: #force_inline proc(pool: ^Pool) -> bool { + return pool_num_outstanding(pool) == 0 && pool_num_done(pool) == 0 +} + +// Mostly for internal use. +pool_pop_waiting :: proc(pool: ^Pool) -> (task: Task, got_task: bool) { + sync.guard(&pool.mutex) + + if len(pool.tasks) != 0 { + intrinsics.atomic_sub(&pool.num_waiting, 1) + intrinsics.atomic_add(&pool.num_in_processing, 1) + task = pop_front(&pool.tasks) + got_task = true } + return } +// Use this to take out finished tasks. +pool_pop_done :: proc(pool: ^Pool) -> (task: Task, got_task: bool) { + sync.guard(&pool.mutex) -pool_do_work :: proc(pool: ^Pool, task: ^Task) { - task.procedure(task) - intrinsics.atomic_sub(&pool.processing_task_count, 1) -} - - -pool_wait_and_process :: proc(pool: ^Pool) { - for len(pool.tasks) != 0 || intrinsics.atomic_load(&pool.processing_task_count) != 0 { - if task, ok := pool_try_and_pop_task(pool); ok { - pool_do_work(pool, &task) - } - - // Safety kick - if len(pool.tasks) != 0 && intrinsics.atomic_load(&pool.processing_task_count) == 0 { - sync.mutex_lock(&pool.mutex) - sync.semaphore_post(&pool.sem_available, len(pool.tasks)) - sync.mutex_unlock(&pool.mutex) - } - - yield() + if len(pool.tasks_done) != 0 { + task = pop_front(&pool.tasks_done) + got_task = true + intrinsics.atomic_sub(&pool.num_done, 1) } + return +} + +// Mostly for internal use. +pool_do_work :: proc(pool: ^Pool, task: Task) { + { + context.allocator = task.allocator + task.procedure(task) + } + + sync.guard(&pool.mutex) + + append(&pool.tasks_done, task) + intrinsics.atomic_add(&pool.num_done, 1) + intrinsics.atomic_sub(&pool.num_outstanding, 1) + intrinsics.atomic_sub(&pool.num_in_processing, 1) +} + +// Process the rest of the tasks, also use this thread for processing, then join +// all the pool threads. +pool_finish :: proc(pool: ^Pool) { + for task in pool_pop_waiting(pool) { + pool_do_work(pool, task) + } pool_join(pool) } diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index b6679bbc2..8452df112 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -7,30 +7,19 @@ import "core:intrinsics" import "core:sync" import "core:sys/unix" +Thread_State :: enum u8 { + Started, + Joined, + Done, +} + // NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. // Also see core/sys/darwin/mach_darwin.odin/semaphore_t. Thread_Os_Specific :: struct #align 16 { unix_thread: unix.pthread_t, // NOTE: very large on Darwin, small on Linux. - - // NOTE: pthread has a proc to query this, but it is marked - // as non-portable ("np") so we do this instead. - done: bool, - - // since libpthread doesn't seem to have a way to create a thread - // in a suspended state, we have it wait on this gate, which we - // signal to start it. - // destroyed after thread is started. - start_gate: sync.Condition, - start_mutex: sync.Mutex, - - // if true, the thread has been started and the start_gate has been destroyed. - started: bool, - - // NOTE: with pthreads, it is undefined behavior for multiple threads - // to call join on the same thread at the same time. - // this value is atomically updated to detect this. - // See the comment in `join`. - already_joined: bool, + cond: sync.Cond, + mutex: sync.Mutex, + flags: bit_set[Thread_State; u8], } // // Creates a thread which will run the given procedure. @@ -38,26 +27,31 @@ Thread_Os_Specific :: struct #align 16 { // _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { __linux_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr { - context = runtime.default_context() t := (^Thread)(t) - sync.condition_wait_for(&t.start_gate) - sync.condition_destroy(&t.start_gate) - sync.mutex_destroy(&t.start_mutex) - t.start_gate = {} - t.start_mutex = {} - context = t.init_context.? or_else runtime.default_context() - + context = runtime.default_context() + + sync.lock(&t.mutex) + t.id = sync.current_thread_id() - 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) - } + for (.Started not_in t.flags) { + sync.wait(&t.cond, &t.mutex) + } + + init_context := t.init_context + context = init_context.? or_else runtime.default_context() + + t.procedure(t) + + intrinsics.atomic_store(&t.flags, t.flags + { .Done }) + + sync.unlock(&t.mutex) + + if init_context == nil && 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 nil } @@ -76,9 +70,6 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^ return nil } thread.creation_allocator = context.allocator - - sync.mutex_init(&thread.start_mutex) - sync.condition_init(&thread.start_gate, &thread.start_mutex) // Set thread priority. policy: i32 @@ -97,65 +88,35 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^ res = unix.pthread_attr_setschedparam(&attrs, ¶ms) assert(res == 0) + thread.procedure = procedure if unix.pthread_create(&thread.unix_thread, &attrs, __linux_thread_entry_proc, thread) != 0 { free(thread, thread.creation_allocator) - - sync.condition_destroy(&thread.start_gate) - sync.mutex_destroy(&thread.start_mutex) return nil } - thread.procedure = procedure - return thread } _start :: proc(t: ^Thread) { - if intrinsics.atomic_xchg(&t.started, true) { - return - } - sync.condition_signal(&t.start_gate) + sync.guard(&t.mutex) + t.flags += { .Started } + sync.signal(&t.cond) } _is_done :: proc(t: ^Thread) -> bool { - return intrinsics.atomic_load(&t.done) + return .Done in intrinsics.atomic_load(&t.flags) } _join :: proc(t: ^Thread) { - if unix.pthread_equal(unix.pthread_self(), t.unix_thread) { - return - } - // if unix.pthread_self().x == t.unix_thread.x do return; + sync.guard(&t.mutex) - // NOTE(tetra): It's apparently UB for multiple threads to join the same thread - // at the same time. - // If someone else already did, spin until the thread dies. - // See note on `already_joined` field. - // TODO(tetra): I'm not sure if we should do this, or panic, since I'm not - // sure it makes sense to need to join from multiple threads? - if intrinsics.atomic_xchg(&t.already_joined, true) { - for { - if intrinsics.atomic_load(&t.done) { - return - } - intrinsics.cpu_relax() - } - } - - // NOTE(tetra): If we're already dead, don't bother calling to pthread_join as that - // will just return 3 (ESRCH). - // We do this instead because I don't know if there is a danger - // that you may join a different thread from the one you called join on, - // if the thread handle is reused. - if intrinsics.atomic_load(&t.done) { + if .Joined in t.flags || unix.pthread_equal(unix.pthread_self(), t.unix_thread) { return } - ret_val: rawptr - _ = unix.pthread_join(t.unix_thread, &ret_val) - if !intrinsics.atomic_load(&t.done) { - panic("thread not done after join") - } + unix.pthread_join(t.unix_thread, nil) + + t.flags += { .Joined } } _join_multiple :: proc(threads: ..^Thread) { @@ -164,14 +125,12 @@ _join_multiple :: proc(threads: ..^Thread) { } } - _destroy :: proc(t: ^Thread) { _join(t) t.unix_thread = {} free(t, t.creation_allocator) } - _terminate :: proc(t: ^Thread, exit_code: int) { // TODO(bill) } diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 428e241d0..68382444c 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -3,13 +3,21 @@ package thread import "core:runtime" -import sync "core:sync/sync2" +import "core:intrinsics" +import "core:sync" import win32 "core:sys/windows" +Thread_State :: enum u8 { + Started, + Joined, + Done, +} + Thread_Os_Specific :: struct { win32_thread: win32.HANDLE, win32_thread_id: win32.DWORD, - done: bool, // see note in `is_done` + mutex: sync.Mutex, + flags: bit_set[Thread_State; u8], } _thread_priority_map := [Thread_Priority]i32{ @@ -26,15 +34,16 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^ context = t.init_context.? or_else runtime.default_context() t.id = sync.current_thread_id() + t.procedure(t) + intrinsics.atomic_store(&t.flags, t.flags + {.Done}) + 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) } } - - sync.atomic_store(&t.done, true) return 0 } @@ -61,23 +70,31 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^ return thread } -_start :: proc(thread: ^Thread) { - win32.ResumeThread(thread.win32_thread) +_start :: proc(t: ^Thread) { + sync.guard(&t.mutex) + t.flags += {.Started} + win32.ResumeThread(t.win32_thread) } -_is_done :: proc(using thread: ^Thread) -> bool { +_is_done :: proc(t: ^Thread) -> bool { // NOTE(tetra, 2019-10-31): Apparently using wait_for_single_object and // checking if it didn't time out immediately, is not good enough, // so we do it this way instead. - return sync.atomic_load(&done) + return .Done in sync.atomic_load(&t.flags) } -_join :: proc(using thread: ^Thread) { - if win32_thread != win32.INVALID_HANDLE { - win32.WaitForSingleObject(win32_thread, win32.INFINITE) - win32.CloseHandle(win32_thread) - win32_thread = win32.INVALID_HANDLE +_join :: proc(t: ^Thread) { + sync.guard(&t.mutex) + + if .Joined in t.flags || t.win32_thread == win32.INVALID_HANDLE { + return } + + win32.WaitForSingleObject(t.win32_thread, win32.INFINITE) + win32.CloseHandle(t.win32_thread) + t.win32_thread = win32.INVALID_HANDLE + + t.flags += {.Joined} } _join_multiple :: proc(threads: ..^Thread) { diff --git a/core/unicode/utf8/utf8string/string.odin b/core/unicode/utf8/utf8string/string.odin index 23e2eefc6..86267defb 100644 --- a/core/unicode/utf8/utf8string/string.odin +++ b/core/unicode/utf8/utf8string/string.odin @@ -16,140 +16,140 @@ String :: struct { } @(private) -_len :: builtin.len; // helper procedure +_len :: builtin.len // helper procedure init :: proc(s: ^String, contents: string) -> ^String { - s.contents = contents; - s.byte_pos = 0; - s.rune_pos = 0; + s.contents = contents + s.byte_pos = 0 + s.rune_pos = 0 for i in 0..<_len(contents) { if contents[i] >= utf8.RUNE_SELF { - s.rune_count = utf8.rune_count_in_string(contents); - _, s.width = utf8.decode_rune_in_string(contents); - s.non_ascii = i; - return s; + s.rune_count = utf8.rune_count_in_string(contents) + _, s.width = utf8.decode_rune_in_string(contents) + s.non_ascii = i + return s } } - s.rune_count = _len(contents); - s.width = 0; - s.non_ascii = _len(contents); - return s; + s.rune_count = _len(contents) + s.width = 0 + s.non_ascii = _len(contents) + return s } to_string :: proc(s: ^String) -> string { - return s.contents; + return s.contents } len :: proc(s: ^String) -> int { - return s.rune_count; + return s.rune_count } is_ascii :: proc(s: ^String) -> bool { - return s.width == 0; + return s.width == 0 } at :: proc(s: ^String, i: int, loc := #caller_location) -> (r: rune) { - runtime.bounds_check_error_loc(loc, i, s.rune_count); + runtime.bounds_check_error_loc(loc, i, s.rune_count) if i < s.non_ascii { - return rune(s.contents[i]); + return rune(s.contents[i]) } switch i { case 0: - r, s.width = utf8.decode_rune_in_string(s.contents); - s.rune_pos = 0; - s.byte_pos = 0; - return; + r, s.width = utf8.decode_rune_in_string(s.contents) + s.rune_pos = 0 + s.byte_pos = 0 + return case s.rune_count-1: - r, s.width = utf8.decode_rune_in_string(s.contents); - s.rune_pos = i; - s.byte_pos = _len(s.contents) - s.width; - return; + r, s.width = utf8.decode_rune_in_string(s.contents) + s.rune_pos = i + s.byte_pos = _len(s.contents) - s.width + return case s.rune_pos-1: - r, s.width = utf8.decode_rune_in_string(s.contents[0:s.byte_pos]); - s.rune_pos = i; - s.byte_pos -= s.width; - return; + r, s.width = utf8.decode_rune_in_string(s.contents[0:s.byte_pos]) + s.rune_pos = i + s.byte_pos -= s.width + return case s.rune_pos+1: - s.rune_pos = i; - s.byte_pos += s.width; - fallthrough; + s.rune_pos = i + s.byte_pos += s.width + fallthrough case s.rune_pos: - r, s.width = utf8.decode_rune_in_string(s.contents[s.byte_pos:]); - return; + r, s.width = utf8.decode_rune_in_string(s.contents[s.byte_pos:]) + return } // Linear scan - scan_forward := true; + scan_forward := true if i < s.rune_pos { if i < (s.rune_pos-s.non_ascii)/2 { - s.byte_pos, s.rune_pos = s.non_ascii, s.non_ascii; + s.byte_pos, s.rune_pos = s.non_ascii, s.non_ascii } else { - scan_forward = false; + scan_forward = false } } else if i-s.rune_pos < (s.rune_count-s.rune_pos)/2 { - // scan_forward = true; + // scan_forward = true } else { - s.byte_pos, s.rune_pos = _len(s.contents), s.rune_count; - scan_forward = false; + s.byte_pos, s.rune_pos = _len(s.contents), s.rune_count + scan_forward = false } if scan_forward { for { - r, s.width = utf8.decode_rune_in_string(s.contents[s.byte_pos:]); + r, s.width = utf8.decode_rune_in_string(s.contents[s.byte_pos:]) if s.rune_pos == i { - return; + return } - s.rune_pos += 1; - s.byte_pos += s.width; + s.rune_pos += 1 + s.byte_pos += s.width } } else { for { - r, s.width = utf8.decode_last_rune_in_string(s.contents[:s.byte_pos]); - s.rune_pos -= 1; - s.byte_pos -= s.width; + r, s.width = utf8.decode_last_rune_in_string(s.contents[:s.byte_pos]) + s.rune_pos -= 1 + s.byte_pos -= s.width if s.rune_pos == i { - return; + return } } } } slice :: proc(s: ^String, i, j: int, loc := #caller_location) -> string { - runtime.slice_expr_error_lo_hi_loc(loc, i, j, s.rune_count); + runtime.slice_expr_error_lo_hi_loc(loc, i, j, s.rune_count) if j < s.non_ascii { - return s.contents[i:j]; + return s.contents[i:j] } if i == j { - return ""; + return "" } - lo, hi: int; + lo, hi: int if i < s.non_ascii { - lo = i; + lo = i } else if i == s.rune_count { - lo = _len(s.contents); + lo = _len(s.contents) } else { - at(s, i, loc); - lo = s.byte_pos; + at(s, i, loc) + lo = s.byte_pos } if j == s.rune_count { - hi = _len(s.contents); + hi = _len(s.contents) } else { - at(s, j, loc); - hi = s.byte_pos; + at(s, j, loc) + hi = s.byte_pos } - return s.contents[lo:hi]; + return s.contents[lo:hi] } diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 7d2082250..6a039e4dd 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -96,7 +96,6 @@ import sort "core:sort" import strconv "core:strconv" import strings "core:strings" import sync "core:sync" -import sync2 "core:sync/sync2" import testing "core:testing" import scanner "core:text/scanner" import thread "core:thread" @@ -104,6 +103,7 @@ import time "core:time" import unicode "core:unicode" import utf8 "core:unicode/utf8" +import utf8string "core:unicode/utf8/utf8string" import utf16 "core:unicode/utf16" main :: proc(){} @@ -186,11 +186,11 @@ _ :: sort _ :: strconv _ :: strings _ :: sync -_ :: sync2 _ :: testing _ :: scanner _ :: thread _ :: time _ :: unicode _ :: utf8 +_ :: utf8string _ :: utf16 \ No newline at end of file diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index 8c2321dae..b3ce3ade9 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -1107,11 +1107,6 @@ prefix_table := [?]string{ } threading_example :: proc() { - if ODIN_OS == .Darwin { - // TODO: Fix threads on darwin/macOS - return - } - fmt.println("\n# threading_example") { // Basic Threads @@ -1152,7 +1147,7 @@ threading_example :: proc() { { // Thread Pool fmt.println("\n## Thread Pool") - task_proc :: proc(t: ^thread.Task) { + task_proc :: proc(t: thread.Task) { index := t.user_index % len(prefix_table) for iteration in 1..=5 { fmt.printf("Worker Task %d is on iteration %d\n", t.user_index, iteration) @@ -1162,7 +1157,7 @@ threading_example :: proc() { } pool: thread.Pool - thread.pool_init(pool=&pool, thread_count=3) + thread.pool_init(pool=&pool, thread_count=3, allocator=context.allocator) defer thread.pool_destroy(&pool) @@ -1171,7 +1166,7 @@ threading_example :: proc() { } thread.pool_start(&pool) - thread.pool_wait_and_process(&pool) + thread.pool_finish(&pool) } } diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 4cd09b8e9..8b8814176 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -304,7 +304,7 @@ bool check_builtin_objc_procedure(CheckerContext *c, Operand *operand, Ast *call } else if (!is_operand_value(self) || !check_is_assignable_to(c, &self, t_objc_id)) { gbString e = expr_to_string(self.expr); gbString t = type_to_string(self.type); - error(self.expr, "'%.*s' expected a type or value derived from intrinsics.objc_object, got '%s' of type %s %d", LIT(builtin_name), e, t, self.type->kind); + error(self.expr, "'%.*s' expected a type or value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t); gb_string_free(t); gb_string_free(e); return false; @@ -379,6 +379,35 @@ bool check_builtin_objc_procedure(CheckerContext *c, Operand *operand, Ast *call } } +bool check_atomic_memory_order_argument(CheckerContext *c, Ast *expr, String const &builtin_name, OdinAtomicMemoryOrder *memory_order_, char const *extra_message = nullptr) { + Operand x = {}; + check_expr_with_type_hint(c, &x, expr, t_atomic_memory_order); + if (x.mode == Addressing_Invalid) { + return false; + } + if (!are_types_identical(x.type, t_atomic_memory_order) || x.mode != Addressing_Constant) { + gbString str = type_to_string(x.type); + if (extra_message) { + error(x.expr, "Expected a constant Atomic_Memory_Order value for the %s of '%.*s', got %s", extra_message, LIT(builtin_name), str); + } else { + error(x.expr, "Expected a constant Atomic_Memory_Order value for '%.*s', got %s", LIT(builtin_name), str); + } + gb_string_free(str); + return false; + } + i64 value = exact_value_to_i64(x.value); + if (value < 0 || value >= OdinAtomicMemoryOrder_COUNT) { + error(x.expr, "Illegal Atomic_Memory_Order value, got %lld", cast(long long)value); + return false; + } + if (memory_order_) { + *memory_order_ = cast(OdinAtomicMemoryOrder)value; + } + + return true; + +} + bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 id, Type *type_hint) { ast_node(ce, CallExpr, call); if (ce->inlining != ProcInlining_none) { @@ -423,6 +452,11 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 // NOTE(bill): The first arg may be a Type, this will be checked case by case break; + case BuiltinProc_atomic_thread_fence: + case BuiltinProc_atomic_signal_fence: + // NOTE(bill): first type will require a type hint + break; + case BuiltinProc_DIRECTIVE: { ast_node(bd, BasicDirective, ce->proc); String name = bd->name.string; @@ -3198,11 +3232,27 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 break; - case BuiltinProc_atomic_fence: - case BuiltinProc_atomic_fence_acq: - case BuiltinProc_atomic_fence_rel: - case BuiltinProc_atomic_fence_acqrel: - operand->mode = Addressing_NoValue; + + case BuiltinProc_atomic_thread_fence: + case BuiltinProc_atomic_signal_fence: + { + OdinAtomicMemoryOrder memory_order = {}; + if (!check_atomic_memory_order_argument(c, ce->args[0], builtin_name, &memory_order)) { + return false; + } + switch (memory_order) { + case OdinAtomicMemoryOrder_acquire: + case OdinAtomicMemoryOrder_release: + case OdinAtomicMemoryOrder_acq_rel: + case OdinAtomicMemoryOrder_seq_cst: + break; + default: + error(ce->args[0], "Illegal memory ordering for '%.*s', got .%s", LIT(builtin_name), OdinAtomicMemoryOrder_strings[memory_order]); + break; + } + + operand->mode = Addressing_NoValue; + } break; case BuiltinProc_volatile_store: @@ -3210,9 +3260,6 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 case BuiltinProc_unaligned_store: /*fallthrough*/ case BuiltinProc_atomic_store: - case BuiltinProc_atomic_store_rel: - case BuiltinProc_atomic_store_relaxed: - case BuiltinProc_atomic_store_unordered: { Type *elem = nullptr; if (!is_type_normal_pointer(operand->type, &elem)) { @@ -3228,14 +3275,40 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 break; } + case BuiltinProc_atomic_store_explicit: + { + Type *elem = nullptr; + if (!is_type_normal_pointer(operand->type, &elem)) { + error(operand->expr, "Expected a pointer for '%.*s'", LIT(builtin_name)); + return false; + } + Operand x = {}; + check_expr_with_type_hint(c, &x, ce->args[1], elem); + check_assignment(c, &x, elem, builtin_name); + + OdinAtomicMemoryOrder memory_order = {}; + if (!check_atomic_memory_order_argument(c, ce->args[2], builtin_name, &memory_order)) { + return false; + } + switch (memory_order) { + case OdinAtomicMemoryOrder_consume: + case OdinAtomicMemoryOrder_acquire: + case OdinAtomicMemoryOrder_acq_rel: + error(ce->args[2], "Illegal memory order .%s for '%.*s'", OdinAtomicMemoryOrder_strings[memory_order], LIT(builtin_name)); + break; + } + + operand->type = nullptr; + operand->mode = Addressing_NoValue; + break; + } + + case BuiltinProc_volatile_load: /*fallthrough*/ case BuiltinProc_unaligned_load: /*fallthrough*/ case BuiltinProc_atomic_load: - case BuiltinProc_atomic_load_acq: - case BuiltinProc_atomic_load_relaxed: - case BuiltinProc_atomic_load_unordered: { Type *elem = nullptr; if (!is_type_normal_pointer(operand->type, &elem)) { @@ -3247,41 +3320,38 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 break; } + case BuiltinProc_atomic_load_explicit: + { + Type *elem = nullptr; + if (!is_type_normal_pointer(operand->type, &elem)) { + error(operand->expr, "Expected a pointer for '%.*s'", LIT(builtin_name)); + return false; + } + + OdinAtomicMemoryOrder memory_order = {}; + if (!check_atomic_memory_order_argument(c, ce->args[1], builtin_name, &memory_order)) { + return false; + } + + switch (memory_order) { + case OdinAtomicMemoryOrder_release: + case OdinAtomicMemoryOrder_acq_rel: + error(ce->args[1], "Illegal memory order .%s for '%.*s'", OdinAtomicMemoryOrder_strings[memory_order], LIT(builtin_name)); + break; + } + + operand->type = elem; + operand->mode = Addressing_Value; + break; + } + case BuiltinProc_atomic_add: - case BuiltinProc_atomic_add_acq: - case BuiltinProc_atomic_add_rel: - case BuiltinProc_atomic_add_acqrel: - case BuiltinProc_atomic_add_relaxed: case BuiltinProc_atomic_sub: - case BuiltinProc_atomic_sub_acq: - case BuiltinProc_atomic_sub_rel: - case BuiltinProc_atomic_sub_acqrel: - case BuiltinProc_atomic_sub_relaxed: case BuiltinProc_atomic_and: - case BuiltinProc_atomic_and_acq: - case BuiltinProc_atomic_and_rel: - case BuiltinProc_atomic_and_acqrel: - case BuiltinProc_atomic_and_relaxed: case BuiltinProc_atomic_nand: - case BuiltinProc_atomic_nand_acq: - case BuiltinProc_atomic_nand_rel: - case BuiltinProc_atomic_nand_acqrel: - case BuiltinProc_atomic_nand_relaxed: case BuiltinProc_atomic_or: - case BuiltinProc_atomic_or_acq: - case BuiltinProc_atomic_or_rel: - case BuiltinProc_atomic_or_acqrel: - case BuiltinProc_atomic_or_relaxed: case BuiltinProc_atomic_xor: - case BuiltinProc_atomic_xor_acq: - case BuiltinProc_atomic_xor_rel: - case BuiltinProc_atomic_xor_acqrel: - case BuiltinProc_atomic_xor_relaxed: - case BuiltinProc_atomic_xchg: - case BuiltinProc_atomic_xchg_acq: - case BuiltinProc_atomic_xchg_rel: - case BuiltinProc_atomic_xchg_acqrel: - case BuiltinProc_atomic_xchg_relaxed: + case BuiltinProc_atomic_exchange: { Type *elem = nullptr; if (!is_type_normal_pointer(operand->type, &elem)) { @@ -3297,25 +3367,35 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 break; } - case BuiltinProc_atomic_cxchg: - case BuiltinProc_atomic_cxchg_acq: - case BuiltinProc_atomic_cxchg_rel: - case BuiltinProc_atomic_cxchg_acqrel: - case BuiltinProc_atomic_cxchg_relaxed: - case BuiltinProc_atomic_cxchg_failrelaxed: - case BuiltinProc_atomic_cxchg_failacq: - case BuiltinProc_atomic_cxchg_acq_failrelaxed: - case BuiltinProc_atomic_cxchg_acqrel_failrelaxed: + case BuiltinProc_atomic_add_explicit: + case BuiltinProc_atomic_sub_explicit: + case BuiltinProc_atomic_and_explicit: + case BuiltinProc_atomic_nand_explicit: + case BuiltinProc_atomic_or_explicit: + case BuiltinProc_atomic_xor_explicit: + case BuiltinProc_atomic_exchange_explicit: + { + Type *elem = nullptr; + if (!is_type_normal_pointer(operand->type, &elem)) { + error(operand->expr, "Expected a pointer for '%.*s'", LIT(builtin_name)); + return false; + } + Operand x = {}; + check_expr_with_type_hint(c, &x, ce->args[1], elem); + check_assignment(c, &x, elem, builtin_name); - case BuiltinProc_atomic_cxchgweak: - case BuiltinProc_atomic_cxchgweak_acq: - case BuiltinProc_atomic_cxchgweak_rel: - case BuiltinProc_atomic_cxchgweak_acqrel: - case BuiltinProc_atomic_cxchgweak_relaxed: - case BuiltinProc_atomic_cxchgweak_failrelaxed: - case BuiltinProc_atomic_cxchgweak_failacq: - case BuiltinProc_atomic_cxchgweak_acq_failrelaxed: - case BuiltinProc_atomic_cxchgweak_acqrel_failrelaxed: + + if (!check_atomic_memory_order_argument(c, ce->args[2], builtin_name, nullptr)) { + return false; + } + + operand->type = elem; + operand->mode = Addressing_Value; + break; + } + + case BuiltinProc_atomic_compare_exchange_strong: + case BuiltinProc_atomic_compare_exchange_weak: { Type *elem = nullptr; if (!is_type_normal_pointer(operand->type, &elem)) { @@ -3333,7 +3413,92 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 operand->type = elem; break; } - break; + + case BuiltinProc_atomic_compare_exchange_strong_explicit: + case BuiltinProc_atomic_compare_exchange_weak_explicit: + { + Type *elem = nullptr; + if (!is_type_normal_pointer(operand->type, &elem)) { + error(operand->expr, "Expected a pointer for '%.*s'", LIT(builtin_name)); + return false; + } + Operand x = {}; + Operand y = {}; + check_expr_with_type_hint(c, &x, ce->args[1], elem); + check_expr_with_type_hint(c, &y, ce->args[2], elem); + check_assignment(c, &x, elem, builtin_name); + check_assignment(c, &y, elem, builtin_name); + + OdinAtomicMemoryOrder success_memory_order = {}; + OdinAtomicMemoryOrder failure_memory_order = {}; + if (!check_atomic_memory_order_argument(c, ce->args[3], builtin_name, &success_memory_order, "success ordering")) { + return false; + } + if (!check_atomic_memory_order_argument(c, ce->args[4], builtin_name, &failure_memory_order, "failure ordering")) { + return false; + } + + bool invalid_combination = false; + + switch (success_memory_order) { + case OdinAtomicMemoryOrder_relaxed: + case OdinAtomicMemoryOrder_release: + if (failure_memory_order != OdinAtomicMemoryOrder_relaxed) { + invalid_combination = true; + } + break; + case OdinAtomicMemoryOrder_consume: + switch (failure_memory_order) { + case OdinAtomicMemoryOrder_relaxed: + case OdinAtomicMemoryOrder_consume: + break; + default: + invalid_combination = true; + break; + } + break; + case OdinAtomicMemoryOrder_acquire: + case OdinAtomicMemoryOrder_acq_rel: + switch (failure_memory_order) { + case OdinAtomicMemoryOrder_relaxed: + case OdinAtomicMemoryOrder_consume: + case OdinAtomicMemoryOrder_acquire: + break; + default: + invalid_combination = true; + break; + } + break; + case OdinAtomicMemoryOrder_seq_cst: + switch (failure_memory_order) { + case OdinAtomicMemoryOrder_relaxed: + case OdinAtomicMemoryOrder_consume: + case OdinAtomicMemoryOrder_acquire: + case OdinAtomicMemoryOrder_seq_cst: + break; + default: + invalid_combination = true; + break; + } + break; + default: + invalid_combination = true; + break; + } + + + if (invalid_combination) { + error(ce->args[3], "Illegal memory order pairing for '%.*s', success = .%s, failure = .%s", + LIT(builtin_name), + OdinAtomicMemoryOrder_strings[success_memory_order], + OdinAtomicMemoryOrder_strings[failure_memory_order] + ); + } + + operand->mode = Addressing_OptionalOk; + operand->type = elem; + break; + } case BuiltinProc_fixed_point_mul: case BuiltinProc_fixed_point_div: @@ -4111,6 +4276,73 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 } + case BuiltinProc_wasm_memory_grow: + { + if (!is_arch_wasm()) { + error(call, "'%.*s' is only allowed on wasm targets", LIT(builtin_name)); + return false; + } + + Operand index = {}; + Operand delta = {}; + check_expr(c, &index, ce->args[0]); if (index.mode == Addressing_Invalid) return false; + check_expr(c, &delta, ce->args[1]); if (delta.mode == Addressing_Invalid) return false; + + convert_to_typed(c, &index, t_uintptr); if (index.mode == Addressing_Invalid) return false; + convert_to_typed(c, &delta, t_uintptr); if (delta.mode == Addressing_Invalid) return false; + + if (!is_operand_value(index) || !check_is_assignable_to(c, &index, t_uintptr)) { + gbString e = expr_to_string(index.expr); + gbString t = type_to_string(index.type); + error(index.expr, "'%.*s' expected a uintptr for the memory index, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + if (!is_operand_value(delta) || !check_is_assignable_to(c, &delta, t_uintptr)) { + gbString e = expr_to_string(delta.expr); + gbString t = type_to_string(delta.type); + error(delta.expr, "'%.*s' expected a uintptr for the memory delta, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + operand->mode = Addressing_Value; + operand->type = t_int; + operand->value = {}; + break; + } + break; + case BuiltinProc_wasm_memory_size: + { + if (!is_arch_wasm()) { + error(call, "'%.*s' is only allowed on wasm targets", LIT(builtin_name)); + return false; + } + + Operand index = {}; + check_expr(c, &index, ce->args[0]); if (index.mode == Addressing_Invalid) return false; + + convert_to_typed(c, &index, t_uintptr); if (index.mode == Addressing_Invalid) return false; + + if (!is_operand_value(index) || !check_is_assignable_to(c, &index, t_uintptr)) { + gbString e = expr_to_string(index.expr); + gbString t = type_to_string(index.type); + error(index.expr, "'%.*s' expected a uintptr for the memory index, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + operand->mode = Addressing_Value; + operand->type = t_int; + operand->value = {}; + break; + } + break; + } return true; diff --git a/src/check_decl.cpp b/src/check_decl.cpp index d4a320f03..5acd56097 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1137,7 +1137,9 @@ void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast *type_expr, ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix); if (is_arch_wasm() && e->Variable.thread_local_model.len != 0) { - error(e->token, "@(thread_local) is not supported for this target platform"); + e->Variable.thread_local_model.len = 0; + // NOTE(bill): ignore this message for the time begin + // error(e->token, "@(thread_local) is not supported for this target platform"); } String context_name = str_lit("variable declaration"); diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 577f3b07c..dcf17af39 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -4872,25 +4872,16 @@ bool is_expr_constant_zero(Ast *expr) { return false; } - -CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { - ast_node(ce, CallExpr, call); - GB_ASSERT(is_type_proc(proc_type)); - proc_type = base_type(proc_type); - TypeProc *pt = &proc_type->Proc; - +isize get_procedure_param_count_excluding_defaults(Type *pt, isize *param_count_) { + GB_ASSERT(pt != nullptr); + GB_ASSERT(pt->kind == Type_Proc); isize param_count = 0; isize param_count_excluding_defaults = 0; - bool variadic = pt->variadic; - bool vari_expand = (ce->ellipsis.pos.line != 0); - i64 score = 0; - bool show_error = show_error_mode == CallArgumentMode_ShowErrors; - - + bool variadic = pt->Proc.variadic; TypeTuple *param_tuple = nullptr; - if (pt->params != nullptr) { - param_tuple = &pt->params->Tuple; + if (pt->Proc.params != nullptr) { + param_tuple = &pt->Proc.params->Tuple; param_count = param_tuple->variables.count; if (variadic) { @@ -4930,6 +4921,31 @@ CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { } } + if (param_count_) *param_count_ = param_count; + return param_count_excluding_defaults; +} + + +CALL_ARGUMENT_CHECKER(check_call_arguments_internal) { + ast_node(ce, CallExpr, call); + GB_ASSERT(is_type_proc(proc_type)); + proc_type = base_type(proc_type); + TypeProc *pt = &proc_type->Proc; + + isize param_count = 0; + isize param_count_excluding_defaults = get_procedure_param_count_excluding_defaults(proc_type, ¶m_count); + bool variadic = pt->variadic; + bool vari_expand = (ce->ellipsis.pos.line != 0); + i64 score = 0; + bool show_error = show_error_mode == CallArgumentMode_ShowErrors; + + + TypeTuple *param_tuple = nullptr; + if (pt->params != nullptr) { + param_tuple = &pt->params->Tuple; + } + + CallArgumentError err = CallArgumentError_None; Type *final_proc_type = proc_type; Entity *gen_entity = nullptr; @@ -5602,7 +5618,37 @@ CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Type if (operand->mode == Addressing_ProcGroup) { check_entity_decl(c, operand->proc_group, nullptr, nullptr); - Array procs = proc_group_entities(c, *operand); + auto procs = proc_group_entities_cloned(c, *operand); + + if (procs.count > 1) { + isize max_arg_count = args.count; + for_array(i, args) { + // NOTE(bill): The only thing that may have multiple values + // will be a call expression (assuming `or_return` and `()` will be stripped) + Ast *arg = strip_or_return_expr(args[i]); + if (arg && arg->kind == Ast_CallExpr) { + max_arg_count = ISIZE_MAX; + break; + } + } + + for (isize proc_index = 0; proc_index < procs.count; /**/) { + Entity *proc = procs[proc_index]; + Type *pt = base_type(proc->type); + if (!(pt != nullptr && is_type_proc(pt))) { + continue; + } + + isize param_count = 0; + isize param_count_excluding_defaults = get_procedure_param_count_excluding_defaults(pt, ¶m_count); + + if (param_count_excluding_defaults > max_arg_count) { + array_unordered_remove(&procs, proc_index); + } else { + proc_index++; + } + } + } if (procs.count == 1) { Ast *ident = operand->expr; @@ -5632,6 +5678,7 @@ CallArgumentData check_call_arguments(CheckerContext *c, Operand *operand, Type return data; } + Entity **lhs = nullptr; isize lhs_count = -1; @@ -8953,6 +9000,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type o->mode = Addressing_Invalid; o->type = t_invalid; + o->value = {ExactValue_Invalid}; switch (node->kind) { default: @@ -9999,8 +10047,11 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) { str = write_expr_to_string(str, st->polymorphic_params, shorthand); str = gb_string_appendc(str, ") "); } - if (st->no_nil) str = gb_string_appendc(str, "#no_nil "); - if (st->maybe) str = gb_string_appendc(str, "#maybe "); + switch (st->kind) { + case UnionType_maybe: str = gb_string_appendc(str, "#maybe "); break; + case UnionType_no_nil: str = gb_string_appendc(str, "#no_nil "); break; + case UnionType_shared_nil: str = gb_string_appendc(str, "#shared_nil "); break; + } if (st->align) { str = gb_string_appendc(str, "#align "); str = write_expr_to_string(str, st->align, shorthand); diff --git a/src/check_type.cpp b/src/check_type.cpp index ecb2c26ea..51f472961 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -675,22 +675,31 @@ void check_union_type(CheckerContext *ctx, Type *union_type, Ast *node, Arraykind == UnionType_shared_nil) { + if (!type_has_nil(t)) { + gbString s = type_to_string(t); + error(node, "Each variant of a union with #shared_nil must have a 'nil' value, got %s", s); + gb_string_free(s); + } + } } } } union_type->Union.variants = slice_from_array(variants); - union_type->Union.no_nil = ut->no_nil; - union_type->Union.maybe = ut->maybe; - if (union_type->Union.no_nil) { + union_type->Union.kind = ut->kind; + switch (ut->kind) { + case UnionType_no_nil: if (variants.count < 2) { error(ut->align, "A union with #no_nil must have at least 2 variants"); } - } - if (union_type->Union.maybe) { + break; + case UnionType_maybe: if (variants.count != 1) { error(ut->align, "A union with #maybe must have at 1 variant, got %lld", cast(long long)variants.count); } + break; } if (ut->align != nullptr) { @@ -1960,20 +1969,6 @@ bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc_type_node, if (params) param_count = params ->Tuple.variables.count; if (results) result_count = results->Tuple.variables.count; - if (param_count > 0) { - for_array(i, params->Tuple.variables) { - Entity *param = params->Tuple.variables[i]; - if (param->kind == Entity_Variable) { - ParameterValue pv = param->Variable.param_value; - if (pv.kind == ParameterValue_Constant && - pv.value.kind == ExactValue_Procedure) { - type->Proc.has_proc_default_values = true; - break; - } - } - } - } - if (result_count > 0) { Entity *first = results->Tuple.variables[0]; type->Proc.has_named_results = first->token.string != ""; diff --git a/src/checker.cpp b/src/checker.cpp index ea300afd9..1bb786ea1 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -829,15 +829,16 @@ struct GlobalEnumValue { i64 value; }; -Slice add_global_enum_type(String const &type_name, GlobalEnumValue *values, isize value_count) { +Slice add_global_enum_type(String const &type_name, GlobalEnumValue *values, isize value_count, Type **enum_type_ = nullptr) { Scope *scope = create_scope(nullptr, builtin_pkg->scope); - Entity *e = alloc_entity_type_name(scope, make_token_ident(type_name), nullptr, EntityState_Resolved); + Entity *entity = alloc_entity_type_name(scope, make_token_ident(type_name), nullptr, EntityState_Resolved); Type *enum_type = alloc_type_enum(); - Type *named_type = alloc_type_named(type_name, enum_type, e); + Type *named_type = alloc_type_named(type_name, enum_type, entity); set_base_type(named_type, enum_type); enum_type->Enum.base_type = t_int; enum_type->Enum.scope = scope; + entity->type = named_type; auto fields = array_make(permanent_allocator(), value_count); for (isize i = 0; i < value_count; i++) { @@ -858,6 +859,9 @@ Slice add_global_enum_type(String const &type_name, GlobalEnumValue *v enum_type->Enum.min_value = &enum_type->Enum.fields[enum_type->Enum.min_value_index]->Constant.value; enum_type->Enum.max_value = &enum_type->Enum.fields[enum_type->Enum.max_value_index]->Constant.value; + + if (enum_type_) *enum_type_ = named_type; + return slice_from_array(fields); } void add_global_enum_constant(Slice const &fields, char const *name, i64 value) { @@ -986,6 +990,21 @@ void init_universal(void) { add_global_enum_constant(fields, "ODIN_ERROR_POS_STYLE", build_context.ODIN_ERROR_POS_STYLE); } + { + GlobalEnumValue values[OdinAtomicMemoryOrder_COUNT] = { + {OdinAtomicMemoryOrder_strings[OdinAtomicMemoryOrder_relaxed], OdinAtomicMemoryOrder_relaxed}, + {OdinAtomicMemoryOrder_strings[OdinAtomicMemoryOrder_consume], OdinAtomicMemoryOrder_consume}, + {OdinAtomicMemoryOrder_strings[OdinAtomicMemoryOrder_acquire], OdinAtomicMemoryOrder_acquire}, + {OdinAtomicMemoryOrder_strings[OdinAtomicMemoryOrder_release], OdinAtomicMemoryOrder_release}, + {OdinAtomicMemoryOrder_strings[OdinAtomicMemoryOrder_acq_rel], OdinAtomicMemoryOrder_acq_rel}, + {OdinAtomicMemoryOrder_strings[OdinAtomicMemoryOrder_seq_cst], OdinAtomicMemoryOrder_seq_cst}, + }; + + add_global_enum_type(str_lit("Atomic_Memory_Order"), values, gb_count_of(values), &t_atomic_memory_order); + GB_ASSERT(t_atomic_memory_order->kind == Type_Named); + scope_insert(intrinsics_pkg->scope, t_atomic_memory_order->Named.type_name); + } + add_global_bool_constant("ODIN_DEBUG", bc->ODIN_DEBUG); add_global_bool_constant("ODIN_DISABLE_ASSERT", bc->ODIN_DISABLE_ASSERT); @@ -2657,6 +2676,15 @@ Array proc_group_entities(CheckerContext *c, Operand o) { return procs; } +Array proc_group_entities_cloned(CheckerContext *c, Operand o) { + auto entities = proc_group_entities(c, o); + if (entities.count == 0) { + return {}; + } + return array_clone(permanent_allocator(), entities); +} + + void init_core_type_info(Checker *c) { diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index cba952ddf..c6b0afee0 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -86,77 +86,30 @@ enum BuiltinProcId { BuiltinProc_prefetch_write_instruction, BuiltinProc_prefetch_write_data, - BuiltinProc_atomic_fence, - BuiltinProc_atomic_fence_acq, - BuiltinProc_atomic_fence_rel, - BuiltinProc_atomic_fence_acqrel, - + BuiltinProc_atomic_thread_fence, + BuiltinProc_atomic_signal_fence, BuiltinProc_atomic_store, - BuiltinProc_atomic_store_rel, - BuiltinProc_atomic_store_relaxed, - BuiltinProc_atomic_store_unordered, - + BuiltinProc_atomic_store_explicit, BuiltinProc_atomic_load, - BuiltinProc_atomic_load_acq, - BuiltinProc_atomic_load_relaxed, - BuiltinProc_atomic_load_unordered, - + BuiltinProc_atomic_load_explicit, BuiltinProc_atomic_add, - BuiltinProc_atomic_add_acq, - BuiltinProc_atomic_add_rel, - BuiltinProc_atomic_add_acqrel, - BuiltinProc_atomic_add_relaxed, + BuiltinProc_atomic_add_explicit, BuiltinProc_atomic_sub, - BuiltinProc_atomic_sub_acq, - BuiltinProc_atomic_sub_rel, - BuiltinProc_atomic_sub_acqrel, - BuiltinProc_atomic_sub_relaxed, + BuiltinProc_atomic_sub_explicit, BuiltinProc_atomic_and, - BuiltinProc_atomic_and_acq, - BuiltinProc_atomic_and_rel, - BuiltinProc_atomic_and_acqrel, - BuiltinProc_atomic_and_relaxed, + BuiltinProc_atomic_and_explicit, BuiltinProc_atomic_nand, - BuiltinProc_atomic_nand_acq, - BuiltinProc_atomic_nand_rel, - BuiltinProc_atomic_nand_acqrel, - BuiltinProc_atomic_nand_relaxed, + BuiltinProc_atomic_nand_explicit, BuiltinProc_atomic_or, - BuiltinProc_atomic_or_acq, - BuiltinProc_atomic_or_rel, - BuiltinProc_atomic_or_acqrel, - BuiltinProc_atomic_or_relaxed, + BuiltinProc_atomic_or_explicit, BuiltinProc_atomic_xor, - BuiltinProc_atomic_xor_acq, - BuiltinProc_atomic_xor_rel, - BuiltinProc_atomic_xor_acqrel, - BuiltinProc_atomic_xor_relaxed, - - BuiltinProc_atomic_xchg, - BuiltinProc_atomic_xchg_acq, - BuiltinProc_atomic_xchg_rel, - BuiltinProc_atomic_xchg_acqrel, - BuiltinProc_atomic_xchg_relaxed, - - BuiltinProc_atomic_cxchg, - BuiltinProc_atomic_cxchg_acq, - BuiltinProc_atomic_cxchg_rel, - BuiltinProc_atomic_cxchg_acqrel, - BuiltinProc_atomic_cxchg_relaxed, - BuiltinProc_atomic_cxchg_failrelaxed, - BuiltinProc_atomic_cxchg_failacq, - BuiltinProc_atomic_cxchg_acq_failrelaxed, - BuiltinProc_atomic_cxchg_acqrel_failrelaxed, - - BuiltinProc_atomic_cxchgweak, - BuiltinProc_atomic_cxchgweak_acq, - BuiltinProc_atomic_cxchgweak_rel, - BuiltinProc_atomic_cxchgweak_acqrel, - BuiltinProc_atomic_cxchgweak_relaxed, - BuiltinProc_atomic_cxchgweak_failrelaxed, - BuiltinProc_atomic_cxchgweak_failacq, - BuiltinProc_atomic_cxchgweak_acq_failrelaxed, - BuiltinProc_atomic_cxchgweak_acqrel_failrelaxed, + BuiltinProc_atomic_xor_explicit, + BuiltinProc_atomic_exchange, + BuiltinProc_atomic_exchange_explicit, + BuiltinProc_atomic_compare_exchange_strong, + BuiltinProc_atomic_compare_exchange_strong_explicit, + BuiltinProc_atomic_compare_exchange_weak, + BuiltinProc_atomic_compare_exchange_weak_explicit, BuiltinProc_fixed_point_mul, BuiltinProc_fixed_point_div, @@ -260,6 +213,8 @@ BuiltinProc__type_end, BuiltinProc_constant_utf16_cstring, + BuiltinProc_wasm_memory_grow, + BuiltinProc_wasm_memory_size, BuiltinProc_COUNT, }; @@ -350,78 +305,30 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("prefetch_write_instruction"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("prefetch_write_data"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_fence"), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_fence_acq"), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_fence_rel"), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_fence_acqrel"), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - - {STR_LIT("atomic_store"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_store_rel"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_store_relaxed"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_store_unordered"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - - {STR_LIT("atomic_load"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_load_acq"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_load_relaxed"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_load_unordered"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - - {STR_LIT("atomic_add"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_add_acq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_add_rel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_add_acqrel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_add_relaxed"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_sub_acq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_sub_rel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_sub_acqrel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_sub_relaxed"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_and"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_and_acq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_and_rel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_and_acqrel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_and_relaxed"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_nand"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_nand_acq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_nand_rel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_nand_acqrel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_nand_relaxed"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_or"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_or_acq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_or_rel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_or_acqrel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_or_relaxed"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xor"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xor_acq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xor_rel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xor_acqrel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xor_relaxed"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - - {STR_LIT("atomic_xchg"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xchg_acq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xchg_rel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xchg_acqrel"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xchg_relaxed"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - - {STR_LIT("atomic_cxchg"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchg_acq"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchg_rel"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchg_acqrel"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchg_relaxed"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchg_failrelaxed"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchg_failacq"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchg_acq_failrelaxed"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchg_acqrel_failrelaxed"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - - {STR_LIT("atomic_cxchgweak"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchgweak_acq"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchgweak_rel"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchgweak_acqrel"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchgweak_relaxed"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchgweak_failrelaxed"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchgweak_failacq"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchgweak_acq_failrelaxed"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_cxchgweak_acqrel_failrelaxed"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - + {STR_LIT("atomic_thread_fence"), 1, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_signal_fence"), 1, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_store"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_store_explicit"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_load"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_load_explicit"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_add"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_add_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_sub_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_and"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_and_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_nand"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_nand_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_or"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_or_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_xor"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_xor_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_exchange"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_exchange_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_compare_exchange_strong"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_compare_exchange_strong_explicit"), 5, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_compare_exchange_weak"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_compare_exchange_weak_explicit"), 5, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("fixed_point_mul"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("fixed_point_div"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -523,4 +430,6 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("wasm_memory_grow"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("wasm_memory_size"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, }; diff --git a/src/docs_format.cpp b/src/docs_format.cpp index 7ce93d2bf..ee32d0e05 100644 --- a/src/docs_format.cpp +++ b/src/docs_format.cpp @@ -99,6 +99,7 @@ enum OdinDocTypeFlag_Union : u32 { OdinDocTypeFlag_Union_polymorphic = 1<<0, OdinDocTypeFlag_Union_no_nil = 1<<1, OdinDocTypeFlag_Union_maybe = 1<<2, + OdinDocTypeFlag_Union_shared_nil = 1<<3, }; enum OdinDocTypeFlag_Proc : u32 { diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 2c5186c39..0ad10ac49 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -619,9 +619,11 @@ OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { case Type_Union: doc_type.kind = OdinDocType_Union; if (type->Union.is_polymorphic) { doc_type.flags |= OdinDocTypeFlag_Union_polymorphic; } - if (type->Union.no_nil) { doc_type.flags |= OdinDocTypeFlag_Union_no_nil; } - if (type->Union.maybe) { doc_type.flags |= OdinDocTypeFlag_Union_maybe; } - + switch (type->Union.kind) { + case UnionType_maybe: doc_type.flags |= OdinDocTypeFlag_Union_maybe; break; + case UnionType_no_nil: doc_type.flags |= OdinDocTypeFlag_Union_no_nil; break; + case UnionType_shared_nil: doc_type.flags |= OdinDocTypeFlag_Union_shared_nil; break; + } { auto variants = array_make(heap_allocator(), type->Union.variants.count); defer (array_free(&variants)); diff --git a/src/entity.cpp b/src/entity.cpp index f5720293f..17fa884e1 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -115,6 +115,16 @@ struct ParameterValue { }; }; +bool has_parameter_value(ParameterValue const ¶m_value) { + if (param_value.kind != ParameterValue_Invalid) { + return true; + } + if (param_value.original_ast_expr != nullptr) { + return true; + } + return false; +} + enum EntityConstantFlags : u32 { EntityConstantFlag_ImplicitEnumValue = 1<<0, }; diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 4294747b9..133df4d41 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -3028,7 +3028,7 @@ lbValue lb_build_expr(lbProcedure *p, Ast *expr) { 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_build_cond(p, te->cond, then, else_); lb_start_block(p, then); Type *type = default_type(type_of_expr(expr)); @@ -4646,7 +4646,7 @@ lbAddr lb_build_addr(lbProcedure *p, Ast *expr) { 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_build_cond(p, te->cond, then, else_); lb_start_block(p, then); Type *ptr_type = alloc_type_pointer(default_type(type_of_expr(expr))); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 1c98aa77f..330059622 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -1176,10 +1176,35 @@ void lb_emit_store_union_variant_tag(lbProcedure *p, lbValue parent, Type *varia } void lb_emit_store_union_variant(lbProcedure *p, lbValue parent, lbValue variant, Type *variant_type) { - lbValue underlying = lb_emit_conv(p, parent, alloc_type_pointer(variant_type)); + Type *pt = base_type(type_deref(parent.type)); + GB_ASSERT(pt->kind == Type_Union); + if (pt->Union.kind == UnionType_shared_nil) { + lbBlock *if_nil = lb_create_block(p, "shared_nil.if_nil"); + lbBlock *if_not_nil = lb_create_block(p, "shared_nil.if_not_nil"); + lbBlock *done = lb_create_block(p, "shared_nil.done"); - lb_emit_store(p, underlying, variant); - lb_emit_store_union_variant_tag(p, parent, variant_type); + lbValue cond_is_nil = lb_emit_comp_against_nil(p, Token_CmpEq, variant); + lb_emit_if(p, cond_is_nil, if_nil, if_not_nil); + + lb_start_block(p, if_nil); + lb_emit_store(p, parent, lb_const_nil(p->module, type_deref(parent.type))); + lb_emit_jump(p, done); + + lb_start_block(p, if_not_nil); + lbValue underlying = lb_emit_conv(p, parent, alloc_type_pointer(variant_type)); + lb_emit_store(p, underlying, variant); + lb_emit_store_union_variant_tag(p, parent, variant_type); + lb_emit_jump(p, done); + + lb_start_block(p, done); + + + } else { + lbValue underlying = lb_emit_conv(p, parent, alloc_type_pointer(variant_type)); + + lb_emit_store(p, underlying, variant); + lb_emit_store_union_variant_tag(p, parent, variant_type); + } } @@ -2312,7 +2337,7 @@ LLVMValueRef lb_find_or_add_entity_string_ptr(lbModule *m, String const &str) { LLVMValueRef global_data = LLVMAddGlobal(m->mod, LLVMTypeOf(data), name); LLVMSetInitializer(global_data, data); - LLVMSetLinkage(global_data, LLVMInternalLinkage); + LLVMSetLinkage(global_data, LLVMPrivateLinkage); LLVMValueRef ptr = LLVMConstInBoundsGEP(global_data, indices, 2); string_map_set(&m->const_strings, key, ptr); @@ -2354,7 +2379,7 @@ lbValue lb_find_or_add_entity_string_byte_slice(lbModule *m, String const &str) } LLVMValueRef global_data = LLVMAddGlobal(m->mod, LLVMTypeOf(data), name); LLVMSetInitializer(global_data, data); - LLVMSetLinkage(global_data, LLVMInternalLinkage); + LLVMSetLinkage(global_data, LLVMPrivateLinkage); LLVMValueRef ptr = nullptr; if (str.len != 0) { @@ -2590,7 +2615,7 @@ lbValue lb_generate_global_array(lbModule *m, Type *elem_type, i64 count, String g.value = LLVMAddGlobal(m->mod, lb_type(m, t), text); g.type = alloc_type_pointer(t); LLVMSetInitializer(g.value, LLVMConstNull(lb_type(m, t))); - LLVMSetLinkage(g.value, LLVMInternalLinkage); + LLVMSetLinkage(g.value, LLVMPrivateLinkage); string_map_set(&m->members, s, g); return g; } @@ -2602,6 +2627,9 @@ lbValue lb_build_cond(lbProcedure *p, Ast *cond, lbBlock *true_block, lbBlock *f GB_ASSERT(true_block != nullptr); GB_ASSERT(false_block != nullptr); + // Use to signal not to do compile time short circuit for consts + lbValue no_comptime_short_circuit = {}; + switch (cond->kind) { case_ast_node(pe, ParenExpr, cond); return lb_build_cond(p, pe->expr, true_block, false_block); @@ -2609,7 +2637,11 @@ lbValue lb_build_cond(lbProcedure *p, Ast *cond, lbBlock *true_block, lbBlock *f case_ast_node(ue, UnaryExpr, cond); if (ue->op.kind == Token_Not) { - return lb_build_cond(p, ue->expr, false_block, true_block); + lbValue cond_val = lb_build_cond(p, ue->expr, false_block, true_block); + if (cond_val.value && LLVMIsConstant(cond_val.value)) { + return lb_const_bool(p->module, cond_val.type, LLVMConstIntGetZExtValue(cond_val.value) == 0); + } + return no_comptime_short_circuit; } case_end; @@ -2618,12 +2650,14 @@ lbValue lb_build_cond(lbProcedure *p, Ast *cond, lbBlock *true_block, lbBlock *f lbBlock *block = lb_create_block(p, "cmp.and"); lb_build_cond(p, be->left, block, false_block); lb_start_block(p, block); - return lb_build_cond(p, be->right, true_block, false_block); + lb_build_cond(p, be->right, true_block, false_block); + return no_comptime_short_circuit; } else if (be->op.kind == Token_CmpOr) { lbBlock *block = lb_create_block(p, "cmp.or"); lb_build_cond(p, be->left, true_block, block); lb_start_block(p, block); - return lb_build_cond(p, be->right, true_block, false_block); + lb_build_cond(p, be->right, true_block, false_block); + return no_comptime_short_circuit; } case_end; } diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 27a6ec209..4de2af9d8 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1372,12 +1372,20 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, if (build_context.metrics.arch == TargetArch_i386 || build_context.metrics.arch == TargetArch_amd64) { LLVMTypeRef func_type = LLVMFunctionType(LLVMVoidTypeInContext(p->module->ctx), nullptr, 0, false); - LLVMValueRef the_asm = llvm_get_inline_asm(func_type, str_lit("pause"), {}); + LLVMValueRef the_asm = llvm_get_inline_asm(func_type, str_lit("pause"), {}, true); GB_ASSERT(the_asm != nullptr); LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, ""); } else if (build_context.metrics.arch == TargetArch_arm64) { LLVMTypeRef func_type = LLVMFunctionType(LLVMVoidTypeInContext(p->module->ctx), nullptr, 0, false); - LLVMValueRef the_asm = llvm_get_inline_asm(func_type, str_lit("yield"), {}); + // NOTE(bill, 2022-03-30): `isb` appears to a better option that `yield` + // See: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8258604 + LLVMValueRef the_asm = llvm_get_inline_asm(func_type, str_lit("isb"), {}, true); + GB_ASSERT(the_asm != nullptr); + LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, ""); + } else { + // NOTE: default to something to prevent optimization + LLVMTypeRef func_type = LLVMFunctionType(LLVMVoidTypeInContext(p->module->ctx), nullptr, 0, false); + LLVMValueRef the_asm = llvm_get_inline_asm(func_type, str_lit(""), {}, true); GB_ASSERT(the_asm != nullptr); LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, ""); } @@ -1598,36 +1606,26 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, } - - case BuiltinProc_atomic_fence: - LLVMBuildFence(p->builder, LLVMAtomicOrderingSequentiallyConsistent, false, ""); + // TODO(bill): Which is correct? + case BuiltinProc_atomic_thread_fence: + LLVMBuildFence(p->builder, llvm_atomic_ordering_from_odin(ce->args[0]), false, ""); return {}; - case BuiltinProc_atomic_fence_acq: - LLVMBuildFence(p->builder, LLVMAtomicOrderingAcquire, false, ""); - return {}; - case BuiltinProc_atomic_fence_rel: - LLVMBuildFence(p->builder, LLVMAtomicOrderingRelease, false, ""); - return {}; - case BuiltinProc_atomic_fence_acqrel: - LLVMBuildFence(p->builder, LLVMAtomicOrderingAcquireRelease, false, ""); + case BuiltinProc_atomic_signal_fence: + LLVMBuildFence(p->builder, llvm_atomic_ordering_from_odin(ce->args[0]), true, ""); return {}; case BuiltinProc_volatile_store: case BuiltinProc_atomic_store: - case BuiltinProc_atomic_store_rel: - case BuiltinProc_atomic_store_relaxed: - case BuiltinProc_atomic_store_unordered: { + case BuiltinProc_atomic_store_explicit: { lbValue dst = lb_build_expr(p, ce->args[0]); lbValue val = lb_build_expr(p, ce->args[1]); val = lb_emit_conv(p, val, type_deref(dst.type)); LLVMValueRef instr = LLVMBuildStore(p->builder, val.value, dst.value); switch (id) { - case BuiltinProc_volatile_store: LLVMSetVolatile(instr, true); break; - case BuiltinProc_atomic_store: LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); break; - case BuiltinProc_atomic_store_rel: LLVMSetOrdering(instr, LLVMAtomicOrderingRelease); break; - case BuiltinProc_atomic_store_relaxed: LLVMSetOrdering(instr, LLVMAtomicOrderingMonotonic); break; - case BuiltinProc_atomic_store_unordered: LLVMSetOrdering(instr, LLVMAtomicOrderingUnordered); break; + case BuiltinProc_volatile_store: LLVMSetVolatile(instr, true); break; + case BuiltinProc_atomic_store: LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); break; + case BuiltinProc_atomic_store_explicit: LLVMSetOrdering(instr, llvm_atomic_ordering_from_odin(ce->args[2])); break; } LLVMSetAlignment(instr, cast(unsigned)type_align_of(type_deref(dst.type))); @@ -1637,18 +1635,14 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, case BuiltinProc_volatile_load: case BuiltinProc_atomic_load: - case BuiltinProc_atomic_load_acq: - case BuiltinProc_atomic_load_relaxed: - case BuiltinProc_atomic_load_unordered: { + case BuiltinProc_atomic_load_explicit: { lbValue dst = lb_build_expr(p, ce->args[0]); LLVMValueRef instr = LLVMBuildLoad(p->builder, dst.value, ""); switch (id) { - case BuiltinProc_volatile_load: LLVMSetVolatile(instr, true); break; - case BuiltinProc_atomic_load: LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); break; - case BuiltinProc_atomic_load_acq: LLVMSetOrdering(instr, LLVMAtomicOrderingAcquire); break; - case BuiltinProc_atomic_load_relaxed: LLVMSetOrdering(instr, LLVMAtomicOrderingMonotonic); break; - case BuiltinProc_atomic_load_unordered: LLVMSetOrdering(instr, LLVMAtomicOrderingUnordered); break; + case BuiltinProc_volatile_load: LLVMSetVolatile(instr, true); break; + case BuiltinProc_atomic_load: LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); break; + case BuiltinProc_atomic_load_explicit: LLVMSetOrdering(instr, llvm_atomic_ordering_from_odin(ce->args[1])); break; } LLVMSetAlignment(instr, cast(unsigned)type_align_of(type_deref(dst.type))); @@ -1678,40 +1672,19 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, } case BuiltinProc_atomic_add: - case BuiltinProc_atomic_add_acq: - case BuiltinProc_atomic_add_rel: - case BuiltinProc_atomic_add_acqrel: - case BuiltinProc_atomic_add_relaxed: case BuiltinProc_atomic_sub: - case BuiltinProc_atomic_sub_acq: - case BuiltinProc_atomic_sub_rel: - case BuiltinProc_atomic_sub_acqrel: - case BuiltinProc_atomic_sub_relaxed: case BuiltinProc_atomic_and: - case BuiltinProc_atomic_and_acq: - case BuiltinProc_atomic_and_rel: - case BuiltinProc_atomic_and_acqrel: - case BuiltinProc_atomic_and_relaxed: case BuiltinProc_atomic_nand: - case BuiltinProc_atomic_nand_acq: - case BuiltinProc_atomic_nand_rel: - case BuiltinProc_atomic_nand_acqrel: - case BuiltinProc_atomic_nand_relaxed: case BuiltinProc_atomic_or: - case BuiltinProc_atomic_or_acq: - case BuiltinProc_atomic_or_rel: - case BuiltinProc_atomic_or_acqrel: - case BuiltinProc_atomic_or_relaxed: case BuiltinProc_atomic_xor: - case BuiltinProc_atomic_xor_acq: - case BuiltinProc_atomic_xor_rel: - case BuiltinProc_atomic_xor_acqrel: - case BuiltinProc_atomic_xor_relaxed: - case BuiltinProc_atomic_xchg: - case BuiltinProc_atomic_xchg_acq: - case BuiltinProc_atomic_xchg_rel: - case BuiltinProc_atomic_xchg_acqrel: - case BuiltinProc_atomic_xchg_relaxed: { + case BuiltinProc_atomic_exchange: + case BuiltinProc_atomic_add_explicit: + case BuiltinProc_atomic_sub_explicit: + case BuiltinProc_atomic_and_explicit: + case BuiltinProc_atomic_nand_explicit: + case BuiltinProc_atomic_or_explicit: + case BuiltinProc_atomic_xor_explicit: + case BuiltinProc_atomic_exchange_explicit: { lbValue dst = lb_build_expr(p, ce->args[0]); lbValue val = lb_build_expr(p, ce->args[1]); val = lb_emit_conv(p, val, type_deref(dst.type)); @@ -1720,41 +1693,20 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, LLVMAtomicOrdering ordering = {}; switch (id) { - case BuiltinProc_atomic_add: op = LLVMAtomicRMWBinOpAdd; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; - case BuiltinProc_atomic_add_acq: op = LLVMAtomicRMWBinOpAdd; ordering = LLVMAtomicOrderingAcquire; break; - case BuiltinProc_atomic_add_rel: op = LLVMAtomicRMWBinOpAdd; ordering = LLVMAtomicOrderingRelease; break; - case BuiltinProc_atomic_add_acqrel: op = LLVMAtomicRMWBinOpAdd; ordering = LLVMAtomicOrderingAcquireRelease; break; - case BuiltinProc_atomic_add_relaxed: op = LLVMAtomicRMWBinOpAdd; ordering = LLVMAtomicOrderingMonotonic; break; - case BuiltinProc_atomic_sub: op = LLVMAtomicRMWBinOpSub; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; - case BuiltinProc_atomic_sub_acq: op = LLVMAtomicRMWBinOpSub; ordering = LLVMAtomicOrderingAcquire; break; - case BuiltinProc_atomic_sub_rel: op = LLVMAtomicRMWBinOpSub; ordering = LLVMAtomicOrderingRelease; break; - case BuiltinProc_atomic_sub_acqrel: op = LLVMAtomicRMWBinOpSub; ordering = LLVMAtomicOrderingAcquireRelease; break; - case BuiltinProc_atomic_sub_relaxed: op = LLVMAtomicRMWBinOpSub; ordering = LLVMAtomicOrderingMonotonic; break; - case BuiltinProc_atomic_and: op = LLVMAtomicRMWBinOpAnd; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; - case BuiltinProc_atomic_and_acq: op = LLVMAtomicRMWBinOpAnd; ordering = LLVMAtomicOrderingAcquire; break; - case BuiltinProc_atomic_and_rel: op = LLVMAtomicRMWBinOpAnd; ordering = LLVMAtomicOrderingRelease; break; - case BuiltinProc_atomic_and_acqrel: op = LLVMAtomicRMWBinOpAnd; ordering = LLVMAtomicOrderingAcquireRelease; break; - case BuiltinProc_atomic_and_relaxed: op = LLVMAtomicRMWBinOpAnd; ordering = LLVMAtomicOrderingMonotonic; break; - case BuiltinProc_atomic_nand: op = LLVMAtomicRMWBinOpNand; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; - case BuiltinProc_atomic_nand_acq: op = LLVMAtomicRMWBinOpNand; ordering = LLVMAtomicOrderingAcquire; break; - case BuiltinProc_atomic_nand_rel: op = LLVMAtomicRMWBinOpNand; ordering = LLVMAtomicOrderingRelease; break; - case BuiltinProc_atomic_nand_acqrel: op = LLVMAtomicRMWBinOpNand; ordering = LLVMAtomicOrderingAcquireRelease; break; - case BuiltinProc_atomic_nand_relaxed: op = LLVMAtomicRMWBinOpNand; ordering = LLVMAtomicOrderingMonotonic; break; - case BuiltinProc_atomic_or: op = LLVMAtomicRMWBinOpOr; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; - case BuiltinProc_atomic_or_acq: op = LLVMAtomicRMWBinOpOr; ordering = LLVMAtomicOrderingAcquire; break; - case BuiltinProc_atomic_or_rel: op = LLVMAtomicRMWBinOpOr; ordering = LLVMAtomicOrderingRelease; break; - case BuiltinProc_atomic_or_acqrel: op = LLVMAtomicRMWBinOpOr; ordering = LLVMAtomicOrderingAcquireRelease; break; - case BuiltinProc_atomic_or_relaxed: op = LLVMAtomicRMWBinOpOr; ordering = LLVMAtomicOrderingMonotonic; break; - case BuiltinProc_atomic_xor: op = LLVMAtomicRMWBinOpXor; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; - case BuiltinProc_atomic_xor_acq: op = LLVMAtomicRMWBinOpXor; ordering = LLVMAtomicOrderingAcquire; break; - case BuiltinProc_atomic_xor_rel: op = LLVMAtomicRMWBinOpXor; ordering = LLVMAtomicOrderingRelease; break; - case BuiltinProc_atomic_xor_acqrel: op = LLVMAtomicRMWBinOpXor; ordering = LLVMAtomicOrderingAcquireRelease; break; - case BuiltinProc_atomic_xor_relaxed: op = LLVMAtomicRMWBinOpXor; ordering = LLVMAtomicOrderingMonotonic; break; - case BuiltinProc_atomic_xchg: op = LLVMAtomicRMWBinOpXchg; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; - case BuiltinProc_atomic_xchg_acq: op = LLVMAtomicRMWBinOpXchg; ordering = LLVMAtomicOrderingAcquire; break; - case BuiltinProc_atomic_xchg_rel: op = LLVMAtomicRMWBinOpXchg; ordering = LLVMAtomicOrderingRelease; break; - case BuiltinProc_atomic_xchg_acqrel: op = LLVMAtomicRMWBinOpXchg; ordering = LLVMAtomicOrderingAcquireRelease; break; - case BuiltinProc_atomic_xchg_relaxed: op = LLVMAtomicRMWBinOpXchg; ordering = LLVMAtomicOrderingMonotonic; break; + case BuiltinProc_atomic_add: op = LLVMAtomicRMWBinOpAdd; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; + case BuiltinProc_atomic_sub: op = LLVMAtomicRMWBinOpSub; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; + case BuiltinProc_atomic_and: op = LLVMAtomicRMWBinOpAnd; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; + case BuiltinProc_atomic_nand: op = LLVMAtomicRMWBinOpNand; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; + case BuiltinProc_atomic_or: op = LLVMAtomicRMWBinOpOr; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; + case BuiltinProc_atomic_xor: op = LLVMAtomicRMWBinOpXor; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; + case BuiltinProc_atomic_exchange: op = LLVMAtomicRMWBinOpXchg; ordering = LLVMAtomicOrderingSequentiallyConsistent; break; + case BuiltinProc_atomic_add_explicit: op = LLVMAtomicRMWBinOpAdd; ordering = llvm_atomic_ordering_from_odin(ce->args[2]); break; + case BuiltinProc_atomic_sub_explicit: op = LLVMAtomicRMWBinOpSub; ordering = llvm_atomic_ordering_from_odin(ce->args[2]); break; + case BuiltinProc_atomic_and_explicit: op = LLVMAtomicRMWBinOpAnd; ordering = llvm_atomic_ordering_from_odin(ce->args[2]); break; + case BuiltinProc_atomic_nand_explicit: op = LLVMAtomicRMWBinOpNand; ordering = llvm_atomic_ordering_from_odin(ce->args[2]); break; + case BuiltinProc_atomic_or_explicit: op = LLVMAtomicRMWBinOpOr; ordering = llvm_atomic_ordering_from_odin(ce->args[2]); break; + case BuiltinProc_atomic_xor_explicit: op = LLVMAtomicRMWBinOpXor; ordering = llvm_atomic_ordering_from_odin(ce->args[2]); break; + case BuiltinProc_atomic_exchange_explicit: op = LLVMAtomicRMWBinOpXchg; ordering = llvm_atomic_ordering_from_odin(ce->args[2]); break; } lbValue res = {}; @@ -1763,24 +1715,10 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, return res; } - case BuiltinProc_atomic_cxchg: - case BuiltinProc_atomic_cxchg_acq: - case BuiltinProc_atomic_cxchg_rel: - case BuiltinProc_atomic_cxchg_acqrel: - case BuiltinProc_atomic_cxchg_relaxed: - case BuiltinProc_atomic_cxchg_failrelaxed: - case BuiltinProc_atomic_cxchg_failacq: - case BuiltinProc_atomic_cxchg_acq_failrelaxed: - case BuiltinProc_atomic_cxchg_acqrel_failrelaxed: - case BuiltinProc_atomic_cxchgweak: - case BuiltinProc_atomic_cxchgweak_acq: - case BuiltinProc_atomic_cxchgweak_rel: - case BuiltinProc_atomic_cxchgweak_acqrel: - case BuiltinProc_atomic_cxchgweak_relaxed: - case BuiltinProc_atomic_cxchgweak_failrelaxed: - case BuiltinProc_atomic_cxchgweak_failacq: - case BuiltinProc_atomic_cxchgweak_acq_failrelaxed: - case BuiltinProc_atomic_cxchgweak_acqrel_failrelaxed: { + case BuiltinProc_atomic_compare_exchange_strong: + case BuiltinProc_atomic_compare_exchange_weak: + case BuiltinProc_atomic_compare_exchange_strong_explicit: + case BuiltinProc_atomic_compare_exchange_weak_explicit: { lbValue address = lb_build_expr(p, ce->args[0]); Type *elem = type_deref(address.type); lbValue old_value = lb_build_expr(p, ce->args[1]); @@ -1793,28 +1731,14 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, LLVMBool weak = false; switch (id) { - case BuiltinProc_atomic_cxchg: success_ordering = LLVMAtomicOrderingSequentiallyConsistent; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = false; break; - case BuiltinProc_atomic_cxchg_acq: success_ordering = LLVMAtomicOrderingAcquire; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = false; break; - case BuiltinProc_atomic_cxchg_rel: success_ordering = LLVMAtomicOrderingRelease; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = false; break; - case BuiltinProc_atomic_cxchg_acqrel: success_ordering = LLVMAtomicOrderingAcquireRelease; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = false; break; - case BuiltinProc_atomic_cxchg_relaxed: success_ordering = LLVMAtomicOrderingMonotonic; failure_ordering = LLVMAtomicOrderingMonotonic; weak = false; break; - case BuiltinProc_atomic_cxchg_failrelaxed: success_ordering = LLVMAtomicOrderingSequentiallyConsistent; failure_ordering = LLVMAtomicOrderingMonotonic; weak = false; break; - case BuiltinProc_atomic_cxchg_failacq: success_ordering = LLVMAtomicOrderingSequentiallyConsistent; failure_ordering = LLVMAtomicOrderingAcquire; weak = false; break; - case BuiltinProc_atomic_cxchg_acq_failrelaxed: success_ordering = LLVMAtomicOrderingAcquire; failure_ordering = LLVMAtomicOrderingMonotonic; weak = false; break; - case BuiltinProc_atomic_cxchg_acqrel_failrelaxed: success_ordering = LLVMAtomicOrderingAcquireRelease; failure_ordering = LLVMAtomicOrderingMonotonic; weak = false; break; - case BuiltinProc_atomic_cxchgweak: success_ordering = LLVMAtomicOrderingSequentiallyConsistent; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = false; break; - case BuiltinProc_atomic_cxchgweak_acq: success_ordering = LLVMAtomicOrderingAcquire; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = true; break; - case BuiltinProc_atomic_cxchgweak_rel: success_ordering = LLVMAtomicOrderingRelease; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = true; break; - case BuiltinProc_atomic_cxchgweak_acqrel: success_ordering = LLVMAtomicOrderingAcquireRelease; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = true; break; - case BuiltinProc_atomic_cxchgweak_relaxed: success_ordering = LLVMAtomicOrderingMonotonic; failure_ordering = LLVMAtomicOrderingMonotonic; weak = true; break; - case BuiltinProc_atomic_cxchgweak_failrelaxed: success_ordering = LLVMAtomicOrderingSequentiallyConsistent; failure_ordering = LLVMAtomicOrderingMonotonic; weak = true; break; - case BuiltinProc_atomic_cxchgweak_failacq: success_ordering = LLVMAtomicOrderingSequentiallyConsistent; failure_ordering = LLVMAtomicOrderingAcquire; weak = true; break; - case BuiltinProc_atomic_cxchgweak_acq_failrelaxed: success_ordering = LLVMAtomicOrderingAcquire; failure_ordering = LLVMAtomicOrderingMonotonic; weak = true; break; - case BuiltinProc_atomic_cxchgweak_acqrel_failrelaxed: success_ordering = LLVMAtomicOrderingAcquireRelease; failure_ordering = LLVMAtomicOrderingMonotonic; weak = true; break; + case BuiltinProc_atomic_compare_exchange_strong: success_ordering = LLVMAtomicOrderingSequentiallyConsistent; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = false; break; + case BuiltinProc_atomic_compare_exchange_weak: success_ordering = LLVMAtomicOrderingSequentiallyConsistent; failure_ordering = LLVMAtomicOrderingSequentiallyConsistent; weak = true; break; + case BuiltinProc_atomic_compare_exchange_strong_explicit: success_ordering = llvm_atomic_ordering_from_odin(ce->args[3]); failure_ordering = llvm_atomic_ordering_from_odin(ce->args[4]); weak = false; break; + case BuiltinProc_atomic_compare_exchange_weak_explicit: success_ordering = llvm_atomic_ordering_from_odin(ce->args[3]); failure_ordering = llvm_atomic_ordering_from_odin(ce->args[4]); weak = true; break; } // TODO(bill): Figure out how to make it weak - LLVMBool single_threaded = weak; + LLVMBool single_threaded = false; LLVMValueRef value = LLVMBuildAtomicCmpXchg( p->builder, address.value, @@ -1823,6 +1747,7 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, failure_ordering, single_threaded ); + LLVMSetWeak(value, weak); if (tv.type->kind == Type_Tuple) { Type *fix_typed = alloc_type_tuple(); @@ -2199,6 +2124,45 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, return res; } + + case BuiltinProc_wasm_memory_grow: + { + char const *name = "llvm.wasm.memory.grow"; + LLVMTypeRef types[1] = { + lb_type(p->module, t_uintptr), + }; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s", name, LLVMPrintTypeToString(types[0])); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + + LLVMValueRef args[2] = {}; + args[0] = lb_emit_conv(p, lb_build_expr(p, ce->args[0]), t_uintptr).value; + args[1] = lb_emit_conv(p, lb_build_expr(p, ce->args[1]), t_uintptr).value; + + lbValue res = {}; + res.type = tv.type; + res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); + return res; + } + case BuiltinProc_wasm_memory_size: + { + char const *name = "llvm.wasm.memory.size"; + LLVMTypeRef types[1] = { + lb_type(p->module, t_uintptr), + }; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s", name, LLVMPrintTypeToString(types[0])); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + + LLVMValueRef args[1] = {}; + args[0] = lb_emit_conv(p, lb_build_expr(p, ce->args[0]), t_uintptr).value; + + lbValue res = {}; + res.type = tv.type; + res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); + return res; + } + } GB_PANIC("Unhandled built-in procedure %.*s", LIT(builtin_procs[id].name)); diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 916c0433e..2afb5300b 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -1652,13 +1652,16 @@ void lb_build_if_stmt(lbProcedure *p, Ast *node) { } lbValue cond = lb_build_cond(p, is->cond, then, else_); + // Note `cond.value` only set for non-and/or conditions and const negs so that the `LLVMIsConstant()` + // and `LLVMConstIntGetZExtValue()` calls below will be valid and `LLVMInstructionEraseFromParent()` + // will target the correct (& only) branch statement if (is->label != nullptr) { lbTargetList *tl = lb_push_target_list(p, is->label, done, nullptr, nullptr); tl->is_block = true; } - if (LLVMIsConstant(cond.value)) { + if (cond.value && LLVMIsConstant(cond.value)) { // NOTE(bill): Do a compile time short circuit for when the condition is constantly known. // This done manually rather than relying on the SSA passes because sometimes the SSA passes // miss some even if they are constantly known, especially with few optimization passes. diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index e245a8b40..7d73956e8 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -641,7 +641,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[7] = {}; + LLVMValueRef vals[8] = {}; isize variant_count = gb_max(0, t->Union.variants.count); lbValue memory_types = lb_type_info_member_types_offset(p, variant_count); @@ -675,8 +675,9 @@ void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup type_info da } 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; + vals[5] = lb_const_bool(m, t_bool, t->Union.kind == UnionType_no_nil).value; + vals[6] = lb_const_bool(m, t_bool, t->Union.kind == UnionType_maybe).value; + vals[7] = lb_const_bool(m, t_bool, t->Union.kind == UnionType_shared_nil).value; for (isize i = 0; i < gb_count_of(vals); i++) { if (vals[i] == nullptr) { diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 399d1632d..037171637 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2005,3 +2005,25 @@ lbValue lb_handle_objc_send(lbProcedure *p, Ast *expr) { } + + +LLVMAtomicOrdering llvm_atomic_ordering_from_odin(ExactValue const &value) { + GB_ASSERT(value.kind == ExactValue_Integer); + i64 v = exact_value_to_i64(value); + switch (v) { + case OdinAtomicMemoryOrder_relaxed: return LLVMAtomicOrderingUnordered; + case OdinAtomicMemoryOrder_consume: return LLVMAtomicOrderingMonotonic; + case OdinAtomicMemoryOrder_acquire: return LLVMAtomicOrderingAcquire; + case OdinAtomicMemoryOrder_release: return LLVMAtomicOrderingRelease; + case OdinAtomicMemoryOrder_acq_rel: return LLVMAtomicOrderingAcquireRelease; + case OdinAtomicMemoryOrder_seq_cst: return LLVMAtomicOrderingSequentiallyConsistent; + } + GB_PANIC("Unknown atomic ordering"); + return LLVMAtomicOrderingSequentiallyConsistent; +} + + +LLVMAtomicOrdering llvm_atomic_ordering_from_odin(Ast *expr) { + ExactValue value = type_and_value_of_expr(expr).value; + return llvm_atomic_ordering_from_odin(value); +} diff --git a/src/main.cpp b/src/main.cpp index 63b2c8386..9e470923d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -117,6 +117,9 @@ i32 system_exec_command_line_app(char const *name, char const *fmt, ...) { gb_printf_err("%s\n\n", cmd_line); } exit_code = system(cmd_line); + if (WIFEXITED(exit_code)) { + exit_code = WEXITSTATUS(exit_code); + } #endif if (exit_code) { @@ -433,6 +436,10 @@ i32 linker_stage(lbGenerator *gen) { String output_ext = {}; gbString link_settings = gb_string_make_reserve(heap_allocator(), 32); + if (build_context.no_crt) { + link_settings = gb_string_append_fmt(link_settings, "-nostdlib "); + } + // NOTE(dweiler): We use clang as a frontend for the linker as there are // other runtime and compiler support libraries that need to be linked in // very specific orders such as libgcc_s, ld-linux-so, unwind, etc. diff --git a/src/parser.cpp b/src/parser.cpp index a435d1317..767119aa8 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1071,15 +1071,14 @@ Ast *ast_struct_type(AstFile *f, Token token, Slice fields, isize field_c } -Ast *ast_union_type(AstFile *f, Token token, Array const &variants, Ast *polymorphic_params, Ast *align, bool no_nil, bool maybe, +Ast *ast_union_type(AstFile *f, Token token, Array const &variants, Ast *polymorphic_params, Ast *align, UnionTypeKind kind, Token where_token, Array const &where_clauses) { Ast *result = alloc_ast_node(f, Ast_UnionType); result->UnionType.token = token; result->UnionType.variants = slice_from_array(variants); result->UnionType.polymorphic_params = polymorphic_params; result->UnionType.align = align; - result->UnionType.no_nil = no_nil; - result->UnionType.maybe = maybe; + result->UnionType.kind = kind; result->UnionType.where_token = where_token; result->UnionType.where_clauses = slice_from_array(where_clauses); return result; @@ -2475,6 +2474,9 @@ Ast *parse_operand(AstFile *f, bool lhs) { Ast *align = nullptr; bool no_nil = false; bool maybe = false; + bool shared_nil = false; + + UnionTypeKind union_kind = UnionType_Normal; Token start_token = f->curr_token; @@ -2501,6 +2503,11 @@ Ast *parse_operand(AstFile *f, bool lhs) { syntax_error(tag, "Duplicate union tag '#%.*s'", LIT(tag.string)); } no_nil = true; + } else if (tag.string == "shared_nil") { + if (shared_nil) { + syntax_error(tag, "Duplicate union tag '#%.*s'", LIT(tag.string)); + } + shared_nil = true; } else if (tag.string == "maybe") { if (maybe) { syntax_error(tag, "Duplicate union tag '#%.*s'", LIT(tag.string)); @@ -2513,6 +2520,21 @@ Ast *parse_operand(AstFile *f, bool lhs) { if (no_nil && maybe) { syntax_error(f->curr_token, "#maybe and #no_nil cannot be applied together"); } + if (no_nil && shared_nil) { + syntax_error(f->curr_token, "#shared_nil and #no_nil cannot be applied together"); + } + if (shared_nil && maybe) { + syntax_error(f->curr_token, "#maybe and #shared_nil cannot be applied together"); + } + + + if (maybe) { + union_kind = UnionType_maybe; + } else if (no_nil) { + union_kind = UnionType_no_nil; + } else if (shared_nil) { + union_kind = UnionType_shared_nil; + } skip_possible_newline_for_literal(f); @@ -2544,7 +2566,7 @@ Ast *parse_operand(AstFile *f, bool lhs) { Token close = expect_closing_brace_of_field_list(f); - return ast_union_type(f, token, variants, polymorphic_params, align, no_nil, maybe, where_token, where_clauses); + return ast_union_type(f, token, variants, polymorphic_params, align, union_kind, where_token, where_clauses); } break; case Token_enum: { diff --git a/src/parser.hpp b/src/parser.hpp index c33d1520b..c7b4fd0d8 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -330,6 +330,13 @@ char const *inline_asm_dialect_strings[InlineAsmDialect_COUNT] = { "intel", }; +enum UnionTypeKind : u8 { + UnionType_Normal = 0, + UnionType_maybe = 1, + UnionType_no_nil = 2, + UnionType_shared_nil = 3, +}; + #define AST_KINDS \ AST_KIND(Ident, "identifier", struct { \ Token token; \ @@ -678,8 +685,7 @@ AST_KIND(_TypeBegin, "", bool) \ Slice variants; \ Ast *polymorphic_params; \ Ast * align; \ - bool maybe; \ - bool no_nil; \ + UnionTypeKind kind; \ Token where_token; \ Slice where_clauses; \ }) \ diff --git a/src/types.cpp b/src/types.cpp index 58ccdf5b9..e10dae1ed 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -165,9 +165,8 @@ struct TypeUnion { i16 tag_size; bool is_polymorphic; - bool is_poly_specialized : 1; - bool no_nil : 1; - bool maybe : 1; + bool is_poly_specialized; + UnionTypeKind kind; }; struct TypeProc { @@ -186,7 +185,6 @@ struct TypeProc { bool c_vararg; bool is_polymorphic; bool is_poly_specialized; - bool has_proc_default_values; bool has_named_results; bool diverging; // no return bool return_by_pointer; @@ -694,6 +692,28 @@ gb_global Type *t_objc_id = nullptr; gb_global Type *t_objc_SEL = nullptr; gb_global Type *t_objc_Class = nullptr; +enum OdinAtomicMemoryOrder : i32 { + OdinAtomicMemoryOrder_relaxed = 0, // unordered + OdinAtomicMemoryOrder_consume = 1, // monotonic + OdinAtomicMemoryOrder_acquire = 2, + OdinAtomicMemoryOrder_release = 3, + OdinAtomicMemoryOrder_acq_rel = 4, + OdinAtomicMemoryOrder_seq_cst = 5, + OdinAtomicMemoryOrder_COUNT, +}; + +char const *OdinAtomicMemoryOrder_strings[OdinAtomicMemoryOrder_COUNT] = { + "Relaxed", + "Consume", + "Acquire", + "Release", + "Acq_Rel", + "Seq_Cst", +}; + +gb_global Type *t_atomic_memory_order = nullptr; + + gb_global RecursiveMutex g_type_mutex; @@ -1665,7 +1685,7 @@ bool is_type_map(Type *t) { bool is_type_union_maybe_pointer(Type *t) { t = base_type(t); - if (t->kind == Type_Union && t->Union.maybe) { + if (t->kind == Type_Union && t->Union.kind == UnionType_maybe) { if (t->Union.variants.count == 1) { Type *v = t->Union.variants[0]; return is_type_pointer(v) || is_type_multi_pointer(v); @@ -1677,7 +1697,7 @@ bool is_type_union_maybe_pointer(Type *t) { bool is_type_union_maybe_pointer_original_alignment(Type *t) { t = base_type(t); - if (t->kind == Type_Union && t->Union.maybe) { + if (t->kind == Type_Union && t->Union.kind == UnionType_maybe) { if (t->Union.variants.count == 1) { Type *v = t->Union.variants[0]; if (is_type_pointer(v) || is_type_multi_pointer(v)) { @@ -2169,7 +2189,7 @@ bool type_has_nil(Type *t) { case Type_Map: return true; case Type_Union: - return !t->Union.no_nil; + return t->Union.kind != UnionType_no_nil; case Type_Struct: if (is_type_soa_struct(t)) { switch (t->Struct.soa_kind) { @@ -2455,7 +2475,7 @@ bool are_types_identical_internal(Type *x, Type *y, bool check_tuple_names) { if (y->kind == Type_Union) { if (x->Union.variants.count == y->Union.variants.count && x->Union.custom_align == y->Union.custom_align && - x->Union.no_nil == y->Union.no_nil) { + x->Union.kind == y->Union.kind) { // NOTE(bill): zeroth variant is nullptr for_array(i, x->Union.variants) { if (!are_types_identical(x->Union.variants[i], y->Union.variants[i])) { @@ -2599,7 +2619,7 @@ i64 union_variant_index(Type *u, Type *v) { for_array(i, u->Union.variants) { Type *vt = u->Union.variants[i]; if (are_types_identical(v, vt)) { - if (u->Union.no_nil) { + if (u->Union.kind == UnionType_no_nil) { return cast(i64)(i+0); } else { return cast(i64)(i+1); @@ -4022,8 +4042,11 @@ gbString write_type_to_string(gbString str, Type *type, bool shorthand=false) { case Type_Union: str = gb_string_appendc(str, "union"); - if (type->Union.no_nil != 0) str = gb_string_appendc(str, " #no_nil"); - if (type->Union.maybe != 0) str = gb_string_appendc(str, " #maybe"); + switch (type->Union.kind) { + case UnionType_maybe: str = gb_string_appendc(str, " #maybe"); break; + case UnionType_no_nil: str = gb_string_appendc(str, " #no_nil"); break; + case UnionType_shared_nil: str = gb_string_appendc(str, " #shared_nil"); break; + } if (type->Union.custom_align != 0) str = gb_string_append_fmt(str, " #align %d", cast(int)type->Union.custom_align); str = gb_string_appendc(str, " {"); for_array(i, type->Union.variants) { diff --git a/tests/common/common.odin b/tests/common/common.odin index 9a715d38e..07b6afef9 100644 --- a/tests/common/common.odin +++ b/tests/common/common.odin @@ -4,6 +4,7 @@ package common import "core:testing" import "core:fmt" import "core:os" +import "core:strings" TEST_count := 0 TEST_fail := 0 @@ -38,3 +39,37 @@ report :: proc(t: ^testing.T) { fmt.printf("%v/%v tests successful.\n", TEST_count, TEST_count) } } + +// Returns absolute path to `sub_path` where `sub_path` is within the "tests/" sub-directory of the Odin project root +// and we're being run from the Odin project root or from a sub-directory of "tests/" +// e.g. get_data_path("assets/blah") will return "/Odin_root/tests/assets/blah" if run within "/Odin_root", +// "/Odin_root/tests" or "/Odin_root/tests/subdir" etc +get_data_path :: proc(t: ^testing.T, sub_path: string) -> (data_path: string) { + + cwd := os.get_current_directory() + defer delete(cwd) + + when ODIN_OS == .Windows { + norm, was_allocation := strings.replace_all(cwd, "\\", "/") + if !was_allocation { + norm = strings.clone(norm) + } + defer delete(norm) + } else { + norm := cwd + } + + last_index := strings.last_index(norm, "/tests/") + if last_index == -1 { + len := len(norm) + if len >= 6 && norm[len-6:] == "/tests" { + data_path = fmt.tprintf("%s/%s", norm, sub_path) + } else { + data_path = fmt.tprintf("%s/tests/%s", norm, sub_path) + } + } else { + data_path = fmt.tprintf("%s/tests/%s", norm[:last_index], sub_path) + } + + return data_path +} diff --git a/tests/core/Makefile b/tests/core/Makefile index 1158434bf..652ebb151 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -2,7 +2,7 @@ ODIN=../../odin PYTHON=$(shell which python3) all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test \ - math_test linalg_glsl_math_test + math_test linalg_glsl_math_test filepath_test reflect_test os_exit_test download_test_assets: $(PYTHON) download_assets.py @@ -26,6 +26,7 @@ noise_test: $(ODIN) run math/noise -out=test_noise encoding_test: + $(ODIN) run encoding/hxa -out=test_hxa -collection:tests=.. $(ODIN) run encoding/json -out=test_json $(ODIN) run encoding/varint -out=test_varint @@ -34,3 +35,12 @@ math_test: linalg_glsl_math_test: $(ODIN) run math/linalg/glsl/test_linalg_glsl_math.odin -out=test_linalg_glsl_math -collection:tests=.. + +filepath_test: + $(ODIN) run path/filepath/test_core_filepath.odin -out=test_core_filepath -collection:tests=.. + +reflect_test: + $(ODIN) run reflect/test_core_reflect.odin -out=test_core_reflect -collection:tests=.. + +os_exit_test: + $(ODIN) run os/test_core_os_exit.odin -out=test_core_os_exit && exit 1 || exit 0 diff --git a/tests/core/assets/HXA/teapot.hxa b/tests/core/assets/HXA/teapot.hxa new file mode 100644 index 000000000..954ab5a10 Binary files /dev/null and b/tests/core/assets/HXA/teapot.hxa differ diff --git a/tests/core/build.bat b/tests/core/build.bat index 0227ac6bb..2f9ba672e 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -1,5 +1,5 @@ @echo off -set COMMON=-show-timings -no-bounds-check -vet -strict-style +set COMMON=-show-timings -no-bounds-check -vet -strict-style -collection:tests=.. set PATH_TO_ODIN==..\..\odin python3 download_assets.py echo --- @@ -35,10 +35,31 @@ echo --- echo --- echo Running core:encoding tests echo --- +%PATH_TO_ODIN% run encoding/hxa %COMMON% %PATH_TO_ODIN% run encoding/json %COMMON% %PATH_TO_ODIN% run encoding/varint %COMMON% echo --- echo Running core:math/noise tests echo --- -%PATH_TO_ODIN% run math/noise %COMMON% \ No newline at end of file +%PATH_TO_ODIN% run math/noise %COMMON% + +echo --- +echo Running core:math tests +echo --- +%PATH_TO_ODIN% run math %COMMON% + +echo --- +echo Running core:math/linalg/glsl tests +echo --- +%PATH_TO_ODIN% run math/linalg/glsl %COMMON% + +echo --- +echo Running core:path/filepath tests +echo --- +%PATH_TO_ODIN% run path/filepath %COMMON% + +echo --- +echo Running core:reflect tests +echo --- +%PATH_TO_ODIN% run reflect %COMMON% diff --git a/tests/core/encoding/hxa/test_core_hxa.odin b/tests/core/encoding/hxa/test_core_hxa.odin new file mode 100644 index 000000000..b93562fd5 --- /dev/null +++ b/tests/core/encoding/hxa/test_core_hxa.odin @@ -0,0 +1,232 @@ +// Tests "core:encoding:hxa". +// Must be run with `-collection:tests=` flag, e.g. +// ./odin run tests/core/encoding/hxa/test_core_hxa.odin -out=tests/core/test_core_hxa -collection:tests=./tests +package test_core_hxa + +import "core:encoding/hxa" +import "core:fmt" +import "core:testing" +import tc "tests:common" + +TEAPOT_PATH :: "core/assets/HXA/teapot.hxa" + +main :: proc() { + t := testing.T{} + + test_read(&t) + test_write(&t) + + tc.report(&t) +} + +@test +test_read :: proc(t: ^testing.T) { + + using hxa + + filename := tc.get_data_path(t, TEAPOT_PATH) + defer delete(filename) + + file, err := read_from_file(filename) + e :: hxa.Read_Error.None + tc.expect(t, err == e, fmt.tprintf("%v: read_from_file(%v) -> %v != %v", #procedure, filename, err, e)) + defer file_destroy(file) + + /* Header */ + tc.expect(t, file.magic_number == 0x417848, fmt.tprintf("%v: file.magic_number %v != %v", + #procedure, file.magic_number, 0x417848)) + tc.expect(t, file.version == 1, fmt.tprintf("%v: file.version %v != %v", + #procedure, file.version, 1)) + tc.expect(t, file.internal_node_count == 1, fmt.tprintf("%v: file.internal_node_count %v != %v", + #procedure, file.internal_node_count, 1)) + + /* Nodes (only one) */ + tc.expect(t, len(file.nodes) == 1, fmt.tprintf("%v: len(file.nodes) %v != %v", #procedure, len(file.nodes), 1)) + + m := &file.nodes[0].meta_data + tc.expect(t, len(m^) == 38, fmt.tprintf("%v: len(m^) %v != %v", #procedure, len(m^), 38)) + { + e :: "Texture resolution" + tc.expect(t, m[0].name == e, fmt.tprintf("%v: m[0].name %v != %v", #procedure, m[0].name, e)) + + m_v, m_v_ok := m[0].value.([]i64le) + tc.expect(t, m_v_ok, fmt.tprintf("%v: m_v_ok %v != %v", #procedure, m_v_ok, true)) + tc.expect(t, len(m_v) == 1, fmt.tprintf("%v: len(m_v) %v != %v", #procedure, len(m_v), 1)) + tc.expect(t, m_v[0] == 1024, fmt.tprintf("%v: m_v[0] %v != %v", #procedure, len(m_v), 1024)) + } + { + e :: "Validate" + tc.expect(t, m[37].name == e, fmt.tprintf("%v: m[37].name %v != %v", #procedure, m[37].name, e)) + + m_v, m_v_ok := m[37].value.([]i64le) + tc.expect(t, m_v_ok, fmt.tprintf("%v: m_v_ok %v != %v", #procedure, m_v_ok, true)) + tc.expect(t, len(m_v) == 1, fmt.tprintf("%v: len(m_v) %v != %v", #procedure, len(m_v), 1)) + tc.expect(t, m_v[0] == -2054847231, fmt.tprintf("%v: m_v[0] %v != %v", #procedure, len(m_v), -2054847231)) + } + + /* Node content */ + v, v_ok := file.nodes[0].content.(hxa.Node_Geometry) + tc.expect(t, v_ok, fmt.tprintf("%v: v_ok %v != %v", #procedure, v_ok, true)) + + tc.expect(t, v.vertex_count == 530, fmt.tprintf("%v: v.vertex_count %v != %v", #procedure, v.vertex_count, 530)) + tc.expect(t, v.edge_corner_count == 2026, fmt.tprintf("%v: v.edge_corner_count %v != %v", + #procedure, v.edge_corner_count, 2026)) + tc.expect(t, v.face_count == 517, fmt.tprintf("%v: v.face_count %v != %v", #procedure, v.face_count, 517)) + + /* Vertex stack */ + tc.expect(t, len(v.vertex_stack) == 1, fmt.tprintf("%v: len(v.vertex_stack) %v != %v", + #procedure, len(v.vertex_stack), 1)) + { + e := "vertex" + tc.expect(t, v.vertex_stack[0].name == e, fmt.tprintf("%v: v.vertex_stack[0].name %v != %v", + #procedure, v.vertex_stack[0].name, e)) + } + tc.expect(t, v.vertex_stack[0].components == 3, fmt.tprintf("%v: v.vertex_stack[0].components %v != %v", + #procedure, v.vertex_stack[0].components, 3)) + + /* Vertex stack data */ + vs_d, vs_d_ok := v.vertex_stack[0].data.([]f64le) + tc.expect(t, vs_d_ok, fmt.tprintf("%v: vs_d_ok %v != %v", #procedure, vs_d_ok, true)) + tc.expect(t, len(vs_d) == 1590, fmt.tprintf("%v: len(vs_d) %v != %v", #procedure, len(vs_d), 1590)) + + tc.expect(t, vs_d[0] == 4.06266, fmt.tprintf("%v: vs_d[0] %v (%h) != %v (%h)", + #procedure, vs_d[0], vs_d[0], 4.06266, 4.06266)) + tc.expect(t, vs_d[1] == 2.83457, fmt.tprintf("%v: vs_d[1] %v (%h) != %v (%h)", + #procedure, vs_d[1], vs_d[1], 2.83457, 2.83457)) + tc.expect(t, vs_d[2] == 0hbfbc5da6a4441787, fmt.tprintf("%v: vs_d[2] %v (%h) != %v (%h)", + #procedure, vs_d[2], vs_d[2], + 0hbfbc5da6a4441787, 0hbfbc5da6a4441787)) + tc.expect(t, vs_d[3] == 0h4010074fb549f948, fmt.tprintf("%v: vs_d[3] %v (%h) != %v (%h)", + #procedure, vs_d[3], vs_d[3], + 0h4010074fb549f948, 0h4010074fb549f948)) + tc.expect(t, vs_d[1587] == 0h400befa82e87d2c7, fmt.tprintf("%v: vs_d[1587] %v (%h) != %v (%h)", + #procedure, vs_d[1587], vs_d[1587], + 0h400befa82e87d2c7, 0h400befa82e87d2c7)) + tc.expect(t, vs_d[1588] == 2.83457, fmt.tprintf("%v: vs_d[1588] %v (%h) != %v (%h)", + #procedure, vs_d[1588], vs_d[1588], 2.83457, 2.83457)) + tc.expect(t, vs_d[1589] == -1.56121, fmt.tprintf("%v: vs_d[1589] %v (%h) != %v (%h)", + #procedure, vs_d[1589], vs_d[1589], -1.56121, -1.56121)) + + /* Corner stack */ + tc.expect(t, len(v.corner_stack) == 1, + fmt.tprintf("%v: len(v.corner_stack) %v != %v", #procedure, len(v.corner_stack), 1)) + { + e := "reference" + tc.expect(t, v.corner_stack[0].name == e, fmt.tprintf("%v: v.corner_stack[0].name %v != %v", + #procedure, v.corner_stack[0].name, e)) + } + tc.expect(t, v.corner_stack[0].components == 1, fmt.tprintf("%v: v.corner_stack[0].components %v != %v", + #procedure, v.corner_stack[0].components, 1)) + + /* Corner stack data */ + cs_d, cs_d_ok := v.corner_stack[0].data.([]i32le) + tc.expect(t, cs_d_ok, fmt.tprintf("%v: cs_d_ok %v != %v", #procedure, cs_d_ok, true)) + tc.expect(t, len(cs_d) == 2026, fmt.tprintf("%v: len(cs_d) %v != %v", #procedure, len(cs_d), 2026)) + tc.expect(t, cs_d[0] == 6, fmt.tprintf("%v: cs_d[0] %v != %v", #procedure, cs_d[0], 6)) + tc.expect(t, cs_d[2025] == -32, fmt.tprintf("%v: cs_d[2025] %v != %v", #procedure, cs_d[2025], -32)) + + /* Edge and face stacks (empty) */ + tc.expect(t, len(v.edge_stack) == 0, fmt.tprintf("%v: len(v.edge_stack) %v != %v", + #procedure, len(v.edge_stack), 0)) + tc.expect(t, len(v.face_stack) == 0, fmt.tprintf("%v: len(v.face_stack) %v != %v", + #procedure, len(v.face_stack), 0)) +} + +@test +test_write :: proc(t: ^testing.T) { + + using hxa + + n1 :Node + + n1_m1_value := []f64le{0.4, -1.23, 2341.6, -333.333} + n1_m1 := Meta{"m1", n1_m1_value} + + n1.meta_data = []Meta{n1_m1} + + n1_l1 := Layer{"l1", 2, []f32le{32.1, -41.3}} + n1_l2 := Layer{"l2", 3, []f64le{0.64, 1.64, -2.64}} + + n1_content := Node_Image{Image_Type.Image_1D, [3]u32le{1, 1, 2}, Layer_Stack{n1_l1, n1_l2}} + + n1.content = n1_content + + w_file :File + w_file.nodes = []Node{n1} + + required_size := required_write_size(w_file) + buf := make([]u8, required_size) + + n, write_err := write(buf, w_file) + write_e :: hxa.Write_Error.None + tc.expect(t, write_err == write_e, fmt.tprintf("%v: write_err %v != %v", #procedure, write_err, write_e)) + tc.expect(t, n == required_size, fmt.tprintf("%v: n %v != %v", #procedure, n, required_size)) + + file, read_err := read(buf) + read_e :: hxa.Read_Error.None + tc.expect(t, read_err == read_e, fmt.tprintf("%v: read_err %v != %v", #procedure, read_err, read_e)) + defer file_destroy(file) + + delete(buf) + + tc.expect(t, file.magic_number == 0x417848, fmt.tprintf("%v: file.magic_number %v != %v", + #procedure, file.magic_number, 0x417848)) + tc.expect(t, file.version == 3, fmt.tprintf("%v: file.version %v != %v", #procedure, file.version, 3)) + tc.expect(t, file.internal_node_count == 1, fmt.tprintf("%v: file.internal_node_count %v != %v", + #procedure, file.internal_node_count, 1)) + + tc.expect(t, len(file.nodes) == len(w_file.nodes), fmt.tprintf("%v: len(file.nodes) %v != %v", + #procedure, len(file.nodes), len(w_file.nodes))) + + m := &file.nodes[0].meta_data + w_m := &w_file.nodes[0].meta_data + tc.expect(t, len(m^) == len(w_m^), fmt.tprintf("%v: len(m^) %v != %v", #procedure, len(m^), len(w_m^))) + tc.expect(t, m[0].name == w_m[0].name, fmt.tprintf("%v: m[0].name %v != %v", #procedure, m[0].name, w_m[0].name)) + + m_v, m_v_ok := m[0].value.([]f64le) + tc.expect(t, m_v_ok, fmt.tprintf("%v: m_v_ok %v != %v", #procedure, m_v_ok, true)) + tc.expect(t, len(m_v) == len(n1_m1_value), fmt.tprintf("%v: %v != len(m_v) %v", + #procedure, len(m_v), len(n1_m1_value))) + for i := 0; i < len(m_v); i += 1 { + tc.expect(t, m_v[i] == n1_m1_value[i], fmt.tprintf("%v: m_v[%d] %v != %v", + #procedure, i, m_v[i], n1_m1_value[i])) + } + + v, v_ok := file.nodes[0].content.(hxa.Node_Image) + tc.expect(t, v_ok, fmt.tprintf("%v: v_ok %v != %v", #procedure, v_ok, true)) + tc.expect(t, v.type == n1_content.type, fmt.tprintf("%v: v.type %v != %v", #procedure, v.type, n1_content.type)) + tc.expect(t, len(v.resolution) == 3, fmt.tprintf("%v: len(v.resolution) %v != %v", + #procedure, len(v.resolution), 3)) + tc.expect(t, len(v.image_stack) == len(n1_content.image_stack), fmt.tprintf("%v: len(v.image_stack) %v != %v", + #procedure, len(v.image_stack), len(n1_content.image_stack))) + for i := 0; i < len(v.image_stack); i += 1 { + tc.expect(t, v.image_stack[i].name == n1_content.image_stack[i].name, + fmt.tprintf("%v: v.image_stack[%d].name %v != %v", + #procedure, i, v.image_stack[i].name, n1_content.image_stack[i].name)) + tc.expect(t, v.image_stack[i].components == n1_content.image_stack[i].components, + fmt.tprintf("%v: v.image_stack[%d].components %v != %v", + #procedure, i, v.image_stack[i].components, n1_content.image_stack[i].components)) + + switch n1_t in n1_content.image_stack[i].data { + case []u8: + tc.expect(t, false, fmt.tprintf("%v: n1_content.image_stack[i].data []u8", #procedure)) + case []i32le: + tc.expect(t, false, fmt.tprintf("%v: n1_content.image_stack[i].data []i32le", #procedure)) + case []f32le: + l, l_ok := v.image_stack[i].data.([]f32le) + tc.expect(t, l_ok, fmt.tprintf("%v: l_ok %v != %v", #procedure, l_ok, true)) + tc.expect(t, len(l) == len(n1_t), fmt.tprintf("%v: len(l) %v != %v", #procedure, len(l), len(n1_t))) + for j := 0; j < len(l); j += 1 { + tc.expect(t, l[j] == n1_t[j], fmt.tprintf("%v: l[%d] %v (%h) != %v (%h)", + #procedure, j, l[j], l[j], n1_t[j], n1_t[j])) + } + case []f64le: + l, l_ok := v.image_stack[i].data.([]f64le) + tc.expect(t, l_ok, fmt.tprintf("%v: l_ok %v != %v", #procedure, l_ok, true)) + tc.expect(t, len(l) == len(n1_t), fmt.tprintf("%v: len(l) %v != %v", #procedure, len(l), len(n1_t))) + for j := 0; j < len(l); j += 1 { + tc.expect(t, l[j] == n1_t[j], fmt.tprintf("%v: l[%d] %v != %v", #procedure, j, l[j], n1_t[j])) + } + } + } +} diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index 60e71bf72..285cc04a1 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -88,5 +88,5 @@ marshal_json :: proc(t: ^testing.T) { _, err := json.marshal(my_struct) - expect(t, err == .None, "expected json error to be none") + expect(t, err == nil, "expected json error to be none") } diff --git a/tests/core/os/test_core_os_exit.odin b/tests/core/os/test_core_os_exit.odin new file mode 100644 index 000000000..2ab274f5e --- /dev/null +++ b/tests/core/os/test_core_os_exit.odin @@ -0,0 +1,10 @@ +// Tests that Odin run returns exit code of built executable on Unix +// Needs exit status to be inverted to return 0 on success, e.g. +// $(./odin run tests/core/os/test_core_os_exit.odin && exit 1 || exit 0) +package test_core_os_exit + +import "core:os" + +main :: proc() { + os.exit(1) +} diff --git a/tests/core/path/filepath/test_core_filepath.odin b/tests/core/path/filepath/test_core_filepath.odin new file mode 100644 index 000000000..0268fb62c --- /dev/null +++ b/tests/core/path/filepath/test_core_filepath.odin @@ -0,0 +1,123 @@ +// Tests "path.odin" in "core:path/filepath". +// Must be run with `-collection:tests=` flag, e.g. +// ./odin run tests/core/path/filepath/test_core_filepath.odin -collection:tests=tests +package test_core_filepath + +import "core:fmt" +import "core:path/filepath" +import "core:testing" +import tc "tests:common" + +main :: proc() { + t := testing.T{} + + when ODIN_OS == .Windows { + test_split_list_windows(&t) + } else { + test_split_list_unix(&t) + } + + tc.report(&t) +} + +@test +test_split_list_windows :: proc(t: ^testing.T) { + + using filepath + + Datum :: struct { + i: int, + v: string, + e: [3]string, + } + @static data := []Datum{ + { 0, "C:\\Odin;C:\\Visual Studio;\"C:\\Some Other\"", + [3]string{"C:\\Odin", "C:\\Visual Studio", "C:\\Some Other"} }, // Issue #1537 + { 1, "a;;b", [3]string{"a", "", "b"} }, + { 2, "a;b;", [3]string{"a", "b", ""} }, + { 3, ";a;b", [3]string{"", "a", "b"} }, + { 4, ";;", [3]string{"", "", ""} }, + { 5, "\"a;b\"c;d;\"f\"", [3]string{"a;bc", "d", "f"} }, + { 6, "\"a;b;c\";d\";e\";f", [3]string{"a;b;c", "d;e", "f"} }, + } + + for d, i in data { + assert(i == d.i, fmt.tprintf("wrong data index: i %d != d.i %d\n", i, d.i)) + r := split_list(d.v) + defer delete(r) + tc.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d", + i, #procedure, d.v, len(r), len(d.e))) + if len(r) == len(d.e) { + for _, j in r { + tc.expect(t, r[j] == d.e[j], fmt.tprintf("i:%d %s(%v) -> %v[%d] != %v", + i, #procedure, d.v, r[j], j, d.e[j])) + } + } + } + + { + v := "" + r := split_list(v) + tc.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r)) + } + { + v := "a" + r := split_list(v) + defer delete(r) + tc.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r))) + if len(r) == 1 { + tc.expect(t, r[0] == "a", fmt.tprintf("%s(%v) -> %v[0] != a", #procedure, v, r[0])) + } + } +} + +@test +test_split_list_unix :: proc(t: ^testing.T) { + + using filepath + + Datum :: struct { + i: int, + v: string, + e: [3]string, + } + @static data := []Datum{ + { 0, "/opt/butler:/home/fancykillerpanda/Projects/Odin/Odin:/usr/local/sbin", + [3]string{"/opt/butler", "/home/fancykillerpanda/Projects/Odin/Odin", "/usr/local/sbin"} }, // Issue #1537 + { 1, "a::b", [3]string{"a", "", "b"} }, + { 2, "a:b:", [3]string{"a", "b", ""} }, + { 3, ":a:b", [3]string{"", "a", "b"} }, + { 4, "::", [3]string{"", "", ""} }, + { 5, "\"a:b\"c:d:\"f\"", [3]string{"a:bc", "d", "f"} }, + { 6, "\"a:b:c\":d\":e\":f", [3]string{"a:b:c", "d:e", "f"} }, + } + + for d, i in data { + assert(i == d.i, fmt.tprintf("wrong data index: i %d != d.i %d\n", i, d.i)) + r := split_list(d.v) + defer delete(r) + tc.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d", + i, #procedure, d.v, len(r), len(d.e))) + if len(r) == len(d.e) { + for _, j in r { + tc.expect(t, r[j] == d.e[j], fmt.tprintf("i:%d %s(%v) -> %v[%d] != %v", + i, #procedure, d.v, r[j], j, d.e[j])) + } + } + } + + { + v := "" + r := split_list(v) + tc.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r)) + } + { + v := "a" + r := split_list(v) + defer delete(r) + tc.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r))) + if len(r) == 1 { + tc.expect(t, r[0] == "a", fmt.tprintf("%s(%v) -> %v[0] != a", #procedure, v, r[0])) + } + } +} diff --git a/tests/core/reflect/test_core_reflect.odin b/tests/core/reflect/test_core_reflect.odin new file mode 100644 index 000000000..039501735 --- /dev/null +++ b/tests/core/reflect/test_core_reflect.odin @@ -0,0 +1,288 @@ +// Tests "core:reflect/reflect". +// Must be run with `-collection:tests=` flag, e.g. +// ./odin run tests/core/reflect/test_core_reflect.odin -out=tests/core/test_core_reflect -collection:tests=./tests +package test_core_reflect + +import "core:fmt" +import "core:reflect" +import "core:testing" +import tc "tests:common" + +main :: proc() { + t := testing.T{} + + test_as_u64(&t) + test_as_f64(&t) + + tc.report(&t) +} + +@test +test_as_u64 :: proc(t: ^testing.T) { + using reflect + + { + /* i8 */ + Datum :: struct { i: int, v: i8, e: u64 } + @static data := []Datum{ + { 0, 0x7F, 0x7F }, + { 1, -1, 0xFFFF_FFFF_FFFF_FFFF }, + { 2, -0x80, 0xFFFF_FFFF_FFFF_FF80 }, + } + + for d, i in data { + assert(i == d.i) + r, valid := as_u64(d.v) + tc.expect(t, valid, fmt.tprintf("i:%d %s(i8 %v) !valid\n", i, #procedure, d.v)) + tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i8 %v) -> %v (0x%X) != %v (0x%X)\n", + i, #procedure, d.v, r, r, d.e, d.e)) + } + } + { + /* i16 */ + Datum :: struct { i: int, v: i16, e: u64 } + @static data := []Datum{ + { 0, 0x7FFF, 0x7FFF }, + { 1, -1, 0xFFFF_FFFF_FFFF_FFFF }, + { 2, -0x8000, 0xFFFF_FFFF_FFFF_8000 }, + } + + for d, i in data { + assert(i == d.i) + r, valid := as_u64(d.v) + tc.expect(t, valid, fmt.tprintf("i:%d %s(i16 %v) !valid\n", i, #procedure, d.v)) + tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i16 %v) -> %v (0x%X) != %v (0x%X)\n", + i, #procedure, d.v, r, r, d.e, d.e)) + } + } + { + /* i32 */ + Datum :: struct { i: int, v: i32, e: u64 } + @static data := []Datum{ + { 0, 0x7FFF_FFFF, 0x7FFF_FFFF }, + { 1, -1, 0xFFFF_FFFF_FFFF_FFFF }, + { 2, -0x8000_0000, 0xFFFF_FFFF_8000_0000 }, + } + + for d, i in data { + assert(i == d.i) + r, valid := as_u64(d.v) + tc.expect(t, valid, fmt.tprintf("i:%d %s(i32 %v) !valid\n", i, #procedure, d.v)) + tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i32 %v) -> %v (0x%X) != %v (0x%X)\n", + i, #procedure, d.v, r, r, d.e, d.e)) + } + } + { + /* i64 */ + Datum :: struct { i: int, v: i64, e: u64 } + @static data := []Datum{ + { 0, 0x7FFF_FFFF_FFFF_FFFF, 0x7FFF_FFFF_FFFF_FFFF }, + { 1, -1, 0xFFFF_FFFF_FFFF_FFFF }, + { 2, -0x8000_0000_0000_0000, 0x8000_0000_0000_0000 }, + } + + for d, i in data { + assert(i == d.i) + r, valid := as_u64(d.v) + tc.expect(t, valid, fmt.tprintf("i:%d %s(i64 %v) !valid\n", i, #procedure, d.v)) + tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i64 %v) -> %v (0x%X) != %v (0x%X)\n", + i, #procedure, d.v, r, r, d.e, d.e)) + } + } + { + /* i128 */ + Datum :: struct { i: int, v: i128, e: u64 } + @static data := []Datum{ + { 0, 0x7FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF }, + { 1, -1, 0xFFFF_FFFF_FFFF_FFFF }, + { 2, 0x8000_0000_0000_0000, 0x8000_0000_0000_0000 }, + { 3, -0x8000_0000_0000_0000, 0x8000_0000_0000_0000 }, + { 4, 0x0001_0000_0000_0000_0000, 0 }, + { 5, -0x8000_0000_0000_0000_0000_0000_0000_0000, 0 }, + } + + for d, i in data { + assert(i == d.i) + r, valid := as_u64(d.v) + tc.expect(t, valid, fmt.tprintf("i:%d %s(i128 %v) !valid\n", i, #procedure, d.v)) + tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i128 %v) -> %v (0x%X) != %v (0x%X)\n", + i, #procedure, d.v, r, r, d.e, d.e)) + } + } + { + /* f16 */ + Datum :: struct { i: int, v: f16, e: u64 } + @static data := []Datum{ + { 0, 1.2, 1 }, + { 1, 123.12, 123 }, + } + + for d, i in data { + assert(i == d.i) + r, valid := as_u64(d.v) + tc.expect(t, valid, fmt.tprintf("i:%d %s(f16 %v) !valid\n", i, #procedure, d.v)) + tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f16 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + } + } + { + /* f32 */ + Datum :: struct { i: int, v: f32, e: u64 } + @static data := []Datum{ + { 0, 123.3415, 123 }, + } + + for d, i in data { + assert(i == d.i) + r, valid := as_u64(d.v) + tc.expect(t, valid, fmt.tprintf("i:%d %s(f32 %v) !valid\n", i, #procedure, d.v)) + tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f32 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + } + } + { + /* f64 */ + Datum :: struct { i: int, v: f64, e: u64 } + @static data := []Datum{ + { 0, 12345345345.3415234234, 12345345345 }, + } + + for d, i in data { + assert(i == d.i) + r, valid := as_u64(d.v) + tc.expect(t, valid, fmt.tprintf("i:%d %s(f64 %v) !valid\n", i, #procedure, d.v)) + tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + } + } +} + +@test +test_as_f64 :: proc(t: ^testing.T) { + using reflect + + { + /* i8 */ + Datum :: struct { i: int, v: i8, e: f64 } + @static data := []Datum{ + { 0, 0x7F, 0x7F }, + { 1, -1, -1 }, + { 2, -0x80, -0x80 }, + } + + for d, i in data { + assert(i == d.i) + r, valid := as_f64(d.v) + tc.expect(t, valid, fmt.tprintf("i:%d %s(i8 %v) !valid\n", i, #procedure, d.v)) + tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i8 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + } + } + { + /* i16 */ + Datum :: struct { i: int, v: i16, e: f64 } + @static data := []Datum{ + { 0, 0x7FFF, 0x7FFF }, + { 1, -1, -1 }, + { 2, -0x8000, -0x8000 }, + } + + for d, i in data { + assert(i == d.i) + r, valid := as_f64(d.v) + tc.expect(t, valid, fmt.tprintf("i:%d %s(i16 %v) !valid\n", i, #procedure, d.v)) + tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i16 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + } + } + { + /* i32 */ + Datum :: struct { i: int, v: i32, e: f64 } + @static data := []Datum{ + { 0, 0x7FFF_FFFF, 0x7FFF_FFFF }, + { 1, -1, -1 }, + { 2, -0x8000_0000, -0x8000_0000 }, + } + + for d, i in data { + assert(i == d.i) + r, valid := as_f64(d.v) + tc.expect(t, valid, fmt.tprintf("i:%d %s(i32 %v) !valid\n", i, #procedure, d.v)) + tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i32 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + } + } + { + /* i64 */ + Datum :: struct { i: int, v: i64, e: f64 } + @static data := []Datum{ + { 0, 0x7FFF_FFFF_FFFF_FFFF, 0x7FFF_FFFF_FFFF_FFFF }, + { 1, -1, -1 }, + { 2, -0x8000_0000_0000_0000, -0x8000_0000_0000_0000 }, + } + + for d, i in data { + assert(i == d.i) + r, valid := as_f64(d.v) + tc.expect(t, valid, fmt.tprintf("i:%d %s(i64 %v) !valid\n", i, #procedure, d.v)) + tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + } + } + { + /* i128 */ + Datum :: struct { i: int, v: i128, e: f64 } + @static data := []Datum{ + { 0, 0x7FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF, 0x7FFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF }, + { 1, -1, -1 }, + { 2, 0x8000_0000_0000_0000_0000_0000_0000, 0x8000_0000_0000_0000_0000_0000_0000 }, + { 3, -0x8000_0000_0000_0000_0000_0000_0000_0000, -0x8000_0000_0000_0000_0000_0000_0000_0000 }, + } + + for d, i in data { + assert(i == d.i) + r, valid := as_f64(d.v) + tc.expect(t, valid, fmt.tprintf("i:%d %s(i128 %v) !valid\n", i, #procedure, d.v)) + tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i128 %v) -> %v (%H) != %v (%H)\n", + i, #procedure, d.v, r, r, d.e, d.e)) + } + } + { + /* f16 */ + Datum :: struct { i: int, v: f16, e: f64 } + @static data := []Datum{ + { 0, 1.2, 0h3FF3_3400_0000_0000 }, // Precision difference TODO: check + { 1, 123.12, 0h405E_C800_0000_0000 }, // Precision difference TODO: check + } + + for d, i in data { + assert(i == d.i) + r, valid := as_f64(d.v) + tc.expect(t, valid, fmt.tprintf("i:%d %s(f16 %v) !valid\n", i, #procedure, d.v)) + tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f16 %v (%H)) -> %v (%H) != %v (%H)\n", + i, #procedure, d.v, d.v, r, r, d.e, d.e)) + } + } + { + /* f32 */ + Datum :: struct { i: int, v: f32, e: f64 } + @static data := []Datum{ + { 0, 123.3415, 0h405E_D5DB_2000_0000 }, // Precision difference TODO: check + } + + for d, i in data { + assert(i == d.i) + r, valid := as_f64(d.v) + tc.expect(t, valid, fmt.tprintf("i:%d %s(f32 %v) !valid\n", i, #procedure, d.v)) + tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f32 %v (%H)) -> %v (%H) != %v (%H)\n", + i, #procedure, d.v, d.v, r, r, d.e, d.e)) + } + } + { + /* f64 */ + Datum :: struct { i: int, v: f64, e: f64 } + @static data := []Datum{ + { 0, 12345345345.3415234234, 12345345345.3415234234 }, + } + + for d, i in data { + assert(i == d.i) + r, valid := as_f64(d.v) + tc.expect(t, valid, fmt.tprintf("i:%d %s(f64 %v) !valid\n", i, #procedure, d.v)) + tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + } + } +} diff --git a/tests/core/strings/test_core_strings.odin b/tests/core/strings/test_core_strings.odin index 70da1a73b..e97734dda 100644 --- a/tests/core/strings/test_core_strings.odin +++ b/tests/core/strings/test_core_strings.odin @@ -1,4 +1,4 @@ -package test_core_image +package test_core_strings import "core:strings" import "core:testing" @@ -32,6 +32,7 @@ main :: proc() { test_index_any_larger_string_not_found(&t) test_index_any_small_string_found(&t) test_index_any_larger_string_found(&t) + test_cut(&t) fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) if TEST_fail > 0 { @@ -42,7 +43,6 @@ main :: proc() { @test test_index_any_small_string_not_found :: proc(t: ^testing.T) { index := strings.index_any(".", "/:\"") - log(t, index) expect(t, index == -1, "index_any should be negative") } @@ -63,3 +63,30 @@ test_index_any_larger_string_found :: proc(t: ^testing.T) { index := strings.index_any("aaaaaaaa:aaaaaaaa", "/:\"") expect(t, index == 8, "index_any should be 8") } + +Cut_Test :: struct { + input: string, + offset: int, + length: int, + output: string, +} + +cut_tests :: []Cut_Test{ + {"some example text", 0, 4, "some" }, + {"some example text", 2, 2, "me" }, + {"some example text", 5, 7, "example" }, + {"some example text", 5, 0, "example text"}, + {"恥ずべきフクロウ", 4, 0, "フクロウ" }, +} + +@test +test_cut :: proc(t: ^testing.T) { + for test in cut_tests { + res := strings.cut(test.input, test.offset, test.length) + defer delete(res) + + msg := fmt.tprintf("cut(\"%v\", %v, %v) expected to return \"%v\", got \"%v\"", + test.input, test.offset, test.length, test.output, res) + expect(t, res == test.output, msg) + } +} \ No newline at end of file diff --git a/tests/issues/run.bat b/tests/issues/run.bat new file mode 100644 index 000000000..b145dda48 --- /dev/null +++ b/tests/issues/run.bat @@ -0,0 +1,17 @@ +@echo off + +if not exist "tests\issues\build\" mkdir tests\issues\build + +set COMMON=-collection:tests=tests -out:tests\issues\build\test_issue + +@echo on + +.\odin build tests\issues\test_issue_829.odin %COMMON% +tests\issues\build\test_issue + +.\odin build tests\issues\test_issue_1592.odin %COMMON% +tests\issues\build\test_issue + +@echo off + +rmdir /S /Q tests\issues\build diff --git a/tests/issues/run.sh b/tests/issues/run.sh new file mode 100755 index 000000000..c4f978771 --- /dev/null +++ b/tests/issues/run.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -eu + +mkdir -p tests/issues/build + +COMMON="-collection:tests=tests -out:tests/issues/build/test_issue" + +set -x + +./odin build tests/issues/test_issue_829.odin $COMMON +tests/issues/build/test_issue + +./odin build tests/issues/test_issue_1592.odin $COMMON +tests/issues/build/test_issue + +set +x + +rm -rf tests/issues/build diff --git a/tests/issues/test_issue_1592.odin b/tests/issues/test_issue_1592.odin new file mode 100644 index 000000000..bb350a30b --- /dev/null +++ b/tests/issues/test_issue_1592.odin @@ -0,0 +1,489 @@ +// Tests issue #1592 https://github.com/odin-lang/Odin/issues/1592 +package test_issues + +import "core:fmt" +import "core:testing" +import tc "tests:common" + +main :: proc() { + t := testing.T{} + + /* This won't short-circuit */ + test_orig() + + /* These will short-circuit */ + test_simple_const_false(&t) + test_simple_const_true(&t) + + /* These won't short-circuit */ + test_simple_proc_false(&t) + test_simple_proc_true(&t) + + /* These won't short-circuit */ + test_const_false_const_false(&t) + test_const_false_const_true(&t) + test_const_true_const_false(&t) + test_const_true_const_true(&t) + + /* These won't short-circuit */ + test_proc_false_const_false(&t) + test_proc_false_const_true(&t) + test_proc_true_const_false(&t) + test_proc_true_const_true(&t) + + tc.report(&t) +} + +/* Original issue #1592 example */ + +// I get a LLVM code gen error when this constant is false, but it works when it is true +CONSTANT_BOOL :: false + +bool_result :: proc() -> bool { + return false +} + +@test +test_orig :: proc() { + if bool_result() || CONSTANT_BOOL { + } +} + +CONSTANT_FALSE :: false +CONSTANT_TRUE :: true + +false_result :: proc() -> bool { + return false +} +true_result :: proc() -> bool { + return true +} + +@test +test_simple_const_false :: proc(t: ^testing.T) { + if CONSTANT_FALSE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if (CONSTANT_FALSE) { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if !CONSTANT_FALSE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if (!CONSTANT_FALSE) { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if !(CONSTANT_FALSE) { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if !!CONSTANT_FALSE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if CONSTANT_FALSE == true { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if CONSTANT_FALSE == false { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if !(CONSTANT_FALSE == true) { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if !(CONSTANT_FALSE == false) { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } +} + +@test +test_simple_const_true :: proc(t: ^testing.T) { + if CONSTANT_TRUE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if (CONSTANT_TRUE) { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if !CONSTANT_TRUE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if (!CONSTANT_TRUE) { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if (!CONSTANT_TRUE) { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if !(CONSTANT_TRUE) { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if !!CONSTANT_TRUE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if CONSTANT_TRUE == true { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if CONSTANT_TRUE == false { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if !(CONSTANT_TRUE == true) { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if !(CONSTANT_TRUE == false) { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } +} + +@test +test_simple_proc_false :: proc(t: ^testing.T) { + if false_result() { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if !false_result() { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } +} + +@test +test_simple_proc_true :: proc(t: ^testing.T) { + if true_result() { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if !true_result() { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } +} + +@test +test_const_false_const_false :: proc(t: ^testing.T) { + if CONSTANT_FALSE || CONSTANT_FALSE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if CONSTANT_FALSE && CONSTANT_FALSE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + + if !CONSTANT_FALSE || CONSTANT_FALSE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if !CONSTANT_FALSE && CONSTANT_FALSE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + + if CONSTANT_FALSE || !CONSTANT_FALSE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if CONSTANT_FALSE && !CONSTANT_FALSE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + + if !(CONSTANT_FALSE || CONSTANT_FALSE) { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if !(CONSTANT_FALSE && CONSTANT_FALSE) { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } +} + +@test +test_const_false_const_true :: proc(t: ^testing.T) { + if CONSTANT_FALSE || CONSTANT_TRUE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if CONSTANT_FALSE && CONSTANT_TRUE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + + if !CONSTANT_FALSE || CONSTANT_TRUE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if !CONSTANT_FALSE && CONSTANT_TRUE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + + if CONSTANT_FALSE || !CONSTANT_TRUE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if CONSTANT_FALSE && !CONSTANT_TRUE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + + if !(CONSTANT_FALSE || CONSTANT_TRUE) { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if !(CONSTANT_FALSE && CONSTANT_TRUE) { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } +} + +@test +test_const_true_const_false :: proc(t: ^testing.T) { + if CONSTANT_TRUE || CONSTANT_FALSE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if CONSTANT_TRUE && CONSTANT_FALSE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + + if !CONSTANT_TRUE || CONSTANT_FALSE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if !CONSTANT_TRUE && CONSTANT_FALSE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + + if CONSTANT_TRUE || !CONSTANT_FALSE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if CONSTANT_TRUE && !CONSTANT_FALSE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + + if !(CONSTANT_TRUE || CONSTANT_FALSE) { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if !(CONSTANT_TRUE && CONSTANT_FALSE) { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } +} + +@test +test_const_true_const_true :: proc(t: ^testing.T) { + if CONSTANT_TRUE || CONSTANT_TRUE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if CONSTANT_TRUE && CONSTANT_TRUE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + + if !CONSTANT_TRUE || CONSTANT_TRUE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if !CONSTANT_TRUE && CONSTANT_TRUE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + + if CONSTANT_TRUE || !CONSTANT_TRUE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if CONSTANT_TRUE && !CONSTANT_TRUE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + + if !(CONSTANT_TRUE || CONSTANT_TRUE) { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if !(CONSTANT_TRUE && CONSTANT_TRUE) { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } +} + +@test +test_proc_false_const_false :: proc(t: ^testing.T) { + if false_result() || CONSTANT_FALSE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if false_result() && CONSTANT_FALSE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + + if !(false_result() || CONSTANT_FALSE) { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if !(false_result() && CONSTANT_FALSE) { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } +} + +@test +test_proc_false_const_true :: proc(t: ^testing.T) { + if false_result() || CONSTANT_TRUE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if false_result() && CONSTANT_TRUE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + + if !(false_result() || CONSTANT_TRUE) { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if !(false_result() && CONSTANT_TRUE) { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } +} + +@test +test_proc_true_const_false :: proc(t: ^testing.T) { + if true_result() || CONSTANT_FALSE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if true_result() && CONSTANT_FALSE { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + + if !(true_result() || CONSTANT_FALSE) { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if !(true_result() && CONSTANT_FALSE) { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } +} + +@test +test_proc_true_const_true :: proc(t: ^testing.T) { + if true_result() || CONSTANT_TRUE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + if true_result() && CONSTANT_TRUE { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } else { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } + + if !(true_result() || CONSTANT_TRUE) { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } + if !(true_result() && CONSTANT_TRUE) { + tc.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + } else { + tc.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + } +} diff --git a/tests/issues/test_issue_829.odin b/tests/issues/test_issue_829.odin new file mode 100644 index 000000000..4ff3d71f1 --- /dev/null +++ b/tests/issues/test_issue_829.odin @@ -0,0 +1,33 @@ +// Tests issue #829 https://github.com/odin-lang/Odin/issues/829 +package test_issues + +import "core:fmt" +import "core:testing" +import tc "tests:common" + +/* Original issue #829 example */ + +env : map[string]proc(a, b : int) -> int = { + "+" = proc(a, b : int) -> int { + return a + b + }, +} + +test_orig :: proc() { + fmt.println(env["+"](1, 2)) +} + +main :: proc() { + t := testing.T{} + + test_orig() + + test_orig_ret(&t) + + tc.report(&t) +} + +test_orig_ret :: proc(t: ^testing.T) { + r := fmt.tprint(env["+"](1, 2)) + tc.expect(t, r == "3", fmt.tprintf("%s: \"%s\" != \"3\"\n", #procedure, r)) +} diff --git a/vendor/stb/image/stb_image.odin b/vendor/stb/image/stb_image.odin index 4f7e43171..eedce5f04 100644 --- a/vendor/stb/image/stb_image.odin +++ b/vendor/stb/image/stb_image.odin @@ -6,6 +6,7 @@ import c "core:c/libc" when ODIN_OS == .Windows { foreign import stbi "../lib/stb_image.lib" } when ODIN_OS == .Linux { foreign import stbi "../lib/stb_image.a" } +when ODIN_OS == .Darwin { foreign import stbi "../lib/stb_image.a" } #assert(size_of(b32) == size_of(c.int)) diff --git a/vendor/stb/image/stb_image_resize.odin b/vendor/stb/image/stb_image_resize.odin index 65bf3e4a9..362ec9315 100644 --- a/vendor/stb/image/stb_image_resize.odin +++ b/vendor/stb/image/stb_image_resize.odin @@ -4,6 +4,7 @@ import c "core:c/libc" when ODIN_OS == .Windows { foreign import lib "../lib/stb_image_resize.lib" } when ODIN_OS == .Linux { foreign import lib "../lib/stb_image_resize.a" } +when ODIN_OS == .Darwin { foreign import lib "../lib/stb_image_resize.a" } ////////////////////////////////////////////////////////////////////////////// // diff --git a/vendor/stb/image/stb_image_write.odin b/vendor/stb/image/stb_image_write.odin index 67f4299fa..b9433e821 100644 --- a/vendor/stb/image/stb_image_write.odin +++ b/vendor/stb/image/stb_image_write.odin @@ -4,6 +4,7 @@ import c "core:c/libc" when ODIN_OS == .Windows { foreign import stbiw "../lib/stb_image_write.lib" } when ODIN_OS == .Linux { foreign import stbiw "../lib/stb_image_write.a" } +when ODIN_OS == .Darwin { foreign import stbiw "../lib/stb_image_write.a" } write_func :: proc "c" (ctx: rawptr, data: rawptr, size: c.int) diff --git a/vendor/stb/rect_pack/stb_rect_pack.odin b/vendor/stb/rect_pack/stb_rect_pack.odin index f84f1cedc..c9f999bf7 100644 --- a/vendor/stb/rect_pack/stb_rect_pack.odin +++ b/vendor/stb/rect_pack/stb_rect_pack.odin @@ -6,6 +6,7 @@ import c "core:c/libc" when ODIN_OS == .Windows { foreign import lib "../lib/stb_rect_pack.lib" } when ODIN_OS == .Linux { foreign import lib "../lib/stb_rect_pack.a" } +when ODIN_OS == .Darwin { foreign import lib "../lib/stb_rect_pack.a" } Coord :: distinct c.int _MAXVAL :: max(Coord) diff --git a/vendor/stb/truetype/stb_truetype.odin b/vendor/stb/truetype/stb_truetype.odin index cf4af15e9..b51cb037f 100644 --- a/vendor/stb/truetype/stb_truetype.odin +++ b/vendor/stb/truetype/stb_truetype.odin @@ -5,6 +5,7 @@ import stbrp "vendor:stb/rect_pack" when ODIN_OS == .Windows { foreign import stbtt "../lib/stb_truetype.lib" } when ODIN_OS == .Linux { foreign import stbtt "../lib/stb_truetype.a" } +when ODIN_OS == .Darwin { foreign import stbtt "../lib/stb_truetype.a" } /////////////////////////////////////////////////////////////////////////////// diff --git a/vendor/stb/vorbis/stb_vorbis.odin b/vendor/stb/vorbis/stb_vorbis.odin index f35b58e04..43b9bc715 100644 --- a/vendor/stb/vorbis/stb_vorbis.odin +++ b/vendor/stb/vorbis/stb_vorbis.odin @@ -5,6 +5,7 @@ import c "core:c/libc" when ODIN_OS == .Windows { foreign import lib "../lib/stb_vorbis.lib" } when ODIN_OS == .Linux { foreign import lib "../lib/stb_vorbis.a" } +when ODIN_OS == .Darwin { foreign import lib "../lib/stb_vorbis.a" }