Merge branch 'master' into bill/raddebugger-custom-section

This commit is contained in:
gingerBill
2025-05-22 16:04:42 +01:00
committed by GitHub
146 changed files with 11910 additions and 1692 deletions
+49 -47
View File
@@ -75,32 +75,35 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
# MacOS 13 runs on Intel, 14 runs on ARM # MacOS 13 runs on Intel, 14 runs on ARM
os: [ubuntu-latest, macos-13, macos-14] os: [macos-13, macos-14, ubuntu-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel' || 'Ubuntu') }} Build, Check, and Test name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel') || (matrix.os == 'ubuntu-latest' && 'Ubuntu') }} Build, Check, and Test
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- uses: actions/checkout@v4
- name: Download LLVM (Linux) - uses: actions/checkout@v4
if: matrix.os == 'ubuntu-latest'
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20
echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
- name: Download LLVM (MacOS Intel) - name: Download LLVM (MacOS Intel)
if: matrix.os == 'macos-13' if: matrix.os == 'macos-13'
run: | run: |
brew update brew update
brew install llvm@20 lua@5.4 lld brew install llvm@20 lua@5.4 lld
echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
- name: Download LLVM (MacOS ARM) - name: Download LLVM (MacOS ARM)
if: matrix.os == 'macos-14' if: matrix.os == 'macos-14'
run: | run: |
brew update brew update
brew install llvm@20 wasmtime lua@5.4 lld brew install llvm@20 wasmtime lua@5.4 lld
echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
- name: Download LLVM (Ubuntu)
if: matrix.os == 'ubuntu-latest'
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20
echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
- name: Build Odin - name: Build Odin
run: ./build_odin.sh release run: ./build_odin.sh release
@@ -124,53 +127,52 @@ jobs:
- name: Odin check vendor/sdl3 - name: Odin check vendor/sdl3
run: ./odin check vendor/sdl3 -strict-style -vet -disallow-do -no-entry-point run: ./odin check vendor/sdl3 -strict-style -vet -disallow-do -no-entry-point
- name: Normal Core library tests - name: Normal Core library tests
run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Optimized Core library tests - name: Optimized Core library tests
run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Vendor library tests - name: Vendor library tests
run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Internals tests - name: Internals tests
run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: GitHub Issue tests - name: GitHub Issue tests
run: | run: |
cd tests/issues cd tests/issues
./run.sh ./run.sh
- name: Check benchmarks
run: ./odin check tests/benchmark -vet -strict-style -no-entry-point
- name: Odin check examples/all for Linux i386
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
if: matrix.os == 'ubuntu-latest'
- name: Odin check examples/all for Linux arm64
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
if: matrix.os == 'ubuntu-latest'
- name: Odin check examples/all for FreeBSD amd64
run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Odin check examples/all for OpenBSD amd64
run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Odin check vendor/sdl3 for Linux i386
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
if: matrix.os == 'ubuntu-latest'
- name: Odin check vendor/sdl3 for Linux arm64
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
if: matrix.os == 'ubuntu-latest'
- name: Odin check vendor/sdl3 for FreeBSD amd64
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Odin check vendor/sdl3 for OpenBSD amd64
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Run demo on WASI WASM32 - name: Run demo on WASI WASM32
run: | run: |
./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo ./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo
wasmtime ./demo.wasm wasmtime ./demo.wasm
if: matrix.os == 'macos-14' if: matrix.os == 'macos-14'
- name: Check benchmarks
run: ./odin check tests/benchmark -vet -strict-style -no-entry-point
- name: Odin check examples/all for Linux i386
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
- name: Odin check examples/all for Linux arm64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
- name: Odin check examples/all for FreeBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
- name: Odin check examples/all for OpenBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
- name: Odin check vendor/sdl3 for Linux i386
if: matrix.os == 'ubuntu-latest'
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
- name: Odin check vendor/sdl3 for Linux arm64
if: matrix.os == 'ubuntu-latest'
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
- name: Odin check vendor/sdl3 for FreeBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
- name: Odin check vendor/sdl3 for OpenBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64
build_windows: build_windows:
name: Windows Build, Check, and Test name: Windows Build, Check, and Test
runs-on: windows-2022 runs-on: windows-2022
@@ -215,23 +217,23 @@ jobs:
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Optimized core library tests - name: Optimized core library tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Vendor library tests - name: Vendor library tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
copy vendor\lua\5.4\windows\*.dll . copy vendor\lua\5.4\windows\*.dll .
odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Odin internals tests - name: Odin internals tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Check issues - name: Check issues
shell: cmd shell: cmd
run: | run: |
+2 -1
View File
@@ -293,5 +293,6 @@ build.sh
# RAD debugger project file # RAD debugger project file
*.raddbg *.raddbg
*.rdi
tests/issues/build/*
misc/featuregen/featuregen misc/featuregen/featuregen
+2 -1
View File
@@ -119,7 +119,8 @@ jmag :: proc(value: Quaternion) -> Float ---
kmag :: proc(value: Quaternion) -> Float --- kmag :: proc(value: Quaternion) -> Float ---
conj :: proc(value: Complex_Or_Quaternion) -> Complex_Or_Quaternion --- conj :: proc(value: Complex_Or_Quaternion) -> Complex_Or_Quaternion ---
expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) --- expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
compress_values :: proc(values: ...) -> Struct_Or_Array_Like_Type ---
min :: proc(values: ..T) -> T --- min :: proc(values: ..T) -> T ---
max :: proc(values: ..T) -> T --- max :: proc(values: ..T) -> T ---
+7 -1
View File
@@ -221,6 +221,9 @@ type_map_cell_info :: proc($T: typeid) -> ^runtime.Map_Cell_Info ---
type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) --- type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) ---
type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_union(V) --- type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_union(V) ---
type_integer_to_unsigned :: proc($T: typeid) -> type where type_is_integer(T), !type_is_unsigned(T) ---
type_integer_to_signed :: proc($T: typeid) -> type where type_is_integer(T), type_is_unsigned(T) ---
type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) --- type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) ---
constant_utf16_cstring :: proc($literal: string) -> [^]u16 --- constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
@@ -357,15 +360,18 @@ x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
objc_object :: struct{} objc_object :: struct{}
objc_selector :: struct{} objc_selector :: struct{}
objc_class :: struct{} objc_class :: struct{}
objc_ivar :: struct{}
objc_id :: ^objc_object objc_id :: ^objc_object
objc_SEL :: ^objc_selector objc_SEL :: ^objc_selector
objc_Class :: ^objc_class objc_Class :: ^objc_class
objc_Ivar :: ^objc_ivar
objc_find_selector :: proc($name: string) -> objc_SEL --- objc_find_selector :: proc($name: string) -> objc_SEL ---
objc_register_selector :: proc($name: string) -> objc_SEL --- objc_register_selector :: proc($name: string) -> objc_SEL ---
objc_find_class :: proc($name: string) -> objc_Class --- objc_find_class :: proc($name: string) -> objc_Class ---
objc_register_class :: proc($name: string) -> objc_Class --- objc_register_class :: proc($name: string) -> objc_Class ---
objc_ivar_get :: proc(self: ^$T) -> ^$U ---
valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr --- valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr ---
+1
View File
@@ -1109,6 +1109,7 @@ __read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uin
when .Address in ODIN_SANITIZER_FLAGS { when .Address in ODIN_SANITIZER_FLAGS {
foreign { foreign {
@(require)
__asan_unpoison_memory_region :: proc "system" (address: rawptr, size: uint) --- __asan_unpoison_memory_region :: proc "system" (address: rawptr, size: uint) ---
} }
} }
+19 -6
View File
@@ -2,21 +2,34 @@
package runtime package runtime
@(priority_index=-1e6) @(priority_index=-1e6)
foreign import "system:Foundation.framework" foreign import ObjC "system:objc"
import "base:intrinsics" import "base:intrinsics"
objc_id :: ^intrinsics.objc_object objc_id :: ^intrinsics.objc_object
objc_Class :: ^intrinsics.objc_class objc_Class :: ^intrinsics.objc_class
objc_SEL :: ^intrinsics.objc_selector objc_SEL :: ^intrinsics.objc_selector
objc_Ivar :: ^intrinsics.objc_ivar
objc_BOOL :: bool
foreign Foundation {
objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class --- objc_IMP :: proc "c" (object: objc_id, sel: objc_SEL, #c_vararg args: ..any) -> objc_id
foreign ObjC {
sel_registerName :: proc "c" (name: cstring) -> objc_SEL --- sel_registerName :: proc "c" (name: cstring) -> objc_SEL ---
objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 --- objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 ---
objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 --- objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 ---
objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class ---
objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
objc_registerClassPair :: proc "c" (cls : objc_Class) ---
class_addMethod :: proc "c" (cls: objc_Class, name: objc_SEL, imp: objc_IMP, types: cstring) -> objc_BOOL ---
class_addIvar :: proc "c" (cls: objc_Class, name: cstring, size: uint, alignment: u8, types: cstring) -> objc_BOOL ---
class_getInstanceVariable :: proc "c" (cls : objc_Class, name: cstring) -> objc_Ivar ---
class_getInstanceSize :: proc "c" (cls : objc_Class) -> uint ---
ivar_getOffset :: proc "c" (v: objc_Ivar) -> uintptr ---
} }
+18 -24
View File
@@ -19,16 +19,27 @@ if "%VSCMD_ARG_TGT_ARCH%" neq "x64" (
) )
) )
where /Q git.exe || goto skip_git_hash
if not exist .git\ goto skip_git_hash
for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m-%%d" --no-patch --no-notes HEAD') do (
set CURR_DATE_TIME=%%i
set GIT_SHA=%%j
)
if %ERRORLEVEL% equ 0 (
goto have_git_hash_and_date
)
:skip_git_hash
pushd misc pushd misc
cl /nologo get-date.c cl /nologo get-date.c
popd for /f %%i in ('get-date') do (
for /f %%i in ('misc\get-date') do (
set CURR_DATE_TIME=%%i set CURR_DATE_TIME=%%i
rem Don't set GIT_SHA
) )
popd
:have_git_hash_and_date
set curr_year=%CURR_DATE_TIME:~0,4% set curr_year=%CURR_DATE_TIME:~0,4%
set curr_month=%CURR_DATE_TIME:~4,2% set curr_month=%CURR_DATE_TIME:~5,2%
set curr_day=%CURR_DATE_TIME:~6,2% set curr_day=%CURR_DATE_TIME:~8,2%
:: Make sure this is a decent name and not generic :: Make sure this is a decent name and not generic
set exe_name=odin.exe set exe_name=odin.exe
@@ -61,31 +72,14 @@ if %release_mode% equ 0 (
set V4=0 set V4=0
set odin_version_full="%V1%.%V2%.%V3%.%V4%" set odin_version_full="%V1%.%V2%.%V3%.%V4%"
set odin_version_raw="dev-%V1%-%V2%" set odin_version_raw="dev-%V1%-%V2%"
set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF
rem Parse source code as utf-8 even on shift-jis and other codepages rem Parse source code as utf-8 even on shift-jis and other codepages
rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170 rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170
set compiler_flags= %compiler_flags% /utf-8 set compiler_flags= %compiler_flags% /utf-8
set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\" set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\" -DGIT_SHA=\"%GIT_SHA%\"
rem fileversion is defined as {Major,Minor,Build,Private: u16} so a bit limited rem fileversion is defined as {Major,Minor,Build,Private: u16} so a bit limited
set rc_flags=-nologo ^ set rc_flags="-DGIT_SHA=%GIT_SHA% -DVP=dev-%V1%-%V2%:%GIT_SHA% nologo -DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% -DVF=%odin_version_full% -DNIGHTLY=%nightly%"
-DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% ^
-DVF=%odin_version_full% -DNIGHTLY=%nightly%
where /Q git.exe || goto skip_git_hash
if not exist .git\ goto skip_git_hash
for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m" --no-patch --no-notes HEAD') do (
set odin_version_raw=dev-%%i
set GIT_SHA=%%j
)
if %ERRORLEVEL% equ 0 (
set compiler_defines=%compiler_defines% -DGIT_SHA=\"%GIT_SHA%\"
set rc_flags=%rc_flags% -DGIT_SHA=%GIT_SHA% -DVP=%odin_version_raw%:%GIT_SHA%
) else (
set rc_flags=%rc_flags% -DVP=%odin_version_raw%
)
:skip_git_hash
if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY
+4 -1
View File
@@ -6,7 +6,6 @@ set -eu
: ${LDFLAGS=} : ${LDFLAGS=}
: ${LLVM_CONFIG=} : ${LLVM_CONFIG=}
CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\""
CXXFLAGS="$CXXFLAGS -std=c++14" CXXFLAGS="$CXXFLAGS -std=c++14"
DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value" DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
LDFLAGS="$LDFLAGS -pthread -lm" LDFLAGS="$LDFLAGS -pthread -lm"
@@ -15,8 +14,12 @@ OS_NAME="$(uname -s)"
if [ -d ".git" ] && [ -n "$(command -v git)" ]; then if [ -d ".git" ] && [ -n "$(command -v git)" ]; then
GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD) GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD)
GIT_DATE=$(git show "--pretty=%cd" "--date=format:%Y-%m" --no-patch --no-notes HEAD)
CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\"" CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""
else
GIT_DATE=$(date +"%Y-%m")
fi fi
CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$GIT_DATE\""
error() { error() {
printf "ERROR: %s\n" "$1" printf "ERROR: %s\n" "$1"
@@ -133,12 +133,10 @@ pop_safe :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value:
remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) { remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) {
n := builtin.len(pq.queue) n := builtin.len(pq.queue)
if 0 <= i && i < n { if 0 <= i && i < n {
if n != i { pq.swap(pq.queue[:], i, n-1)
pq.swap(pq.queue[:], i, n) _shift_down(pq, i, n-1)
_shift_down(pq, i, n) _shift_up(pq, i)
_shift_up(pq, i) value, ok = builtin.pop(&pq.queue), true
}
value, ok = builtin.pop_safe(&pq.queue)
} }
return return
} }
+14 -8
View File
@@ -406,6 +406,9 @@ unmarshal_expect_token :: proc(p: ^Parser, kind: Token_Kind, loc := #caller_loca
return prev return prev
} }
// Struct tags can include not only the name of the JSON key, but also a tag such as `omitempty`.
// Example: `json:"key_name,omitempty"`
// This returns the first field as `json_name`, and the rest are returned as `extra`.
@(private) @(private)
json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) { json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) {
json_name = value json_name = value
@@ -442,12 +445,6 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
unmarshal_expect_token(p, .Colon) unmarshal_expect_token(p, .Colon)
field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
prev_set := field_used[offset/8] & byte(offset&7) != 0
field_used[offset/8] |= byte(offset&7)
return prev_set
}
field_used_bytes := (reflect.size_of_typeid(ti.id)+7)/8 field_used_bytes := (reflect.size_of_typeid(ti.id)+7)/8
field_used := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types. field_used := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types.
intrinsics.mem_zero(field_used, field_used_bytes) intrinsics.mem_zero(field_used, field_used_bytes)
@@ -465,7 +462,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
if use_field_idx < 0 { if use_field_idx < 0 {
for field, field_idx in fields { for field, field_idx in fields {
if key == field.name { tag_value := reflect.struct_tag_get(field.tag, "json")
json_name, _ := json_name_from_tag_value(tag_value)
if json_name == "" && key == field.name {
use_field_idx = field_idx use_field_idx = field_idx
break break
} }
@@ -486,7 +485,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
} }
} }
if field.name == key || (field.tag != "" && reflect.struct_tag_get(field.tag, "json") == key) { tag_value := reflect.struct_tag_get(field.tag, "json")
json_name, _ := json_name_from_tag_value(tag_value)
if (json_name == "" && field.name == key) || json_name == key {
offset = field.offset offset = field.offset
type = field.type type = field.type
found = true found = true
@@ -508,6 +509,11 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
} }
if field_found { if field_found {
field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
prev_set := field_used[offset/8] & byte(offset&7) != 0
field_used[offset/8] |= byte(offset&7)
return prev_set
}
if field_test(field_used, offset) { if field_test(field_used, offset) {
return .Multiple_Use_Field return .Multiple_Use_Field
} }
+53 -11
View File
@@ -2,10 +2,12 @@
#+build !orca #+build !orca
package log package log
import "core:encoding/ansi" import "base:runtime"
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
import "core:os" import "core:os"
import "core:terminal"
import "core:terminal/ansi"
import "core:time" import "core:time"
Level_Headers := [?]string{ Level_Headers := [?]string{
@@ -37,11 +39,36 @@ File_Console_Logger_Data :: struct {
ident: string, ident: string,
} }
@(private) global_subtract_stdout_options: Options
@(private) global_subtract_stderr_options: Options
@(init, private)
init_standard_stream_status :: proc() {
// NOTE(Feoramund): While it is technically possible for these streams to
// be redirected during the runtime of the program, the cost of checking on
// every single log message is not worth it to support such an
// uncommonly-used feature.
if terminal.color_enabled {
// This is done this way because it's possible that only one of these
// streams could be redirected to a file.
if !terminal.is_terminal(os.stdout) {
global_subtract_stdout_options = {.Terminal_Color}
}
if !terminal.is_terminal(os.stderr) {
global_subtract_stderr_options = {.Terminal_Color}
}
} else {
// Override any terminal coloring.
global_subtract_stdout_options = {.Terminal_Color}
global_subtract_stderr_options = {.Terminal_Color}
}
}
create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger { create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
data := new(File_Console_Logger_Data, allocator) data := new(File_Console_Logger_Data, allocator)
data.file_handle = h data.file_handle = h
data.ident = ident data.ident = ident
return Logger{file_console_logger_proc, data, lowest, opt} return Logger{file_logger_proc, data, lowest, opt}
} }
destroy_file_logger :: proc(log: Logger, allocator := context.allocator) { destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
@@ -56,19 +83,15 @@ create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logg
data := new(File_Console_Logger_Data, allocator) data := new(File_Console_Logger_Data, allocator)
data.file_handle = os.INVALID_HANDLE data.file_handle = os.INVALID_HANDLE
data.ident = ident data.ident = ident
return Logger{file_console_logger_proc, data, lowest, opt} return Logger{console_logger_proc, data, lowest, opt}
} }
destroy_console_logger :: proc(log: Logger, allocator := context.allocator) { destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
free(log.data, allocator) free(log.data, allocator)
} }
file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { @(private)
data := cast(^File_Console_Logger_Data)logger_data _file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) {
h: os.Handle = os.stdout if level <= Level.Error else os.stderr
if data.file_handle != os.INVALID_HANDLE {
h = data.file_handle
}
backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths. backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
buf := strings.builder_from_bytes(backing[:]) buf := strings.builder_from_bytes(backing[:])
@@ -86,13 +109,32 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
fmt.sbprintf(&buf, "[{}] ", os.current_thread_id()) fmt.sbprintf(&buf, "[{}] ", os.current_thread_id())
} }
if data.ident != "" { if ident != "" {
fmt.sbprintf(&buf, "[%s] ", data.ident) fmt.sbprintf(&buf, "[%s] ", ident)
} }
//TODO(Hoej): When we have better atomics and such, make this thread-safe //TODO(Hoej): When we have better atomics and such, make this thread-safe
fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text) fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
} }
file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
data := cast(^File_Console_Logger_Data)logger_data
_file_console_logger_proc(data.file_handle, data.ident, level, text, options, location)
}
console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
options := options
data := cast(^File_Console_Logger_Data)logger_data
h: os.Handle = ---
if level < Level.Error {
h = os.stdout
options -= global_subtract_stdout_options
} else {
h = os.stderr
options -= global_subtract_stderr_options
}
_file_console_logger_proc(h, data.ident, level, text, options, location)
}
do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) { do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
RESET :: ansi.CSI + ansi.RESET + ansi.SGR RESET :: ansi.CSI + ansi.RESET + ansi.SGR
+17 -17
View File
@@ -1276,28 +1276,28 @@ parse_unrolled_for_loop :: proc(p: ^Parser, inline_tok: tokenizer.Token) -> ^ast
args = make([dynamic]^ast.Expr) args = make([dynamic]^ast.Expr)
for p.curr_tok.kind != .Close_Paren && for p.curr_tok.kind != .Close_Paren &&
p.curr_tok.kind != .EOF { p.curr_tok.kind != .EOF {
arg := parse_value(p) arg := parse_value(p)
if p.curr_tok.kind == .Eq { if p.curr_tok.kind == .Eq {
eq := expect_token(p, .Eq) eq := expect_token(p, .Eq)
if arg != nil { if arg != nil {
if _, ok := arg.derived.(^ast.Ident); !ok { if _, ok := arg.derived.(^ast.Ident); !ok {
error(p, arg.pos, "expected an identifier for 'key=value'") error(p, arg.pos, "expected an identifier for 'key=value'")
} }
} }
value := parse_value(p) value := parse_value(p)
fv := ast.new(ast.Field_Value, arg.pos, value) fv := ast.new(ast.Field_Value, arg.pos, value)
fv.field = arg fv.field = arg
fv.sep = eq.pos fv.sep = eq.pos
fv.value = value fv.value = value
arg = fv arg = fv
} }
append(&args, arg) append(&args, arg)
allow_token(p, .Comma) or_break allow_token(p, .Comma) or_break
} }
} }
p.expr_level -= 1 p.expr_level -= 1
+42 -41
View File
@@ -8,43 +8,13 @@ file_allocator :: proc() -> runtime.Allocator {
return heap_allocator() return heap_allocator()
} }
temp_allocator_proc :: runtime.arena_allocator_proc
@(private="file") @(private="file")
MAX_TEMP_ARENA_COUNT :: 2 MAX_TEMP_ARENA_COUNT :: 2
@(private="file")
MAX_TEMP_ARENA_COLLISIONS :: MAX_TEMP_ARENA_COUNT - 1
@(private="file", thread_local) @(private="file", thread_local)
global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena
@(private="file", thread_local)
global_default_temp_allocator_index: uint
@(require_results)
temp_allocator :: proc() -> runtime.Allocator {
arena := &global_default_temp_allocator_arenas[global_default_temp_allocator_index]
if arena.backing_allocator.procedure == nil {
arena.backing_allocator = heap_allocator()
}
return runtime.Allocator{
procedure = temp_allocator_proc,
data = arena,
}
}
@(require_results)
temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) {
temp = runtime.arena_temp_begin(&global_default_temp_allocator_arenas[global_default_temp_allocator_index], loc)
return
}
temp_allocator_temp_end :: proc(temp: runtime.Arena_Temp, loc := #caller_location) {
runtime.arena_temp_end(temp, loc)
}
@(fini, private) @(fini, private)
temp_allocator_fini :: proc() { temp_allocator_fini :: proc() {
for &arena in global_default_temp_allocator_arenas { for &arena in global_default_temp_allocator_arenas {
@@ -53,18 +23,49 @@ temp_allocator_fini :: proc() {
global_default_temp_allocator_arenas = {} global_default_temp_allocator_arenas = {}
} }
TEMP_ALLOCATOR_GUARD_END :: proc(temp: runtime.Arena_Temp, loc := #caller_location) { Temp_Allocator :: struct {
runtime.arena_temp_end(temp, loc) using arena: ^runtime.Arena,
if temp.arena != nil { using allocator: runtime.Allocator,
global_default_temp_allocator_index = (global_default_temp_allocator_index-1)%MAX_TEMP_ARENA_COUNT tmp: runtime.Arena_Temp,
} loc: runtime.Source_Code_Location,
}
TEMP_ALLOCATOR_GUARD_END :: proc(temp: Temp_Allocator) {
runtime.arena_temp_end(temp.tmp, temp.loc)
} }
@(deferred_out=TEMP_ALLOCATOR_GUARD_END) @(deferred_out=TEMP_ALLOCATOR_GUARD_END)
TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) { TEMP_ALLOCATOR_GUARD :: #force_inline proc(collisions: []runtime.Allocator, loc := #caller_location) -> Temp_Allocator {
global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT assert(len(collisions) <= MAX_TEMP_ARENA_COLLISIONS, "Maximum collision count exceeded. MAX_TEMP_ARENA_COUNT must be increased!")
tmp := temp_allocator_temp_begin(loc) good_arena: ^runtime.Arena
return tmp, loc for i in 0..<MAX_TEMP_ARENA_COUNT {
good_arena = &global_default_temp_allocator_arenas[i]
for c in collisions {
if good_arena == c.data {
good_arena = nil
}
}
if good_arena != nil {
break
}
}
assert(good_arena != nil)
if good_arena.backing_allocator.procedure == nil {
good_arena.backing_allocator = heap_allocator()
}
tmp := runtime.arena_temp_begin(good_arena, loc)
return { good_arena, runtime.arena_allocator(good_arena), tmp, loc }
}
temp_allocator_begin :: runtime.arena_temp_begin
temp_allocator_end :: runtime.arena_temp_end
@(deferred_out=_temp_allocator_end)
temp_allocator_scope :: proc(tmp: Temp_Allocator) -> (runtime.Arena_Temp) {
return temp_allocator_begin(tmp.arena)
}
@(private="file")
_temp_allocator_end :: proc(tmp: runtime.Arena_Temp) {
temp_allocator_end(tmp)
} }
@(init, private) @(init, private)
+43 -16
View File
@@ -2,6 +2,7 @@ package os2
import "base:runtime" import "base:runtime"
import "core:slice" import "core:slice"
import "core:strings"
read_dir :: read_directory read_dir :: read_directory
@@ -18,12 +19,12 @@ read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files
size = 100 size = 100
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
it := read_directory_iterator_create(f) it := read_directory_iterator_create(f)
defer _read_directory_iterator_destroy(&it) defer _read_directory_iterator_destroy(&it)
dfi := make([dynamic]File_Info, 0, size, temp_allocator()) dfi := make([dynamic]File_Info, 0, size, temp_allocator)
defer if err != nil { defer if err != nil {
for fi in dfi { for fi in dfi {
file_info_delete(fi, allocator) file_info_delete(fi, allocator)
@@ -194,28 +195,54 @@ read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info,
} }
// Recursively copies a directory to `dst` from `src` // Recursively copies a directory to `dst` from `src`
copy_directory :: proc(dst, src: string, dst_perm := 0o755) -> Error { copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error {
switch err := make_directory_all(dst, dst_perm); err { when #defined(_copy_directory_all_native) {
case nil, .Exist: return _copy_directory_all_native(dst, src, dst_perm)
// okay } else {
case: return _copy_directory_all(dst, src, dst_perm)
}
}
@(private)
_copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error {
err := make_directory(dst, dst_perm)
if err != nil && err != .Exist {
return err return err
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
file_infos := read_all_directory_by_path(src, temp_allocator()) or_return abs_src := get_absolute_path(src, temp_allocator) or_return
for fi in file_infos { abs_dst := get_absolute_path(dst, temp_allocator) or_return
TEMP_ALLOCATOR_GUARD()
dst_path := join_path({dst, fi.name}, temp_allocator()) or_return dst_buf := make([dynamic]byte, 0, len(abs_dst) + 256, temp_allocator) or_return
src_path := fi.fullpath
if fi.type == .Directory { w: Walker
copy_directory(dst_path, src_path) or_return walker_init_path(&w, src)
defer walker_destroy(&w)
for info in walker_walk(&w) {
_ = walker_error(&w) or_break
rel := strings.trim_prefix(info.fullpath, abs_src)
non_zero_resize(&dst_buf, 0)
reserve(&dst_buf, len(abs_dst) + len(Path_Separator_String) + len(rel)) or_return
append(&dst_buf, abs_dst)
append(&dst_buf, Path_Separator_String)
append(&dst_buf, rel)
if info.type == .Directory {
err = make_directory(string(dst_buf[:]), dst_perm)
if err != nil && err != .Exist {
return err
}
} else { } else {
copy_file(dst_path, src_path) or_return copy_file(string(dst_buf[:]), info.fullpath) or_return
} }
} }
_ = walker_error(&w) or_return
return nil return nil
} }
+2 -1
View File
@@ -78,7 +78,8 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
it.impl.prev_fi = fi it.impl.prev_fi = fi
if err != nil { if err != nil {
path, _ := _get_full_path(entry_fd, temp_allocator()) temp_allocator := TEMP_ALLOCATOR_GUARD({})
path, _ := _get_full_path(entry_fd, temp_allocator)
read_directory_iterator_set_error(it, path, err) read_directory_iterator_set_error(it, path, err)
} }
+17
View File
@@ -0,0 +1,17 @@
#+private
package os2
import "core:sys/darwin"
_copy_directory_all_native :: proc(dst, src: string, dst_perm := 0o755) -> (err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
csrc := clone_to_cstring(src, temp_allocator) or_return
cdst := clone_to_cstring(dst, temp_allocator) or_return
if darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL + {.RECURSIVE}) < 0 {
err = _get_platform_error()
}
return
}
+5 -5
View File
@@ -14,7 +14,9 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al
if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 { if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 {
return return
} }
path := concatenate({base_path, `\`, win32_wstring_to_utf8(raw_data(d.cFileName[:]), temp_allocator()) or_else ""}, allocator) or_return
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
path := concatenate({base_path, `\`, win32_wstring_to_utf8(raw_data(d.cFileName[:]), temp_allocator) or_else ""}, allocator) or_return
handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0) handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0)
defer win32.CloseHandle(handle) defer win32.CloseHandle(handle)
@@ -49,8 +51,6 @@ Read_Directory_Iterator_Impl :: struct {
@(require_results) @(require_results)
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
TEMP_ALLOCATOR_GUARD()
for !it.impl.no_more_files { for !it.impl.no_more_files {
err: Error err: Error
file_info_delete(it.impl.prev_fi, file_allocator()) file_info_delete(it.impl.prev_fi, file_allocator())
@@ -116,9 +116,9 @@ _read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
wpath = impl.wname[:i] wpath = impl.wname[:i]
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
wpath_search := make([]u16, len(wpath)+3, temp_allocator()) wpath_search := make([]u16, len(wpath)+3, temp_allocator)
copy(wpath_search, wpath) copy(wpath_search, wpath)
wpath_search[len(wpath)+0] = '\\' wpath_search[len(wpath)+0] = '\\'
wpath_search[len(wpath)+1] = '*' wpath_search[len(wpath)+1] = '*'
+7 -7
View File
@@ -12,9 +12,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
ckey := strings.clone_to_cstring(key, temp_allocator()) ckey := strings.clone_to_cstring(key, temp_allocator)
cval := posix.getenv(ckey) cval := posix.getenv(ckey)
if cval == nil { if cval == nil {
return return
@@ -27,10 +27,10 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
} }
_set_env :: proc(key, value: string) -> (err: Error) { _set_env :: proc(key, value: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
ckey := strings.clone_to_cstring(key, temp_allocator()) or_return ckey := strings.clone_to_cstring(key, temp_allocator) or_return
cval := strings.clone_to_cstring(value, temp_allocator()) or_return cval := strings.clone_to_cstring(value, temp_allocator) or_return
if posix.setenv(ckey, cval, true) != nil { if posix.setenv(ckey, cval, true) != nil {
err = _get_platform_error_from_errno() err = _get_platform_error_from_errno()
@@ -39,9 +39,9 @@ _set_env :: proc(key, value: string) -> (err: Error) {
} }
_unset_env :: proc(key: string) -> (ok: bool) { _unset_env :: proc(key: string) -> (ok: bool) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
ckey := strings.clone_to_cstring(key, temp_allocator()) ckey := strings.clone_to_cstring(key, temp_allocator)
ok = posix.unsetenv(ckey) == .OK ok = posix.unsetenv(ckey) == .OK
return return
+2 -2
View File
@@ -39,9 +39,9 @@ build_env :: proc() -> (err: Error) {
g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return
defer if err != nil { delete(g_env_buf, file_allocator()) } defer if err != nil { delete(g_env_buf, file_allocator()) }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
envs := make([]cstring, num_envs, temp_allocator()) or_return envs := make([]cstring, num_envs, temp_allocator) or_return
_err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf)) _err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf))
if _err != nil { if _err != nil {
+10 -10
View File
@@ -8,8 +8,8 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
if key == "" { if key == "" {
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
wkey, _ := win32_utf8_to_wstring(key, temp_allocator()) wkey, _ := win32_utf8_to_wstring(key, temp_allocator)
n := win32.GetEnvironmentVariableW(wkey, nil, 0) n := win32.GetEnvironmentVariableW(wkey, nil, 0)
if n == 0 { if n == 0 {
@@ -20,7 +20,7 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return "", true return "", true
} }
b := make([]u16, n+1, temp_allocator()) b := make([]u16, n+1, temp_allocator)
n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
if n == 0 { if n == 0 {
@@ -37,9 +37,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
} }
_set_env :: proc(key, value: string) -> Error { _set_env :: proc(key, value: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
k := win32_utf8_to_wstring(key, temp_allocator()) or_return k := win32_utf8_to_wstring(key, temp_allocator) or_return
v := win32_utf8_to_wstring(value, temp_allocator()) or_return v := win32_utf8_to_wstring(value, temp_allocator) or_return
if !win32.SetEnvironmentVariableW(k, v) { if !win32.SetEnvironmentVariableW(k, v) {
return _get_platform_error() return _get_platform_error()
@@ -48,14 +48,14 @@ _set_env :: proc(key, value: string) -> Error {
} }
_unset_env :: proc(key: string) -> bool { _unset_env :: proc(key: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
k, _ := win32_utf8_to_wstring(key, temp_allocator()) k, _ := win32_utf8_to_wstring(key, temp_allocator)
return bool(win32.SetEnvironmentVariableW(k, nil)) return bool(win32.SetEnvironmentVariableW(k, nil))
} }
_clear_env :: proc() { _clear_env :: proc() {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
envs, _ := environ(temp_allocator()) envs, _ := environ(temp_allocator)
for env in envs { for env in envs {
for j in 1..<len(env) { for j in 1..<len(env) {
if env[j] == '=' { if env[j] == '=' {
+2 -2
View File
@@ -108,12 +108,12 @@ error_string :: proc(ferr: Error) -> string {
} }
print_error :: proc(f: ^File, ferr: Error, msg: string) { print_error :: proc(f: ^File, ferr: Error, msg: string) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
err_str := error_string(ferr) err_str := error_string(ferr)
// msg + ": " + err_str + '\n' // msg + ": " + err_str + '\n'
length := len(msg) + 2 + len(err_str) + 1 length := len(msg) + 2 + len(err_str) + 1
buf := make([]u8, length, temp_allocator()) buf := make([]u8, length, temp_allocator)
copy(buf, msg) copy(buf, msg)
buf[len(msg)] = ':' buf[len(msg)] = ':'
+13 -4
View File
@@ -291,8 +291,8 @@ exists :: proc(path: string) -> bool {
@(require_results) @(require_results)
is_file :: proc(path: string) -> bool { is_file :: proc(path: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
fi, err := stat(path, temp_allocator()) fi, err := stat(path, temp_allocator)
if err != nil { if err != nil {
return false return false
} }
@@ -303,8 +303,8 @@ is_dir :: is_directory
@(require_results) @(require_results)
is_directory :: proc(path: string) -> bool { is_directory :: proc(path: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
fi, err := stat(path, temp_allocator()) fi, err := stat(path, temp_allocator)
if err != nil { if err != nil {
return false return false
} }
@@ -313,6 +313,15 @@ is_directory :: proc(path: string) -> bool {
copy_file :: proc(dst_path, src_path: string) -> Error { copy_file :: proc(dst_path, src_path: string) -> Error {
when #defined(_copy_file_native) {
return _copy_file_native(dst_path, src_path)
} else {
return _copy_file(dst_path, src_path)
}
}
@(private)
_copy_file :: proc(dst_path, src_path: string) -> Error {
src := open(src_path) or_return src := open(src_path) or_return
defer close(src) defer close(src)
+29 -29
View File
@@ -66,8 +66,8 @@ _standard_stream_init :: proc() {
} }
_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
// Just default to using O_NOCTTY because needing to open a controlling // Just default to using O_NOCTTY because needing to open a controlling
// terminal would be incredibly rare. This has no effect on files while // terminal would be incredibly rare. This has no effect on files while
@@ -299,8 +299,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
} }
_remove :: proc(name: string) -> Error { _remove :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS + {.NOFOLLOW}); errno == .NONE { if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS + {.NOFOLLOW}); errno == .NONE {
linux.close(fd) linux.close(fd)
@@ -311,25 +311,25 @@ _remove :: proc(name: string) -> Error {
} }
_rename :: proc(old_name, new_name: string) -> Error { _rename :: proc(old_name, new_name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
old_name_cstr := temp_cstring(old_name) or_return old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
new_name_cstr := temp_cstring(new_name) or_return new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr)) return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr))
} }
_link :: proc(old_name, new_name: string) -> Error { _link :: proc(old_name, new_name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
old_name_cstr := temp_cstring(old_name) or_return old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
new_name_cstr := temp_cstring(new_name) or_return new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
return _get_platform_error(linux.link(old_name_cstr, new_name_cstr)) return _get_platform_error(linux.link(old_name_cstr, new_name_cstr))
} }
_symlink :: proc(old_name, new_name: string) -> Error { _symlink :: proc(old_name, new_name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
old_name_cstr := temp_cstring(old_name) or_return old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
new_name_cstr := temp_cstring(new_name) or_return new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr)) return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr))
} }
@@ -352,14 +352,14 @@ _read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (st
} }
_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) { _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _read_link_cstr(name_cstr, allocator) return _read_link_cstr(name_cstr, allocator)
} }
_chdir :: proc(name: string) -> Error { _chdir :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.chdir(name_cstr)) return _get_platform_error(linux.chdir(name_cstr))
} }
@@ -369,8 +369,8 @@ _fchdir :: proc(f: ^File) -> Error {
} }
_chmod :: proc(name: string, mode: int) -> Error { _chmod :: proc(name: string, mode: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode)))) return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode))))
} }
@@ -381,15 +381,15 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
// NOTE: will throw error without super user priviledges // NOTE: will throw error without super user priviledges
_chown :: proc(name: string, uid, gid: int) -> Error { _chown :: proc(name: string, uid, gid: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid))) return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
} }
// NOTE: will throw error without super user priviledges // NOTE: will throw error without super user priviledges
_lchown :: proc(name: string, uid, gid: int) -> Error { _lchown :: proc(name: string, uid, gid: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid))) return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
} }
@@ -400,8 +400,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
} }
_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
times := [2]linux.Time_Spec { times := [2]linux.Time_Spec {
{ {
uint(atime._nsec) / uint(time.Second), uint(atime._nsec) / uint(time.Second),
@@ -431,8 +431,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
} }
_exists :: proc(name: string) -> bool { _exists :: proc(name: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr, _ := temp_cstring(name) name_cstr, _ := clone_to_cstring(name, temp_allocator)
return linux.access(name_cstr, linux.F_OK) == .NONE return linux.access(name_cstr, linux.F_OK) == .NONE
} }
@@ -440,8 +440,8 @@ _exists :: proc(name: string) -> bool {
_read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring } _read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring }
_read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) { _read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
name_cstr := clone_to_cstring(name, temp_allocator()) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _read_entire_pseudo_file_cstring(name_cstr, allocator) return _read_entire_pseudo_file_cstring(name_cstr, allocator)
} }
+36 -35
View File
@@ -69,8 +69,8 @@ _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Err
if .Trunc in flags { sys_flags += {.TRUNC} } if .Trunc in flags { sys_flags += {.TRUNC} }
if .Inheritable in flags { sys_flags -= {.CLOEXEC} } if .Inheritable in flags { sys_flags -= {.CLOEXEC} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm)) fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm))
if fd < 0 { if fd < 0 {
@@ -183,39 +183,39 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
return nil return nil
} }
_remove :: proc(name: string) -> Error { _remove :: proc(name: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.remove(cname) != 0 { if posix.remove(cname) != 0 {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_rename :: proc(old_path, new_path: string) -> Error { _rename :: proc(old_path, new_path: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cold := temp_cstring(old_path) cold := clone_to_cstring(old_path, temp_allocator) or_return
cnew := temp_cstring(new_path) cnew := clone_to_cstring(new_path, temp_allocator) or_return
if posix.rename(cold, cnew) != 0 { if posix.rename(cold, cnew) != 0 {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_link :: proc(old_name, new_name: string) -> Error { _link :: proc(old_name, new_name: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cold := temp_cstring(old_name) cold := clone_to_cstring(old_name, temp_allocator) or_return
cnew := temp_cstring(new_name) cnew := clone_to_cstring(new_name, temp_allocator) or_return
if posix.link(cold, cnew) != .OK { if posix.link(cold, cnew) != .OK {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_symlink :: proc(old_name, new_name: string) -> Error { _symlink :: proc(old_name, new_name: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cold := temp_cstring(old_name) cold := clone_to_cstring(old_name, temp_allocator) or_return
cnew := temp_cstring(new_name) cnew := clone_to_cstring(new_name, temp_allocator) or_return
if posix.symlink(cold, cnew) != .OK { if posix.symlink(cold, cnew) != .OK {
return _get_platform_error() return _get_platform_error()
} }
@@ -223,8 +223,8 @@ _symlink :: proc(old_name, new_name: string) -> Error {
} }
_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
buf: [dynamic]byte buf: [dynamic]byte
buf.allocator = allocator buf.allocator = allocator
@@ -268,9 +268,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
} }
} }
_chdir :: proc(name: string) -> Error { _chdir :: proc(name: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.chdir(cname) != .OK { if posix.chdir(cname) != .OK {
return _get_platform_error() return _get_platform_error()
} }
@@ -291,9 +291,9 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
return nil return nil
} }
_chmod :: proc(name: string, mode: int) -> Error { _chmod :: proc(name: string, mode: int) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK { if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK {
return _get_platform_error() return _get_platform_error()
} }
@@ -307,9 +307,9 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
return nil return nil
} }
_chown :: proc(name: string, uid, gid: int) -> Error { _chown :: proc(name: string, uid, gid: int) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
return _get_platform_error() return _get_platform_error()
} }
@@ -317,15 +317,15 @@ _chown :: proc(name: string, uid, gid: int) -> Error {
} }
_lchown :: proc(name: string, uid, gid: int) -> Error { _lchown :: proc(name: string, uid, gid: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { _chtimes :: proc(name: string, atime, mtime: time.Time) -> (err: Error) {
times := [2]posix.timeval{ times := [2]posix.timeval{
{ {
tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */ tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */
@@ -337,8 +337,8 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
}, },
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.utimes(cname, &times) != .OK { if posix.utimes(cname, &times) != .OK {
return _get_platform_error() return _get_platform_error()
@@ -365,8 +365,9 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
} }
_exists :: proc(path: string) -> bool { _exists :: proc(path: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cpath := temp_cstring(path) cpath, err := clone_to_cstring(path, temp_allocator)
if err != nil { return false }
return posix.access(cpath) == .OK return posix.access(cpath) == .OK
} }
+28
View File
@@ -3,6 +3,7 @@ package os2
import "base:runtime" import "base:runtime"
import "core:sys/darwin"
import "core:sys/posix" import "core:sys/posix"
_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
@@ -16,3 +17,30 @@ _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allo
return clone_to_cstring(string(cstring(&buf[0])), allocator) return clone_to_cstring(string(cstring(&buf[0])), allocator)
} }
_copy_file_native :: proc(dst_path, src_path: string) -> (err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
csrc := clone_to_cstring(src_path, temp_allocator) or_return
cdst := clone_to_cstring(dst_path, temp_allocator) or_return
// Disallow directories, as specified by the generic implementation.
stat: posix.stat_t
if posix.stat(csrc, &stat) != .OK {
err = _get_platform_error()
return
}
if posix.S_ISDIR(stat.st_mode) {
err = .Invalid_File
return
}
ret := darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL)
if ret < 0 {
err = _get_platform_error()
}
return
}
+2 -2
View File
@@ -7,8 +7,8 @@ import "base:runtime"
import "core:sys/posix" import "core:sys/posix"
_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator)
buf: [posix.PATH_MAX]byte buf: [posix.PATH_MAX]byte
path = posix.realpath(cname, raw_data(buf[:])) path = posix.realpath(cname, raw_data(buf[:]))
+32 -45
View File
@@ -12,30 +12,7 @@ import win32 "core:sys/windows"
INVALID_HANDLE :: ~uintptr(0) INVALID_HANDLE :: ~uintptr(0)
// NOTE(Jeroen): We don't translate mode flags for Linux when given to `chmod`. S_IWRITE :: 0o200
// Let's not do so for Windows for `chmod` or `read_directory_iterator` either.
// They're *not* portable between Windows and non-Windows platforms.
//
// It also leads to information loss as flags like Archive, Hidden and System have no equivalent there.
// We can of course parse them so we can set the `.Symlink` and `.Directory` type, but we shouldn't pretend
// that 0o644 is meaningful when returned as a mode.
// `C:\bootmgr` as an example has attributes read only, hidden, system, archive. In no way is it sensible to replace that with 0o444.
FILE_ATTRIBUTE_READONLY :: win32.FILE_ATTRIBUTE_READONLY // 0x00000001
FILE_ATTRIBUTE_HIDDEN :: win32.FILE_ATTRIBUTE_HIDDEN // 0x00000002
FILE_ATTRIBUTE_SYSTEM :: win32.FILE_ATTRIBUTE_SYSTEM // 0x00000004
FILE_ATTRIBUTE_DIRECTORY :: win32.FILE_ATTRIBUTE_DIRECTORY // 0x00000010
FILE_ATTRIBUTE_ARCHIVE :: win32.FILE_ATTRIBUTE_ARCHIVE // 0x00000020
FILE_ATTRIBUTE_DEVICE :: win32.FILE_ATTRIBUTE_DEVICE // 0x00000040
FILE_ATTRIBUTE_NORMAL :: win32.FILE_ATTRIBUTE_NORMAL // 0x00000080
FILE_ATTRIBUTE_TEMPORARY :: win32.FILE_ATTRIBUTE_TEMPORARY // 0x00000100
FILE_ATTRIBUTE_SPARSE_FILE :: win32.FILE_ATTRIBUTE_SPARSE_FILE // 0x00000200
FILE_ATTRIBUTE_REPARSE_Point :: win32.FILE_ATTRIBUTE_REPARSE_Point // 0x00000400
FILE_ATTRIBUTE_REPARSE_POINT :: win32.FILE_ATTRIBUTE_REPARSE_POINT // 0x00000400
FILE_ATTRIBUTE_COMPRESSED :: win32.FILE_ATTRIBUTE_COMPRESSED // 0x00000800
FILE_ATTRIBUTE_OFFLINE :: win32.FILE_ATTRIBUTE_OFFLINE // 0x00001000
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED :: win32.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED // 0x00002000
FILE_ATTRIBUTE_ENCRYPTED :: win32.FILE_ATTRIBUTE_ENCRYPTED // 0x00004000
_ERROR_BAD_NETPATH :: 53 _ERROR_BAD_NETPATH :: 53
MAX_RW :: 1<<30 MAX_RW :: 1<<30
@@ -109,9 +86,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u
err = .Not_Exist err = .Not_Exist
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
path := _fix_long_path(name, temp_allocator()) or_return path := _fix_long_path(name, temp_allocator) or_return
access: u32 access: u32
switch flags & {.Read, .Write} { switch flags & {.Read, .Write} {
case {.Read}: access = win32.FILE_GENERIC_READ case {.Read}: access = win32.FILE_GENERIC_READ
@@ -145,7 +122,7 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u
} }
attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS
if u32(perm) & FILE_ATTRIBUTE_NORMAL == 0 { if perm & S_IWRITE == 0 {
attrs = win32.FILE_ATTRIBUTE_READONLY attrs = win32.FILE_ATTRIBUTE_READONLY
if create_mode == win32.CREATE_ALWAYS { if create_mode == win32.CREATE_ALWAYS {
// NOTE(bill): Open has just asked to create a file in read-only mode. // NOTE(bill): Open has just asked to create a file in read-only mode.
@@ -580,8 +557,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
} }
_remove :: proc(name: string) -> Error { _remove :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
p := _fix_long_path(name, temp_allocator()) or_return p := _fix_long_path(name, temp_allocator) or_return
err, err1: Error err, err1: Error
if !win32.DeleteFileW(p) { if !win32.DeleteFileW(p) {
err = _get_platform_error() err = _get_platform_error()
@@ -618,9 +595,9 @@ _remove :: proc(name: string) -> Error {
} }
_rename :: proc(old_path, new_path: string) -> Error { _rename :: proc(old_path, new_path: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
from := _fix_long_path(old_path, temp_allocator()) or_return from := _fix_long_path(old_path, temp_allocator) or_return
to := _fix_long_path(new_path, temp_allocator()) or_return to := _fix_long_path(new_path, temp_allocator) or_return
if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
return nil return nil
} }
@@ -629,9 +606,9 @@ _rename :: proc(old_path, new_path: string) -> Error {
} }
_link :: proc(old_name, new_name: string) -> Error { _link :: proc(old_name, new_name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
o := _fix_long_path(old_name, temp_allocator()) or_return o := _fix_long_path(old_name, temp_allocator) or_return
n := _fix_long_path(new_name, temp_allocator()) or_return n := _fix_long_path(new_name, temp_allocator) or_return
if win32.CreateHardLinkW(n, o, nil) { if win32.CreateHardLinkW(n, o, nil) {
return nil return nil
} }
@@ -692,9 +669,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st
return "", _get_platform_error() return "", _get_platform_error()
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := make([]u16, n+1, temp_allocator()) buf := make([]u16, n+1, temp_allocator)
n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS) n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS)
if n == 0 { if n == 0 {
return "", _get_platform_error() return "", _get_platform_error()
@@ -718,9 +695,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
@thread_local @thread_local
rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
p := _fix_long_path(name, temp_allocator()) or_return p := _fix_long_path(name, temp_allocator) or_return
handle := _open_sym_link(p) or_return handle := _open_sym_link(p) or_return
defer win32.CloseHandle(handle) defer win32.CloseHandle(handle)
@@ -771,10 +748,20 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
if f == nil || f.impl == nil { if f == nil || f.impl == nil {
return nil return nil
} }
d: win32.BY_HANDLE_FILE_INFORMATION
if !win32.GetFileInformationByHandle(_handle(f), &d) {
return _get_platform_error()
}
attrs := d.dwFileAttributes
if mode & S_IWRITE != 0 {
attrs &~= win32.FILE_ATTRIBUTE_READONLY
} else {
attrs |= win32.FILE_ATTRIBUTE_READONLY
}
info: win32.FILE_BASIC_INFO info: win32.FILE_BASIC_INFO
info.FileAttributes = win32.DWORD(mode) info.FileAttributes = attrs
if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(info)) { if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
@@ -785,8 +772,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
} }
_chdir :: proc(name: string) -> Error { _chdir :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
p := _fix_long_path(name, temp_allocator()) or_return p := _fix_long_path(name, temp_allocator) or_return
if !win32.SetCurrentDirectoryW(p) { if !win32.SetCurrentDirectoryW(p) {
return _get_platform_error() return _get_platform_error()
} }
@@ -834,8 +821,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
} }
_exists :: proc(path: string) -> bool { _exists :: proc(path: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
wpath, _ := _fix_long_path(path, temp_allocator()) wpath, _ := _fix_long_path(path, temp_allocator)
attribs := win32.GetFileAttributesW(wpath) attribs := win32.GetFileAttributesW(wpath)
return attribs != win32.INVALID_FILE_ATTRIBUTES return attribs != win32.INVALID_FILE_ATTRIBUTES
} }
-5
View File
@@ -43,11 +43,6 @@ clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstri
return cstring(&buf[0]), nil return cstring(&buf[0]), nil
} }
@(require_results)
temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) #optional_allocator_error {
return clone_to_cstring(s, temp_allocator())
}
@(require_results) @(require_results)
string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) { string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) {
s := string(b) s := string(b)
+4 -4
View File
@@ -119,11 +119,11 @@ clean_path :: proc(path: string, allocator: runtime.Allocator) -> (cleaned: stri
return strings.clone(".", allocator) return strings.clone(".", allocator)
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
// The extra byte is to simplify appending path elements by letting the // The extra byte is to simplify appending path elements by letting the
// loop to end each with a separator. We'll trim the last one when we're done. // loop to end each with a separator. We'll trim the last one when we're done.
buffer := make([]u8, len(path) + 1, temp_allocator()) or_return buffer := make([]u8, len(path) + 1, temp_allocator) or_return
// This is the only point where Windows and POSIX differ, as Windows has // This is the only point where Windows and POSIX differ, as Windows has
// alphabet-based volumes for root paths. // alphabet-based volumes for root paths.
@@ -326,8 +326,8 @@ For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo
join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) { join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) {
for e, i in elems { for e, i in elems {
if e != "" { if e != "" {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
p := strings.join(elems[i:], Path_Separator_String, temp_allocator()) or_return p := strings.join(elems[i:], Path_Separator_String, temp_allocator) or_return
return clean_path(p, allocator) return clean_path(p, allocator)
} }
} }
+29 -9
View File
@@ -18,8 +18,8 @@ _is_path_separator :: proc(c: byte) -> bool {
} }
_mkdir :: proc(path: string, perm: int) -> Error { _mkdir :: proc(path: string, perm: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
path_cstr := temp_cstring(path) or_return path_cstr := clone_to_cstring(path, temp_allocator) or_return
return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm))) return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm)))
} }
@@ -52,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
} }
return _get_platform_error(errno) return _get_platform_error(errno)
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
// need something we can edit, and use to generate cstrings // need something we can edit, and use to generate cstrings
path_bytes := make([]u8, len(path) + 1, temp_allocator()) path_bytes := make([]u8, len(path) + 1, temp_allocator)
// zero terminate the byte slice to make it a valid cstring // zero terminate the byte slice to make it a valid cstring
copy(path_bytes, path) copy(path_bytes, path)
@@ -129,8 +129,8 @@ _remove_all :: proc(path: string) -> Error {
return nil return nil
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
path_cstr := temp_cstring(path) or_return path_cstr := clone_to_cstring(path, temp_allocator) or_return
fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS) fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS)
#partial switch errno { #partial switch errno {
@@ -168,14 +168,16 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error)
} }
_set_working_directory :: proc(dir: string) -> Error { _set_working_directory :: proc(dir: string) -> Error {
dir_cstr := temp_cstring(dir) or_return temp_allocator := TEMP_ALLOCATOR_GUARD({})
dir_cstr := clone_to_cstring(dir, temp_allocator) or_return
return _get_platform_error(linux.chdir(dir_cstr)) return _get_platform_error(linux.chdir(dir_cstr))
} }
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := make([dynamic]byte, 1024, temp_allocator()) or_return buf := make([dynamic]byte, 1024, temp_allocator) or_return
for { for {
n, errno := linux.readlink("/proc/self/exe", buf[:]) n, errno := linux.readlink("/proc/self/exe", buf[:])
if errno != .NONE { if errno != .NONE {
@@ -205,3 +207,21 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath:
} }
return return
} }
_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
rel := path
if rel == "" {
rel = "."
}
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
fd, errno := linux.open(clone_to_cstring(path, temp_allocator) or_return, {})
if errno != nil {
err = _get_platform_error(errno)
return
}
defer linux.close(fd)
return _get_full_path(fd, allocator)
}
+2 -2
View File
@@ -5,9 +5,9 @@ import "base:runtime"
import "core:sys/posix" import "core:sys/posix"
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := make([dynamic]byte, 1024, temp_allocator()) or_return buf := make([dynamic]byte, 1024, temp_allocator) or_return
for { for {
n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf)) n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf))
if n < 0 { if n < 0 {
+3 -3
View File
@@ -35,11 +35,11 @@ _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err
return real(arg, allocator) return real(arg, allocator)
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := strings.builder_make(temp_allocator()) buf := strings.builder_make(temp_allocator)
paths := get_env("PATH", temp_allocator()) paths := get_env("PATH", temp_allocator)
for dir in strings.split_iterator(&paths, ":") { for dir in strings.split_iterator(&paths, ":") {
strings.builder_reset(&buf) strings.builder_reset(&buf)
strings.write_string(&buf, dir) strings.write_string(&buf, dir)
+30 -13
View File
@@ -14,9 +14,9 @@ _is_path_separator :: proc(c: byte) -> bool {
return c == _Path_Separator return c == _Path_Separator
} }
_mkdir :: proc(name: string, perm: int) -> Error { _mkdir :: proc(name: string, perm: int) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK { if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK {
return _get_platform_error() return _get_platform_error()
} }
@@ -28,13 +28,13 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
return .Invalid_Path return .Invalid_Path
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
if exists(path) { if exists(path) {
return .Exist return .Exist
} }
clean_path := clean_path(path, temp_allocator()) or_return clean_path := clean_path(path, temp_allocator) or_return
return internal_mkdir_all(clean_path, perm) return internal_mkdir_all(clean_path, perm)
internal_mkdir_all :: proc(path: string, perm: int) -> Error { internal_mkdir_all :: proc(path: string, perm: int) -> Error {
@@ -52,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
} }
} }
_remove_all :: proc(path: string) -> Error { _remove_all :: proc(path: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cpath := temp_cstring(path) cpath := clone_to_cstring(path, temp_allocator) or_return
dir := posix.opendir(cpath) dir := posix.opendir(cpath)
if dir == nil { if dir == nil {
@@ -78,7 +78,7 @@ _remove_all :: proc(path: string) -> Error {
continue continue
} }
fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator()) fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator)
if entry.d_type == .DIR { if entry.d_type == .DIR {
_remove_all(fullpath[:len(fullpath)-1]) or_return _remove_all(fullpath[:len(fullpath)-1]) or_return
} else { } else {
@@ -95,10 +95,10 @@ _remove_all :: proc(path: string) -> Error {
} }
_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf: [dynamic]byte buf: [dynamic]byte
buf.allocator = temp_allocator() buf.allocator = temp_allocator
size := uint(posix.PATH_MAX) size := uint(posix.PATH_MAX)
cwd: cstring cwd: cstring
@@ -116,10 +116,27 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er
} }
_set_working_directory :: proc(dir: string) -> (err: Error) { _set_working_directory :: proc(dir: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cdir := temp_cstring(dir) cdir := clone_to_cstring(dir, temp_allocator) or_return
if posix.chdir(cdir) != .OK { if posix.chdir(cdir) != .OK {
err = _get_platform_error() err = _get_platform_error()
} }
return return
} }
_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
rel := path
if rel == "" {
rel = "."
}
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
rel_cstr := clone_to_cstring(rel, temp_allocator) or_return
path_ptr := posix.realpath(rel_cstr, nil)
if path_ptr == nil {
return "", Platform_Error(posix.errno())
}
defer posix.free(path_ptr)
path_str := clone_string(string(path_ptr), allocator) or_return
return path_str, nil
}
-21
View File
@@ -4,10 +4,6 @@ package os2
// This implementation is for all systems that have POSIX-compliant filesystem paths. // This implementation is for all systems that have POSIX-compliant filesystem paths.
import "base:runtime"
import "core:strings"
import "core:sys/posix"
_are_paths_identical :: proc(a, b: string) -> (identical: bool) { _are_paths_identical :: proc(a, b: string) -> (identical: bool) {
return a == b return a == b
} }
@@ -26,23 +22,6 @@ _is_absolute_path :: proc(path: string) -> bool {
return len(path) > 0 && _is_path_separator(path[0]) return len(path) > 0 && _is_path_separator(path[0])
} }
_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
rel := path
if rel == "" {
rel = "."
}
TEMP_ALLOCATOR_GUARD()
rel_cstr := strings.clone_to_cstring(rel, temp_allocator())
path_ptr := posix.realpath(rel_cstr, nil)
if path_ptr == nil {
return "", Platform_Error(posix.errno())
}
defer posix.free(path_ptr)
path_str := strings.clone(string(path_ptr), allocator)
return path_str, nil
}
_get_relative_path_handle_start :: proc(base, target: string) -> bool { _get_relative_path_handle_start :: proc(base, target: string) -> bool {
base_rooted := len(base) > 0 && _is_path_separator(base[0]) base_rooted := len(base) > 0 && _is_path_separator(base[0])
target_rooted := len(target) > 0 && _is_path_separator(target[0]) target_rooted := len(target) > 0 && _is_path_separator(target[0])
+2 -2
View File
@@ -28,13 +28,13 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
return .Invalid_Path return .Invalid_Path
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
if exists(path) { if exists(path) {
return .Exist return .Exist
} }
clean_path := clean_path(path, temp_allocator()) clean_path := clean_path(path, temp_allocator)
return internal_mkdir_all(clean_path) return internal_mkdir_all(clean_path)
internal_mkdir_all :: proc(path: string) -> Error { internal_mkdir_all :: proc(path: string) -> Error {
+23 -19
View File
@@ -14,8 +14,8 @@ _is_path_separator :: proc(c: byte) -> bool {
} }
_mkdir :: proc(name: string, perm: int) -> Error { _mkdir :: proc(name: string, perm: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator()) or_return, nil) { if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator) or_return, nil) {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
@@ -33,9 +33,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
return p, false, nil return p, false, nil
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
dir_stat, err := stat(path, temp_allocator()) dir_stat, err := stat(path, temp_allocator)
if err == nil { if err == nil {
if dir_stat.type == .Directory { if dir_stat.type == .Directory {
return nil return nil
@@ -63,7 +63,7 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
err = mkdir(path, perm) err = mkdir(path, perm)
if err != nil { if err != nil {
new_dir_stat, err1 := lstat(path, temp_allocator()) new_dir_stat, err1 := lstat(path, temp_allocator)
if err1 == nil && new_dir_stat.type == .Directory { if err1 == nil && new_dir_stat.type == .Directory {
return nil return nil
} }
@@ -82,8 +82,8 @@ _remove_all :: proc(path: string) -> Error {
return nil return nil
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
dir := win32_utf8_to_wstring(path, temp_allocator()) or_return dir := win32_utf8_to_wstring(path, temp_allocator) or_return
empty: [1]u16 empty: [1]u16
@@ -109,10 +109,10 @@ _remove_all :: proc(path: string) -> Error {
_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
win32.AcquireSRWLockExclusive(&cwd_lock) win32.AcquireSRWLockExclusive(&cwd_lock)
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
sz_utf16 := win32.GetCurrentDirectoryW(0, nil) sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
dir_buf_wstr := make([]u16, sz_utf16, temp_allocator()) or_return dir_buf_wstr := make([]u16, sz_utf16, temp_allocator) or_return
sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr)) sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL. assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
@@ -123,8 +123,8 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er
} }
_set_working_directory :: proc(dir: string) -> (err: Error) { _set_working_directory :: proc(dir: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
wstr := win32_utf8_to_wstring(dir, temp_allocator()) or_return wstr := win32_utf8_to_wstring(dir, temp_allocator) or_return
win32.AcquireSRWLockExclusive(&cwd_lock) win32.AcquireSRWLockExclusive(&cwd_lock)
@@ -138,9 +138,9 @@ _set_working_directory :: proc(dir: string) -> (err: Error) {
} }
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := make([dynamic]u16, 512, temp_allocator()) or_return buf := make([dynamic]u16, 512, temp_allocator) or_return
for { for {
ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf))) ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf)))
if ret == 0 { if ret == 0 {
@@ -187,7 +187,6 @@ init_long_path_support :: proc() {
if value == 1 { if value == 1 {
can_use_long_paths = true can_use_long_paths = true
} }
} }
@(require_results) @(require_results)
@@ -222,10 +221,10 @@ _fix_long_path_internal :: proc(path: string) -> string {
return path return path
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
PREFIX :: `\\?` PREFIX :: `\\?`
path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator()) path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator)
copy(path_buf, PREFIX) copy(path_buf, PREFIX)
n := len(path) n := len(path)
r, w := 0, len(PREFIX) r, w := 0, len(PREFIX)
@@ -271,6 +270,11 @@ _clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, s
start += 1 start += 1
} }
copy(buffer, path[:start]) copy(buffer, path[:start])
for n in 0..<start {
if _is_path_separator(buffer[n]) {
buffer[n] = _Path_Separator
}
}
} }
return return
} }
@@ -297,14 +301,14 @@ _get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absol
if rel == "" { if rel == "" {
rel = "." rel = "."
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator()) rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator)
n := win32.GetFullPathNameW(raw_data(rel_utf16), 0, nil, nil) n := win32.GetFullPathNameW(raw_data(rel_utf16), 0, nil, nil)
if n == 0 { if n == 0 {
return "", Platform_Error(win32.GetLastError()) return "", Platform_Error(win32.GetLastError())
} }
buf := make([]u16, n, temp_allocator()) or_return buf := make([]u16, n, temp_allocator) or_return
n = win32.GetFullPathNameW(raw_data(rel_utf16), u32(n), raw_data(buf), nil) n = win32.GetFullPathNameW(raw_data(rel_utf16), u32(n), raw_data(buf), nil)
if n == 0 { if n == 0 {
return "", Platform_Error(win32.GetLastError()) return "", Platform_Error(win32.GetLastError())
+24 -24
View File
@@ -50,7 +50,7 @@ _get_ppid :: proc() -> int {
@(private="package") @(private="package")
_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS) dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS)
#partial switch errno { #partial switch errno {
@@ -68,9 +68,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
} }
defer linux.close(dir_fd) defer linux.close(dir_fd)
dynamic_list := make([dynamic]int, temp_allocator()) or_return dynamic_list := make([dynamic]int, temp_allocator) or_return
buf := make([dynamic]u8, 128, 128, temp_allocator()) or_return buf := make([dynamic]u8, 128, 128, temp_allocator) or_return
loop: for { loop: for {
buflen: int buflen: int
buflen, errno = linux.getdents(dir_fd, buf[:]) buflen, errno = linux.getdents(dir_fd, buf[:])
@@ -100,7 +100,7 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
@(private="package") @(private="package")
_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
info.pid = pid info.pid = pid
@@ -126,7 +126,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
passwd_bytes: []u8 passwd_bytes: []u8
passwd_err: Error passwd_err: Error
passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator()) passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator)
if passwd_err != nil { if passwd_err != nil {
err = passwd_err err = passwd_err
break username_if break username_if
@@ -168,7 +168,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
strings.write_int(&path_builder, pid) strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/cmdline") strings.write_string(&path_builder, "/cmdline")
cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()) cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
if cmdline_err != nil || len(cmdline_bytes) == 0 { if cmdline_err != nil || len(cmdline_bytes) == 0 {
err = cmdline_err err = cmdline_err
break cmdline_if break cmdline_if
@@ -189,7 +189,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
strings.write_int(&path_builder, pid) strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/cwd") strings.write_string(&path_builder, "/cwd")
cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator()) // allowed to fail cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator) // allowed to fail
if cwd_err == nil && .Working_Dir in selection { if cwd_err == nil && .Working_Dir in selection {
info.working_dir = strings.clone(cwd, allocator) or_return info.working_dir = strings.clone(cwd, allocator) or_return
info.fields += {.Working_Dir} info.fields += {.Working_Dir}
@@ -245,7 +245,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
strings.write_int(&path_builder, pid) strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/stat") strings.write_string(&path_builder, "/stat")
proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()) proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
if stat_err != nil { if stat_err != nil {
err = stat_err err = stat_err
break stat_if break stat_if
@@ -284,7 +284,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
Nice, Nice,
//... etc, //... etc,
} }
stat_fields := strings.split(stats, " ", temp_allocator()) or_return stat_fields := strings.split(stats, " ", temp_allocator) or_return
if len(stat_fields) <= int(Fields.Nice) { if len(stat_fields) <= int(Fields.Nice) {
break stat_if break stat_if
@@ -327,7 +327,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
strings.write_int(&path_builder, pid) strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/exe") strings.write_string(&path_builder, "/exe")
if exe_bytes, exe_err := _read_link(strings.to_string(path_builder), temp_allocator()); exe_err == nil { if exe_bytes, exe_err := _read_link(strings.to_string(path_builder), temp_allocator); exe_err == nil {
info.executable_path = strings.clone(string(exe_bytes), allocator) or_return info.executable_path = strings.clone(string(exe_bytes), allocator) or_return
info.fields += {.Executable_Path} info.fields += {.Executable_Path}
} else { } else {
@@ -341,7 +341,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
strings.write_int(&path_builder, pid) strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/environ") strings.write_string(&path_builder, "/environ")
if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()); env_err == nil { if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator); env_err == nil {
env := string(env_bytes) env := string(env_bytes)
env_list := make([dynamic]string, allocator) or_return env_list := make([dynamic]string, allocator) or_return
@@ -392,7 +392,7 @@ _process_open :: proc(pid: int, _: Process_Open_Flags) -> (process: Process, err
@(private="package") @(private="package")
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
if len(desc.command) == 0 { if len(desc.command) == 0 {
return process, .Invalid_Command return process, .Invalid_Command
@@ -401,7 +401,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
dir_fd := linux.AT_FDCWD dir_fd := linux.AT_FDCWD
errno: linux.Errno errno: linux.Errno
if desc.working_dir != "" { if desc.working_dir != "" {
dir_cstr := temp_cstring(desc.working_dir) or_return dir_cstr := clone_to_cstring(desc.working_dir, temp_allocator) or_return
if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE { if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE {
return process, _get_platform_error(errno) return process, _get_platform_error(errno)
} }
@@ -414,10 +414,10 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
exe_path: cstring exe_path: cstring
executable_name := desc.command[0] executable_name := desc.command[0]
if strings.index_byte(executable_name, '/') < 0 { if strings.index_byte(executable_name, '/') < 0 {
path_env := get_env("PATH", temp_allocator()) path_env := get_env("PATH", temp_allocator)
path_dirs := split_path_list(path_env, temp_allocator()) or_return path_dirs := split_path_list(path_env, temp_allocator) or_return
exe_builder := strings.builder_make(temp_allocator()) or_return exe_builder := strings.builder_make(temp_allocator) or_return
found: bool found: bool
for dir in path_dirs { for dir in path_dirs {
@@ -444,7 +444,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
} }
} }
} else { } else {
exe_path = temp_cstring(executable_name) or_return exe_path = clone_to_cstring(executable_name, temp_allocator) or_return
if linux.access(exe_path, linux.X_OK) != .NONE { if linux.access(exe_path, linux.X_OK) != .NONE {
return process, .Not_Exist return process, .Not_Exist
} }
@@ -452,20 +452,20 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
// args and environment need to be a list of cstrings // args and environment need to be a list of cstrings
// that are terminated by a nil pointer. // that are terminated by a nil pointer.
cargs := make([]cstring, len(desc.command) + 1, temp_allocator()) or_return cargs := make([]cstring, len(desc.command) + 1, temp_allocator) or_return
for command, i in desc.command { for command, i in desc.command {
cargs[i] = temp_cstring(command) or_return cargs[i] = clone_to_cstring(command, temp_allocator) or_return
} }
// Use current process' environment if description didn't provide it. // Use current process' environment if description didn't provide it.
env: [^]cstring env: [^]cstring
if desc.env == nil { if desc.env == nil {
// take this process's current environment // take this process's current environment
env = raw_data(export_cstring_environment(temp_allocator())) env = raw_data(export_cstring_environment(temp_allocator))
} else { } else {
cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) or_return cenv := make([]cstring, len(desc.env) + 1, temp_allocator) or_return
for env, i in desc.env { for env, i in desc.env {
cenv[i] = temp_cstring(env) or_return cenv[i] = clone_to_cstring(env, temp_allocator) or_return
} }
env = &cenv[0] env = &cenv[0]
} }
@@ -593,7 +593,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
} }
_process_state_update_times :: proc(state: ^Process_State) -> (err: Error) { _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
stat_path_buf: [48]u8 stat_path_buf: [48]u8
path_builder := strings.builder_from_bytes(stat_path_buf[:]) path_builder := strings.builder_from_bytes(stat_path_buf[:])
@@ -602,7 +602,7 @@ _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
strings.write_string(&path_builder, "/stat") strings.write_string(&path_builder, "/stat")
stat_buf: []u8 stat_buf: []u8
stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()) stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
if err != nil { if err != nil {
return return
} }
+9 -9
View File
@@ -52,14 +52,14 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
// search PATH if just a plain name is provided. // search PATH if just a plain name is provided.
exe_builder := strings.builder_make(temp_allocator()) exe_builder := strings.builder_make(temp_allocator)
exe_name := desc.command[0] exe_name := desc.command[0]
if strings.index_byte(exe_name, '/') < 0 { if strings.index_byte(exe_name, '/') < 0 {
path_env := get_env("PATH", temp_allocator()) path_env := get_env("PATH", temp_allocator)
path_dirs := split_path_list(path_env, temp_allocator()) or_return path_dirs := split_path_list(path_env, temp_allocator) or_return
found: bool found: bool
for dir in path_dirs { for dir in path_dirs {
@@ -108,12 +108,12 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
} }
cwd: cstring; if desc.working_dir != "" { cwd: cstring; if desc.working_dir != "" {
cwd = temp_cstring(desc.working_dir) cwd = clone_to_cstring(desc.working_dir, temp_allocator) or_return
} }
cmd := make([]cstring, len(desc.command) + 1, temp_allocator()) cmd := make([]cstring, len(desc.command) + 1, temp_allocator)
for part, i in desc.command { for part, i in desc.command {
cmd[i] = temp_cstring(part) cmd[i] = clone_to_cstring(part, temp_allocator) or_return
} }
env: [^]cstring env: [^]cstring
@@ -121,9 +121,9 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
// take this process's current environment // take this process's current environment
env = posix.environ env = posix.environ
} else { } else {
cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) cenv := make([]cstring, len(desc.env) + 1, temp_allocator)
for env, i in desc.env { for env, i in desc.env {
cenv[i] = temp_cstring(env) cenv[i] = clone_to_cstring(env, temp_allocator) or_return
} }
env = raw_data(cenv) env = raw_data(cenv)
} }
+4 -3
View File
@@ -50,6 +50,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
} }
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
info.pid = pid info.pid = pid
// Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first), // Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first),
@@ -127,7 +128,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
break args break args
} }
buf := runtime.make_aligned([]byte, length, 4, temp_allocator()) buf := runtime.make_aligned([]byte, length, 4, temp_allocator)
if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK { if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK {
if err == nil { if err == nil {
err = _get_platform_error() err = _get_platform_error()
@@ -239,9 +240,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buffer := make([]i32, ret, temp_allocator()) buffer := make([]i32, ret, temp_allocator)
ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32)) ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32))
if ret < 0 { if ret < 0 {
err = _get_platform_error() err = _get_platform_error()
+25 -23
View File
@@ -162,9 +162,10 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
if err != nil { if err != nil {
break read_peb break read_peb
} }
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
if selection >= {.Command_Line, .Command_Args} { if selection >= {.Command_Line, .Command_Args} {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -179,9 +180,9 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
} }
} }
if .Environment in selection { if .Environment in selection {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
env_len := process_params.EnvironmentSize / 2 env_len := process_params.EnvironmentSize / 2
envs_w := make([]u16, env_len, temp_allocator()) or_return envs_w := make([]u16, env_len, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.Environment, envs_w) _, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -190,8 +191,8 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
info.fields += {.Environment} info.fields += {.Environment}
} }
if .Working_Dir in selection { if .Working_Dir in selection {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -272,9 +273,10 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
if err != nil { if err != nil {
break read_peb break read_peb
} }
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
if selection >= {.Command_Line, .Command_Args} { if selection >= {.Command_Line, .Command_Args} {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -289,9 +291,9 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
} }
} }
if .Environment in selection { if .Environment in selection {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
env_len := process_params.EnvironmentSize / 2 env_len := process_params.EnvironmentSize / 2
envs_w := make([]u16, env_len, temp_allocator()) or_return envs_w := make([]u16, env_len, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.Environment, envs_w) _, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -300,8 +302,8 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
info.fields += {.Environment} info.fields += {.Environment}
} }
if .Working_Dir in selection { if .Working_Dir in selection {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -419,15 +421,15 @@ _process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process,
@(private="package") @(private="package")
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
command_line := _build_command_line(desc.command, temp_allocator()) command_line := _build_command_line(desc.command, temp_allocator)
command_line_w := win32_utf8_to_wstring(command_line, temp_allocator()) or_return command_line_w := win32_utf8_to_wstring(command_line, temp_allocator) or_return
environment := desc.env environment := desc.env
if desc.env == nil { if desc.env == nil {
environment = environ(temp_allocator()) or_return environment = environ(temp_allocator) or_return
} }
environment_block := _build_environment_block(environment, temp_allocator()) environment_block := _build_environment_block(environment, temp_allocator)
environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator()) or_return environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator) or_return
stderr_handle: win32.HANDLE stderr_handle: win32.HANDLE
stdout_handle: win32.HANDLE stdout_handle: win32.HANDLE
@@ -474,7 +476,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd) stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd)
} }
working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator) or_else nil) if len(desc.working_dir) > 0 else nil
process_info: win32.PROCESS_INFORMATION process_info: win32.PROCESS_INFORMATION
ok := win32.CreateProcessW( ok := win32.CreateProcessW(
nil, nil,
@@ -612,7 +614,7 @@ _process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path
} }
_get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) { _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
token_handle: win32.HANDLE token_handle: win32.HANDLE
if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) { if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) {
err = _get_platform_error() err = _get_platform_error()
@@ -627,7 +629,7 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc
} }
err = nil err = nil
} }
token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return)) token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator) or_return))
if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) { if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) {
err = _get_platform_error() err = _get_platform_error()
return return
@@ -643,8 +645,8 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc
err = _get_platform_error() err = _get_platform_error()
return return
} }
username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator) or_return
domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator) or_return
return strings.concatenate({domain, "\\", username}, allocator) return strings.concatenate({domain, "\\", username}, allocator)
} }
+4 -4
View File
@@ -73,14 +73,14 @@ last_write_time_by_name :: modification_time_by_path
@(require_results) @(require_results)
modification_time :: proc(f: ^File) -> (time.Time, Error) { modification_time :: proc(f: ^File) -> (time.Time, Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
fi, err := fstat(f, temp_allocator()) fi, err := fstat(f, temp_allocator)
return fi.modification_time, err return fi.modification_time, err
} }
@(require_results) @(require_results)
modification_time_by_path :: proc(path: string) -> (time.Time, Error) { modification_time_by_path :: proc(path: string) -> (time.Time, Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
fi, err := stat(path, temp_allocator()) fi, err := stat(path, temp_allocator)
return fi.modification_time, err return fi.modification_time, err
} }
+4 -4
View File
@@ -47,8 +47,8 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File
// NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath
_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
fd, errno := linux.open(name_cstr, {}) fd, errno := linux.open(name_cstr, {})
if errno != .NONE { if errno != .NONE {
@@ -59,8 +59,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
} }
_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW}) fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW})
if errno != .NONE { if errno != .NONE {
+9 -8
View File
@@ -69,8 +69,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
cname := temp_cstring(name) or_return cname := clone_to_cstring(name, temp_allocator) or_return
fd := posix.open(cname, {}) fd := posix.open(cname, {})
if fd == -1 { if fd == -1 {
@@ -96,33 +96,34 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
// NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks. // NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks.
// NOTE: This might not be correct when given "/symlink/foo.txt", // NOTE: This might not be correct when given "/symlink/foo.txt",
// you would want that to resolve "/symlink", but not resolve "foo.txt". // you would want that to resolve "/symlink", but not resolve "foo.txt".
fullpath := clean_path(name, temp_allocator()) or_return fullpath := clean_path(name, temp_allocator) or_return
assert(len(fullpath) > 0) assert(len(fullpath) > 0)
switch { switch {
case fullpath[0] == '/': case fullpath[0] == '/':
// nothing. // nothing.
case fullpath == ".": case fullpath == ".":
fullpath = getwd(temp_allocator()) or_return fullpath = getwd(temp_allocator) or_return
case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/': case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/':
fullpath = fullpath[2:] fullpath = fullpath[2:]
fallthrough fallthrough
case: case:
fullpath = concatenate({ fullpath = concatenate({
getwd(temp_allocator()) or_return, getwd(temp_allocator) or_return,
"/", "/",
fullpath, fullpath,
}, temp_allocator()) or_return }, temp_allocator) or_return
} }
stat: posix.stat_t stat: posix.stat_t
if posix.lstat(temp_cstring(fullpath), &stat) != .OK { c_fullpath := clone_to_cstring(fullpath, temp_allocator) or_return
if posix.lstat(c_fullpath, &stat) != .OK {
err = _get_platform_error() err = _get_platform_error()
return return
} }
+67 -44
View File
@@ -45,15 +45,15 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
name = "." name = "."
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
p := win32_utf8_to_utf16(name, temp_allocator()) or_return p := win32_utf8_to_utf16(name, temp_allocator) or_return
n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil) n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
if n == 0 { if n == 0 {
return "", _get_platform_error() return "", _get_platform_error()
} }
buf := make([]u16, n+1, temp_allocator()) buf := make([]u16, n+1, temp_allocator)
n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
if n == 0 { if n == 0 {
return "", _get_platform_error() return "", _get_platform_error()
@@ -65,9 +65,9 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt
if len(name) == 0 { if len(name) == 0 {
return {}, .Not_Exist return {}, .Not_Exist
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
wname := _fix_long_path(name, temp_allocator()) or_return wname := _fix_long_path(name, temp_allocator) or_return
fa: win32.WIN32_FILE_ATTRIBUTE_DATA fa: win32.WIN32_FILE_ATTRIBUTE_DATA
ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
@@ -137,9 +137,9 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin
return "", _get_platform_error() return "", _get_platform_error()
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := make([]u16, max(n, 260)+1, temp_allocator()) buf := make([]u16, max(n, 260)+1, temp_allocator)
n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
return _cleanpath_from_buf(buf[:n], allocator) return _cleanpath_from_buf(buf[:n], allocator)
} }
@@ -155,9 +155,9 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
return nil, _get_platform_error() return nil, _get_platform_error()
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
buf := make([]u16, max(n, 260)+1, temp_allocator()) buf := make([]u16, max(n, 260)+1, temp_allocator)
n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
return _cleanpath_strip_prefix(buf[:n]), nil return _cleanpath_strip_prefix(buf[:n]), nil
} }
@@ -212,15 +212,11 @@ _file_type_from_create_file :: proc(wname: win32.wstring, create_file_attributes
} }
_file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: int) { _file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: int) {
// NOTE(Jeroen): We don't translate mode flags for Linux when given to `chmod`. if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
// Let's not do so for Windows for `chmod` or `read_directory_iterator` either. mode |= 0o444
// They're *not* portable between Windows and non-Windows platforms. } else {
// mode |= 0o666
// It also leads to information loss as flags like Archive, Hidden and System have no equivalent there. }
// We can of course parse them so we can set the `.Symlink` and `.Directory` type, but we shouldn't pretend
// that 0o644 is meaningful when returned as a mode.
// `C:\bootmgr` as an example has attributes read only, hidden, system, archive. In no way is it sensible to replace that with 0o444.
mode = int(file_attributes)
is_sym := false is_sym := false
if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
@@ -233,6 +229,7 @@ _file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: wi
type = .Symlink type = .Symlink
} else if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { } else if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
type = .Directory type = .Directory
mode |= 0o111
} else if h != nil { } else if h != nil {
type = file_type(h) type = file_type(h)
} }
@@ -329,42 +326,68 @@ _is_reserved_name :: proc(path: string) -> bool {
return false return false
} }
_is_UNC :: proc(path: string) -> bool { _volume_name_len :: proc(path: string) -> (length: int) {
return _volume_name_len(path) > 2
}
_volume_name_len :: proc(path: string) -> int {
if len(path) < 2 { if len(path) < 2 {
return 0 return 0
} }
c := path[0]
if path[1] == ':' { if path[1] == ':' {
switch c { switch path[0] {
case 'a'..='z', 'A'..='Z': case 'a'..='z', 'A'..='Z':
return 2 return 2
} }
} }
// URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx /*
if l := len(path); l >= 5 && _is_path_separator(path[0]) && _is_path_separator(path[1]) && See: URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
!_is_path_separator(path[2]) && path[2] != '.' { Further allowed paths can be of the form of:
for n := 3; n < l-1; n += 1 { - \\server\share or \\server\share\more\path
if _is_path_separator(path[n]) { - \\?\C:\...
n += 1 - \\.\PhysicalDriveX
if !_is_path_separator(path[n]) { */
if path[n] == '.' { // Any remaining kind of path has to start with two slashes.
break if !_is_path_separator(path[0]) || !_is_path_separator(path[1]) {
} return 0
} }
for ; n < l; n += 1 {
if _is_path_separator(path[n]) { // Device path. The volume name is the whole string
break if len(path) >= 5 && path[2] == '.' && _is_path_separator(path[3]) {
} return len(path)
} }
return n
// We're a UNC share `\\host\share`, file namespace `\\?\C:` or UNC in file namespace `\\?\\host\share`
prefix := 2
// File namespace.
if len(path) >= 5 && path[2] == '?' && _is_path_separator(path[3]) {
if _is_path_separator(path[4]) {
// `\\?\\` UNC path in file namespace
prefix = 5
}
if len(path) >= 6 && path[5] == ':' {
switch path[4] {
case 'a'..='z', 'A'..='Z':
return 6
case:
return 0
} }
break
} }
} }
return 0
// UNC path, minimum version of the volume is `\\h\s` for host, share.
// Can also contain an IP address in the host position.
slash_count := 0
for i in prefix..<len(path) {
// Host needs to be at least 1 character
if _is_path_separator(path[i]) && i > 0 {
slash_count += 1
if slash_count == 2 {
return i
}
}
}
return len(path)
} }
+11 -9
View File
@@ -15,13 +15,13 @@ MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right?
// The caller must `close` the file once finished with. // The caller must `close` the file once finished with.
@(require_results) @(require_results)
create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) { create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
dir := dir if dir != "" else temp_directory(temp_allocator()) or_return dir := dir if dir != "" else temp_directory(temp_allocator) or_return
prefix, suffix := _prefix_and_suffix(pattern) or_return prefix, suffix := _prefix_and_suffix(pattern) or_return
prefix = temp_join_path(dir, prefix) or_return prefix = temp_join_path(dir, prefix) or_return
rand_buf: [10]byte rand_buf: [10]byte
name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator()) name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator)
attempts := 0 attempts := 0
for { for {
@@ -47,13 +47,13 @@ mkdir_temp :: make_directory_temp
// If `dir` is an empty tring, `temp_directory()` will be used. // If `dir` is an empty tring, `temp_directory()` will be used.
@(require_results) @(require_results)
make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (temp_path: string, err: Error) { make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (temp_path: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
dir := dir if dir != "" else temp_directory(temp_allocator()) or_return dir := dir if dir != "" else temp_directory(temp_allocator) or_return
prefix, suffix := _prefix_and_suffix(pattern) or_return prefix, suffix := _prefix_and_suffix(pattern) or_return
prefix = temp_join_path(dir, prefix) or_return prefix = temp_join_path(dir, prefix) or_return
rand_buf: [10]byte rand_buf: [10]byte
name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator()) name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator)
attempts := 0 attempts := 0
for { for {
@@ -70,7 +70,7 @@ make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator)
return "", err return "", err
} }
if err == .Not_Exist { if err == .Not_Exist {
if _, serr := stat(dir, temp_allocator()); serr == .Not_Exist { if _, serr := stat(dir, temp_allocator); serr == .Not_Exist {
return "", serr return "", serr
} }
} }
@@ -89,9 +89,11 @@ temp_directory :: proc(allocator: runtime.Allocator) -> (string, Error) {
@(private="file") @(private="file")
temp_join_path :: proc(dir, name: string) -> (string, runtime.Allocator_Error) { temp_join_path :: proc(dir, name: string) -> (string, runtime.Allocator_Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
if len(dir) > 0 && is_path_separator(dir[len(dir)-1]) { if len(dir) > 0 && is_path_separator(dir[len(dir)-1]) {
return concatenate({dir, name}, temp_allocator(),) return concatenate({dir, name}, temp_allocator,)
} }
return concatenate({dir, Path_Separator_String, name}, temp_allocator()) return concatenate({dir, Path_Separator_String, name}, temp_allocator)
} }
+2 -2
View File
@@ -4,8 +4,8 @@ package os2
import "base:runtime" import "base:runtime"
_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
tmpdir := get_env("TMPDIR", temp_allocator()) tmpdir := get_env("TMPDIR", temp_allocator)
if tmpdir == "" { if tmpdir == "" {
tmpdir = "/tmp" tmpdir = "/tmp"
} }
+2 -2
View File
@@ -9,9 +9,9 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er
if n == 0 { if n == 0 {
return "", nil return "", nil
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
b := make([]u16, max(win32.MAX_PATH, n), temp_allocator()) b := make([]u16, max(win32.MAX_PATH, n), temp_allocator)
n = win32.GetTempPathW(u32(len(b)), raw_data(b)) n = win32.GetTempPathW(u32(len(b)), raw_data(b))
if n == 3 && b[1] == ':' && b[2] == '\\' { if n == 3 && b[1] == ':' && b[2] == '\\' {
+11 -11
View File
@@ -4,27 +4,27 @@ import "base:runtime"
@(require_results) @(require_results)
user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
#partial switch ODIN_OS { #partial switch ODIN_OS {
case .Windows: case .Windows:
dir = get_env("LocalAppData", temp_allocator()) dir = get_env("LocalAppData", temp_allocator)
if dir != "" { if dir != "" {
dir = clone_string(dir, allocator) or_return dir = clone_string(dir, temp_allocator) or_return
} }
case .Darwin: case .Darwin:
dir = get_env("HOME", temp_allocator()) dir = get_env("HOME", temp_allocator)
if dir != "" { if dir != "" {
dir = concatenate({dir, "/Library/Caches"}, allocator) or_return dir = concatenate({dir, "/Library/Caches"}, temp_allocator) or_return
} }
case: // All other UNIX systems case: // All other UNIX systems
dir = get_env("XDG_CACHE_HOME", allocator) dir = get_env("XDG_CACHE_HOME", allocator)
if dir == "" { if dir == "" {
dir = get_env("HOME", temp_allocator()) dir = get_env("HOME", temp_allocator)
if dir == "" { if dir == "" {
return return
} }
dir = concatenate({dir, "/.cache"}, allocator) or_return dir = concatenate({dir, "/.cache"}, temp_allocator) or_return
} }
} }
if dir == "" { if dir == "" {
@@ -35,23 +35,23 @@ user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error
@(require_results) @(require_results)
user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
#partial switch ODIN_OS { #partial switch ODIN_OS {
case .Windows: case .Windows:
dir = get_env("AppData", temp_allocator()) dir = get_env("AppData", temp_allocator)
if dir != "" { if dir != "" {
dir = clone_string(dir, allocator) or_return dir = clone_string(dir, allocator) or_return
} }
case .Darwin: case .Darwin:
dir = get_env("HOME", temp_allocator()) dir = get_env("HOME", temp_allocator)
if dir != "" { if dir != "" {
dir = concatenate({dir, "/.config"}, allocator) or_return dir = concatenate({dir, "/.config"}, allocator) or_return
} }
case: // All other UNIX systems case: // All other UNIX systems
dir = get_env("XDG_CONFIG_HOME", allocator) dir = get_env("XDG_CONFIG_HOME", allocator)
if dir == "" { if dir == "" {
dir = get_env("HOME", temp_allocator()) dir = get_env("HOME", temp_allocator)
if dir == "" { if dir == "" {
return return
} }
+33
View File
@@ -1095,6 +1095,39 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
return transmute(f64)bits, ok return transmute(f64)bits, ok
} }
if len(str) > 2 && str[0] == '0' && str[1] == 'h' {
nr = 2
as_int: u64
digits: int
for r in str[2:] {
if r == '_' {
nr += 1
continue
}
v := u64(_digit_value(r))
if v >= 16 {
break
}
as_int *= 16
as_int += v
digits += 1
}
nr += digits
ok = len(str) == nr
switch digits {
case 4:
value = cast(f64)transmute(f16)cast(u16)as_int
case 8:
value = cast(f64)transmute(f32)cast(u32)as_int
case 16:
value = transmute(f64)as_int
case:
ok = false
}
return
}
if value, nr, ok = check_special(str); ok { if value, nr, ok = check_special(str); ok {
return return
+1 -1
View File
@@ -311,7 +311,7 @@ Returns:
- res: A cstring of the Builder's buffer upon success - res: A cstring of the Builder's buffer upon success
- err: An optional allocator error if one occured, `nil` otherwise - err: An optional allocator error if one occured, `nil` otherwise
*/ */
to_cstring :: proc(b: ^Builder) -> (res: cstring, err: mem.Allocator_Error) { to_cstring :: proc(b: ^Builder) -> (res: cstring, err: mem.Allocator_Error) #optional_allocator_error {
n := append(&b.buf, 0) or_return n := append(&b.buf, 0) or_return
if n != 1 { if n != 1 {
return nil, .Out_Of_Memory return nil, .Out_Of_Memory
@@ -99,7 +99,7 @@ Application_setTitle :: proc "c" (self: ^Application, title: ^String) {
} }
@(objc_type=Application, objc_name="mainMenu") @(objc_type=Application, objc_name="mainMenu")
Window_mainMenu :: proc "c" (self: ^Application) -> ^Menu { Application_mainMenu :: proc "c" (self: ^Application) -> ^Menu {
return msgSend(^Menu, self, "mainMenu") return msgSend(^Menu, self, "mainMenu")
} }
+46
View File
@@ -40,3 +40,49 @@ Array_objectAs :: proc "c" (self: ^Array, index: UInteger, $T: typeid) -> T wher
Array_count :: proc "c" (self: ^Array) -> UInteger { Array_count :: proc "c" (self: ^Array) -> UInteger {
return msgSend(UInteger, self, "count") return msgSend(UInteger, self, "count")
} }
@(objc_class="NSMutableArray")
MutableArray :: struct {
using _: Copying(MutableArray),
}
@(objc_type=MutableArray, objc_name="alloc", objc_is_class_method=true)
MutableArray_alloc :: proc "c" () -> ^MutableArray {
return msgSend(^MutableArray, MutableArray, "alloc")
}
@(objc_type=MutableArray, objc_name="init")
MutableArray_init :: proc "c" (self: ^MutableArray) -> ^MutableArray {
return msgSend(^MutableArray, self, "init")
}
@(objc_type=MutableArray, objc_name="initWithObjects")
MutableArray_initWithObjects :: proc "c" (self: ^MutableArray, objects: [^]^Object, count: UInteger) -> ^MutableArray {
return msgSend(^MutableArray, self, "initWithObjects:count:", objects, count)
}
@(objc_type=MutableArray, objc_name="initWithCoder")
MutableArray_initWithCoder :: proc "c" (self: ^MutableArray, coder: ^Coder) -> ^MutableArray {
return msgSend(^MutableArray, self, "initWithCoder:", coder)
}
@(objc_type=MutableArray, objc_name="object")
MutableArray_object :: proc "c" (self: ^MutableArray, index: UInteger) -> ^Object {
return msgSend(^Object, self, "objectAtIndex:", index)
}
@(objc_type=MutableArray, objc_name="objectAs")
MutableArray_objectAs :: proc "c" (self: ^MutableArray, index: UInteger, $T: typeid) -> T where intrinsics.type_is_pointer(T), intrinsics.type_is_subtype_of(T, ^Object) {
return (T)(MutableArray_object(self, index))
}
@(objc_type=MutableArray, objc_name="count")
MutableArray_count :: proc "c" (self: ^MutableArray) -> UInteger {
return msgSend(UInteger, self, "count")
}
@(objc_type=MutableArray, objc_name="exchangeObjectAtIndex")
MutableArray_exchangeObjectAtIndex :: proc "c" (self: ^MutableArray, idx1, idx2: UInteger) {
msgSend(nil, self, "exchangeObjectAtIndex:withObjectAtIndex:", idx1, idx2)
}
+530 -95
View File
@@ -2,127 +2,562 @@ package objc_Foundation
import "base:builtin" import "base:builtin"
import "base:intrinsics" import "base:intrinsics"
import "core:c"
KeyEquivalentModifierFlag :: enum UInteger {
CapsLock = 16, // Set if Caps Lock key is pressed.
Shift = 17, // Set if Shift key is pressed.
Control = 18, // Set if Control key is pressed.
Option = 19, // Set if Option or Alternate key is pressed.
Command = 20, // Set if Command key is pressed.
NumericPad = 21, // Set if any key in the numeric keypad is pressed.
Help = 22, // Set if the Help key is pressed.
Function = 23, // Set if any function key is pressed.
}
KeyEquivalentModifierMask :: distinct bit_set[KeyEquivalentModifierFlag; UInteger]
// Used to retrieve only the device-independent modifier flags, allowing applications to mask off the device-dependent modifier flags, including event coalescing information.
KeyEventModifierFlagDeviceIndependentFlagsMask := transmute(KeyEquivalentModifierMask)_KeyEventModifierFlagDeviceIndependentFlagsMask
@(private) _KeyEventModifierFlagDeviceIndependentFlagsMask := UInteger(0xffff0000)
MenuItemCallback :: proc "c" (unused: rawptr, name: SEL, sender: ^Object) MenuSelectionMode :: enum c.long {
Automatic = 0,
SelectOne = 1,
@(objc_class="NSMenuItem") SelectAny = 2,
MenuItem :: struct {using _: Object}
@(objc_type=MenuItem, objc_name="alloc", objc_is_class_method=true)
MenuItem_alloc :: proc "c" () -> ^MenuItem {
return msgSend(^MenuItem, MenuItem, "alloc")
} }
@(objc_type=MenuItem, objc_name="registerActionCallback", objc_is_class_method=true) MenuPresentationStyle :: enum c.long {
MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCallback) -> SEL { Regular = 0,
s := string(name) Palette = 1,
n := len(s)
sel: SEL
if n > 0 && s[n-1] != ':' {
col_name := intrinsics.alloca(n+2, 1)
builtin.copy(col_name[:n], s)
col_name[n] = ':'
col_name[n+1] = 0
sel = sel_registerName(cstring(col_name))
} else {
sel = sel_registerName(name)
}
if callback != nil {
class_addMethod(intrinsics.objc_find_class("NSObject"), sel, auto_cast callback, "v@:@")
}
return sel
} }
@(objc_type=MenuItem, objc_name="separatorItem", objc_is_class_method=true) UserInterfaceLayoutDirection :: enum c.long {
MenuItem_separatorItem :: proc "c" () -> ^MenuItem { LeftToRight = 0,
return msgSend(^MenuItem, MenuItem, "separatorItem") RightToLeft = 1,
} }
@(objc_type=MenuItem, objc_name="init") MenuPropertyItem :: enum c.ulong {
MenuItem_init :: proc "c" (self: ^MenuItem) -> ^MenuItem { Title = 0,
return msgSend(^MenuItem, self, "init") AttributedTitle = 1,
KeyEquivalent = 2,
Image = 3,
Enabled = 4,
AccessibilityDescription = 5,
} }
MenuProperties :: distinct bit_set[MenuPropertyItem; c.ulong]
@(objc_type=MenuItem, objc_name="initWithTitle")
MenuItem_initWithTitle :: proc "c" (self: ^MenuItem, title: ^String, action: SEL, keyEquivalent: ^String) -> ^MenuItem {
return msgSend(^MenuItem, self, "initWithTitle:action:keyEquivalent:", title, action, keyEquivalent)
}
@(objc_type=MenuItem, objc_name="setKeyEquivalentModifierMask")
MenuItem_setKeyEquivalentModifierMask :: proc "c" (self: ^MenuItem, modifierMask: KeyEquivalentModifierMask) {
msgSend(nil, self, "setKeyEquivalentModifierMask:", modifierMask)
}
@(objc_type=MenuItem, objc_name="keyEquivalentModifierMask")
MenuItem_keyEquivalentModifierMask :: proc "c" (self: ^MenuItem) -> KeyEquivalentModifierMask {
return msgSend(KeyEquivalentModifierMask, self, "keyEquivalentModifierMask")
}
@(objc_type=MenuItem, objc_name="setSubmenu")
MenuItem_setSubmenu :: proc "c" (self: ^MenuItem, submenu: ^Menu) {
msgSend(nil, self, "setSubmenu:", submenu)
}
@(objc_type=MenuItem, objc_name="title")
MenuItem_title :: proc "c" (self: ^MenuItem) -> ^String {
return msgSend(^String, self, "title")
}
@(objc_type=MenuItem, objc_name="setTitle")
MenuItem_setTitle :: proc "c" (self: ^MenuItem, title: ^String) -> ^String {
return msgSend(^String, self, "title:", title)
}
@(objc_class="NSMenu") @(objc_class="NSMenu")
Menu :: struct {using _: Object} Menu :: struct {using _: Object}
@(objc_type=Menu, objc_name="alloc", objc_is_class_method=true)
Menu_alloc :: proc "c" () -> ^Menu {
return msgSend(^Menu, Menu, "alloc")
}
@(objc_type=Menu, objc_name="init") @(objc_type=Menu, objc_name="init")
Menu_init :: proc "c" (self: ^Menu) -> ^Menu { Menu_init :: proc "c" (self: ^Menu) -> ^Menu {
return msgSend(^Menu, self, "init") return msgSend(^Menu, self, "init")
} }
@(objc_type=Menu, objc_name="initWithTitle") @(objc_type=Menu, objc_name="initWithTitle")
Menu_initWithTitle :: proc "c" (self: ^Menu, title: ^String) -> ^Menu { Menu_initWithTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) -> ^Menu {
return msgSend(^Menu, self, "initWithTitle:", title) return msgSend(^Menu, self, "initWithTitle:", title)
} }
@(objc_type=Menu, objc_name="initWithCoder")
Menu_initWithCoder :: #force_inline proc "c" (self: ^Menu, coder: ^Coder) -> ^Menu {
return msgSend(^Menu, self, "initWithCoder:", coder)
}
@(objc_type=Menu, objc_name="popUpContextMenu_withEvent_forView", objc_is_class_method=true)
Menu_popUpContextMenu_withEvent_forView :: #force_inline proc "c" (menu: ^Menu, event: ^Event, view: ^View) {
msgSend(nil, Menu, "popUpContextMenu:withEvent:forView:", menu, event, view)
}
// @(objc_type=Menu, objc_name="popUpContextMenu_withEvent_forView_withFont", objc_is_class_method=true)
// Menu_popUpContextMenu_withEvent_forView_withFont :: #force_inline proc "c" (menu: ^Menu, event: ^Event, view: ^View, font: ^Font) {
// msgSend(nil, Menu, "popUpContextMenu:withEvent:forView:withFont:", menu, event, view, font)
// }
@(objc_type=Menu, objc_name="popUpMenuPositioningItem")
Menu_popUpMenuPositioningItem :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem, location: Point, view: ^View) -> bool {
return msgSend(bool, self, "popUpMenuPositioningItem:atLocation:inView:", item, location, view)
}
@(objc_type=Menu, objc_name="setMenuBarVisible", objc_is_class_method=true)
Menu_setMenuBarVisible :: #force_inline proc "c" (visible: bool) {
msgSend(nil, Menu, "setMenuBarVisible:", visible)
}
@(objc_type=Menu, objc_name="menuBarVisible", objc_is_class_method=true)
Menu_menuBarVisible :: #force_inline proc "c" () -> bool {
return msgSend(bool, Menu, "menuBarVisible")
}
@(objc_type=Menu, objc_name="insertItem")
Menu_insertItem :: #force_inline proc "c" (self: ^Menu, newItem: ^MenuItem, index: Integer) {
msgSend(nil, self, "insertItem:atIndex:", newItem, index)
}
@(objc_type=Menu, objc_name="addItem") @(objc_type=Menu, objc_name="addItem")
Menu_addItem :: proc "c" (self: ^Menu, item: ^MenuItem) { Menu_addItem :: #force_inline proc "c" (self: ^Menu, newItem: ^MenuItem) {
msgSend(nil, self, "addItem:", item) msgSend(nil, self, "addItem:", newItem)
}
@(objc_type=Menu, objc_name="insertItemWithTitle")
Menu_insertItemWithTitle :: #force_inline proc "c" (self: ^Menu, string: ^String, selector: SEL, charCode: ^String, index: Integer) -> ^MenuItem {
return msgSend(^MenuItem, self, "insertItemWithTitle:action:keyEquivalent:atIndex:", string, selector, charCode, index)
} }
@(objc_type=Menu, objc_name="addItemWithTitle") @(objc_type=Menu, objc_name="addItemWithTitle")
Menu_addItemWithTitle :: proc "c" (self: ^Menu, title: ^String, selector: SEL, keyEquivalent: ^String) -> ^MenuItem { Menu_addItemWithTitle :: #force_inline proc "c" (self: ^Menu, string: ^String, selector: SEL, charCode: ^String) -> ^MenuItem {
return msgSend(^MenuItem, self, "addItemWithTitle:action:keyEquivalent:", title, selector, keyEquivalent) return msgSend(^MenuItem, self, "addItemWithTitle:action:keyEquivalent:", string, selector, charCode)
}
@(objc_type=Menu, objc_name="removeItemAtIndex")
Menu_removeItemAtIndex :: #force_inline proc "c" (self: ^Menu, index: Integer) {
msgSend(nil, self, "removeItemAtIndex:", index)
}
@(objc_type=Menu, objc_name="removeItem")
Menu_removeItem :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem) {
msgSend(nil, self, "removeItem:", item)
}
@(objc_type=Menu, objc_name="setSubmenu")
Menu_setSubmenu :: #force_inline proc "c" (self: ^Menu, menu: ^Menu, item: ^MenuItem) {
msgSend(nil, self, "setSubmenu:forItem:", menu, item)
}
@(objc_type=Menu, objc_name="removeAllItems")
Menu_removeAllItems :: #force_inline proc "c" (self: ^Menu) {
msgSend(nil, self, "removeAllItems")
}
@(objc_type=Menu, objc_name="itemAtIndex")
Menu_itemAtIndex :: #force_inline proc "c" (self: ^Menu, index: Integer) -> ^MenuItem {
return msgSend(^MenuItem, self, "itemAtIndex:", index)
}
@(objc_type=Menu, objc_name="indexOfItem")
Menu_indexOfItem :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem) -> Integer {
return msgSend(Integer, self, "indexOfItem:", item)
}
@(objc_type=Menu, objc_name="indexOfItemWithTitle")
Menu_indexOfItemWithTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) -> Integer {
return msgSend(Integer, self, "indexOfItemWithTitle:", title)
}
@(objc_type=Menu, objc_name="indexOfItemWithTag")
Menu_indexOfItemWithTag :: #force_inline proc "c" (self: ^Menu, tag: Integer) -> Integer {
return msgSend(Integer, self, "indexOfItemWithTag:", tag)
}
@(objc_type=Menu, objc_name="indexOfItemWithRepresentedObject")
Menu_indexOfItemWithRepresentedObject :: #force_inline proc "c" (self: ^Menu, object: id) -> Integer {
return msgSend(Integer, self, "indexOfItemWithRepresentedObject:", object)
}
@(objc_type=Menu, objc_name="indexOfItemWithSubmenu")
Menu_indexOfItemWithSubmenu :: #force_inline proc "c" (self: ^Menu, submenu: ^Menu) -> Integer {
return msgSend(Integer, self, "indexOfItemWithSubmenu:", submenu)
}
@(objc_type=Menu, objc_name="indexOfItemWithTarget")
Menu_indexOfItemWithTarget :: #force_inline proc "c" (self: ^Menu, target: id, actionSelector: SEL) -> Integer {
return msgSend(Integer, self, "indexOfItemWithTarget:andAction:", target, actionSelector)
}
@(objc_type=Menu, objc_name="itemWithTitle")
Menu_itemWithTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) -> ^MenuItem {
return msgSend(^MenuItem, self, "itemWithTitle:", title)
}
@(objc_type=Menu, objc_name="itemWithTag")
Menu_itemWithTag :: #force_inline proc "c" (self: ^Menu, tag: Integer) -> ^MenuItem {
return msgSend(^MenuItem, self, "itemWithTag:", tag)
}
@(objc_type=Menu, objc_name="update")
Menu_update :: #force_inline proc "c" (self: ^Menu) {
msgSend(nil, self, "update")
}
@(objc_type=Menu, objc_name="performKeyEquivalent")
Menu_performKeyEquivalent :: #force_inline proc "c" (self: ^Menu, event: ^Event) -> bool {
return msgSend(bool, self, "performKeyEquivalent:", event)
}
@(objc_type=Menu, objc_name="itemChanged")
Menu_itemChanged :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem) {
msgSend(nil, self, "itemChanged:", item)
}
@(objc_type=Menu, objc_name="performActionForItemAtIndex")
Menu_performActionForItemAtIndex :: #force_inline proc "c" (self: ^Menu, index: Integer) {
msgSend(nil, self, "performActionForItemAtIndex:", index)
}
@(objc_type=Menu, objc_name="cancelTracking")
Menu_cancelTracking :: #force_inline proc "c" (self: ^Menu) {
msgSend(nil, self, "cancelTracking")
}
@(objc_type=Menu, objc_name="cancelTrackingWithoutAnimation")
Menu_cancelTrackingWithoutAnimation :: #force_inline proc "c" (self: ^Menu) {
msgSend(nil, self, "cancelTrackingWithoutAnimation")
}
@(objc_type=Menu, objc_name="title")
Menu_title :: #force_inline proc "c" (self: ^Menu) -> ^String {
return msgSend(^String, self, "title")
}
@(objc_type=Menu, objc_name="setTitle")
Menu_setTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) {
msgSend(nil, self, "setTitle:", title)
}
@(objc_type=Menu, objc_name="supermenu")
Menu_supermenu :: #force_inline proc "c" (self: ^Menu) -> ^Menu {
return msgSend(^Menu, self, "supermenu")
}
@(objc_type=Menu, objc_name="setSupermenu")
Menu_setSupermenu :: #force_inline proc "c" (self: ^Menu, supermenu: ^Menu) {
msgSend(nil, self, "setSupermenu:", supermenu)
} }
@(objc_type=Menu, objc_name="itemArray") @(objc_type=Menu, objc_name="itemArray")
Menu_itemArray :: proc "c" (self: ^Menu) -> ^Array { Menu_itemArray :: #force_inline proc "c" (self: ^Menu) -> ^Array {
return msgSend(^Array, self, "itemArray") return msgSend(^Array, self, "itemArray")
} }
@(objc_type=Menu, objc_name="setItemArray")
Menu_setItemArray :: #force_inline proc "c" (self: ^Menu, itemArray: ^Array) {
msgSend(nil, self, "setItemArray:", itemArray)
}
@(objc_type=Menu, objc_name="numberOfItems")
Menu_numberOfItems :: #force_inline proc "c" (self: ^Menu) -> Integer {
return msgSend(Integer, self, "numberOfItems")
}
@(objc_type=Menu, objc_name="autoenablesItems")
Menu_autoenablesItems :: #force_inline proc "c" (self: ^Menu) -> bool {
return msgSend(bool, self, "autoenablesItems")
}
@(objc_type=Menu, objc_name="setAutoenablesItems")
Menu_setAutoenablesItems :: #force_inline proc "c" (self: ^Menu, autoenablesItems: bool) {
msgSend(nil, self, "setAutoenablesItems:", autoenablesItems)
}
@(objc_type=Menu, objc_name="delegate")
Menu_delegate :: #force_inline proc "c" (self: ^Menu) -> ^MenuDelegate {
return msgSend(^MenuDelegate, self, "delegate")
}
@(objc_type=Menu, objc_name="setDelegate")
Menu_setDelegate :: #force_inline proc "c" (self: ^Menu, delegate: ^MenuDelegate) {
msgSend(nil, self, "setDelegate:", delegate)
}
@(objc_type=Menu, objc_name="menuBarHeight")
Menu_menuBarHeight :: #force_inline proc "c" (self: ^Menu) -> Float {
return msgSend(Float, self, "menuBarHeight")
}
@(objc_type=Menu, objc_name="highlightedItem")
Menu_highlightedItem :: #force_inline proc "c" (self: ^Menu) -> ^MenuItem {
return msgSend(^MenuItem, self, "highlightedItem")
}
@(objc_type=Menu, objc_name="minimumWidth")
Menu_minimumWidth :: #force_inline proc "c" (self: ^Menu) -> Float {
return msgSend(Float, self, "minimumWidth")
}
@(objc_type=Menu, objc_name="setMinimumWidth")
Menu_setMinimumWidth :: #force_inline proc "c" (self: ^Menu, minimumWidth: Float) {
msgSend(nil, self, "setMinimumWidth:", minimumWidth)
}
@(objc_type=Menu, objc_name="size")
Menu_size :: #force_inline proc "c" (self: ^Menu) -> Size {
return msgSend(Size, self, "size")
}
// @(objc_type=Menu, objc_name="font")
// Menu_font :: #force_inline proc "c" (self: ^Menu) -> ^Font {
// return msgSend(^Font, self, "font")
// }
// @(objc_type=Menu, objc_name="setFont")
// Menu_setFont :: #force_inline proc "c" (self: ^Menu, font: ^Font) {
// msgSend(nil, self, "setFont:", font)
// }
@(objc_type=Menu, objc_name="allowsContextMenuPlugIns")
Menu_allowsContextMenuPlugIns :: #force_inline proc "c" (self: ^Menu) -> bool {
return msgSend(bool, self, "allowsContextMenuPlugIns")
}
@(objc_type=Menu, objc_name="setAllowsContextMenuPlugIns")
Menu_setAllowsContextMenuPlugIns :: #force_inline proc "c" (self: ^Menu, allowsContextMenuPlugIns: bool) {
msgSend(nil, self, "setAllowsContextMenuPlugIns:", allowsContextMenuPlugIns)
}
@(objc_type=Menu, objc_name="showsStateColumn")
Menu_showsStateColumn :: #force_inline proc "c" (self: ^Menu) -> bool {
return msgSend(bool, self, "showsStateColumn")
}
@(objc_type=Menu, objc_name="setShowsStateColumn")
Menu_setShowsStateColumn :: #force_inline proc "c" (self: ^Menu, showsStateColumn: bool) {
msgSend(nil, self, "setShowsStateColumn:", showsStateColumn)
}
@(objc_type=Menu, objc_name="userInterfaceLayoutDirection")
Menu_userInterfaceLayoutDirection :: #force_inline proc "c" (self: ^Menu) -> UserInterfaceLayoutDirection {
return msgSend(UserInterfaceLayoutDirection, self, "userInterfaceLayoutDirection")
}
@(objc_type=Menu, objc_name="setUserInterfaceLayoutDirection")
Menu_setUserInterfaceLayoutDirection :: #force_inline proc "c" (self: ^Menu, userInterfaceLayoutDirection: UserInterfaceLayoutDirection) {
msgSend(nil, self, "setUserInterfaceLayoutDirection:", userInterfaceLayoutDirection)
}
@(objc_type=Menu, objc_name="paletteMenuWithColors_titles_selectionHandler", objc_is_class_method=true)
Menu_paletteMenuWithColors_titles_selectionHandler :: #force_inline proc "c" (colors: ^Array, itemTitles: ^Array, onSelectionChange: proc "c" (_arg_0: ^Menu)) -> ^Menu {
return msgSend(^Menu, Menu, "paletteMenuWithColors:titles:selectionHandler:", colors, itemTitles, onSelectionChange)
}
// @(objc_type=Menu, objc_name="paletteMenuWithColors_titles_templateImage_selectionHandler", objc_is_class_method=true)
// Menu_paletteMenuWithColors_titles_templateImage_selectionHandler :: #force_inline proc "c" (colors: ^Array, itemTitles: ^Array, image: ^Image, onSelectionChange: proc "c" (_arg_0: ^Menu)) -> ^Menu {
// return msgSend(^Menu, Menu, "paletteMenuWithColors:titles:templateImage:selectionHandler:", colors, itemTitles, image, onSelectionChange)
// }
@(objc_type=Menu, objc_name="presentationStyle")
Menu_presentationStyle :: #force_inline proc "c" (self: ^Menu) -> MenuPresentationStyle {
return msgSend(MenuPresentationStyle, self, "presentationStyle")
}
@(objc_type=Menu, objc_name="setPresentationStyle")
Menu_setPresentationStyle :: #force_inline proc "c" (self: ^Menu, presentationStyle: MenuPresentationStyle) {
msgSend(nil, self, "setPresentationStyle:", presentationStyle)
}
@(objc_type=Menu, objc_name="selectionMode")
Menu_selectionMode :: #force_inline proc "c" (self: ^Menu) -> MenuSelectionMode {
return msgSend(MenuSelectionMode, self, "selectionMode")
}
@(objc_type=Menu, objc_name="setSelectionMode")
Menu_setSelectionMode :: #force_inline proc "c" (self: ^Menu, selectionMode: MenuSelectionMode) {
msgSend(nil, self, "setSelectionMode:", selectionMode)
}
@(objc_type=Menu, objc_name="selectedItems")
Menu_selectedItems :: #force_inline proc "c" (self: ^Menu) -> ^Array {
return msgSend(^Array, self, "selectedItems")
}
@(objc_type=Menu, objc_name="setSelectedItems")
Menu_setSelectedItems :: #force_inline proc "c" (self: ^Menu, selectedItems: ^Array) {
msgSend(nil, self, "setSelectedItems:", selectedItems)
}
@(objc_type=Menu, objc_name="submenuAction")
Menu_submenuAction :: #force_inline proc "c" (self: ^Menu, sender: id) {
msgSend(nil, self, "submenuAction:", sender)
}
@(objc_type=Menu, objc_name="propertiesToUpdate")
Menu_propertiesToUpdate :: #force_inline proc "c" (self: ^Menu) -> MenuProperties {
return msgSend(MenuProperties, self, "propertiesToUpdate")
}
@(objc_type=Menu, objc_name="setMenuRepresentation")
Menu_setMenuRepresentation :: #force_inline proc "c" (self: ^Menu, menuRep: id) {
msgSend(nil, self, "setMenuRepresentation:", menuRep)
}
@(objc_type=Menu, objc_name="menuRepresentation")
Menu_menuRepresentation :: #force_inline proc "c" (self: ^Menu) -> id {
return msgSend(id, self, "menuRepresentation")
}
@(objc_type=Menu, objc_name="setContextMenuRepresentation")
Menu_setContextMenuRepresentation :: #force_inline proc "c" (self: ^Menu, menuRep: id) {
msgSend(nil, self, "setContextMenuRepresentation:", menuRep)
}
@(objc_type=Menu, objc_name="contextMenuRepresentation")
Menu_contextMenuRepresentation :: #force_inline proc "c" (self: ^Menu) -> id {
return msgSend(id, self, "contextMenuRepresentation")
}
@(objc_type=Menu, objc_name="setTearOffMenuRepresentation")
Menu_setTearOffMenuRepresentation :: #force_inline proc "c" (self: ^Menu, menuRep: id) {
msgSend(nil, self, "setTearOffMenuRepresentation:", menuRep)
}
@(objc_type=Menu, objc_name="tearOffMenuRepresentation")
Menu_tearOffMenuRepresentation :: #force_inline proc "c" (self: ^Menu) -> id {
return msgSend(id, self, "tearOffMenuRepresentation")
}
@(objc_type=Menu, objc_name="menuZone", objc_is_class_method=true)
Menu_menuZone :: #force_inline proc "c" () -> ^Zone {
return msgSend(^Zone, Menu, "menuZone")
}
@(objc_type=Menu, objc_name="setMenuZone", objc_is_class_method=true)
Menu_setMenuZone :: #force_inline proc "c" (zone: ^Zone) {
msgSend(nil, Menu, "setMenuZone:", zone)
}
@(objc_type=Menu, objc_name="attachedMenu")
Menu_attachedMenu :: #force_inline proc "c" (self: ^Menu) -> ^Menu {
return msgSend(^Menu, self, "attachedMenu")
}
@(objc_type=Menu, objc_name="isAttached")
Menu_isAttached :: #force_inline proc "c" (self: ^Menu) -> bool {
return msgSend(bool, self, "isAttached")
}
@(objc_type=Menu, objc_name="sizeToFit")
Menu_sizeToFit :: #force_inline proc "c" (self: ^Menu) {
msgSend(nil, self, "sizeToFit")
}
@(objc_type=Menu, objc_name="locationForSubmenu")
Menu_locationForSubmenu :: #force_inline proc "c" (self: ^Menu, submenu: ^Menu) -> Point {
return msgSend(Point, self, "locationForSubmenu:", submenu)
}
@(objc_type=Menu, objc_name="helpRequested")
Menu_helpRequested :: #force_inline proc "c" (self: ^Menu, eventPtr: ^Event) {
msgSend(nil, self, "helpRequested:", eventPtr)
}
@(objc_type=Menu, objc_name="menuChangedMessagesEnabled")
Menu_menuChangedMessagesEnabled :: #force_inline proc "c" (self: ^Menu) -> bool {
return msgSend(bool, self, "menuChangedMessagesEnabled")
}
@(objc_type=Menu, objc_name="setMenuChangedMessagesEnabled")
Menu_setMenuChangedMessagesEnabled :: #force_inline proc "c" (self: ^Menu, menuChangedMessagesEnabled: bool) {
msgSend(nil, self, "setMenuChangedMessagesEnabled:", menuChangedMessagesEnabled)
}
@(objc_type=Menu, objc_name="isTornOff")
Menu_isTornOff :: #force_inline proc "c" (self: ^Menu) -> bool {
return msgSend(bool, self, "isTornOff")
}
@(objc_type=Menu, objc_name="load", objc_is_class_method=true)
Menu_load :: #force_inline proc "c" () {
msgSend(nil, Menu, "load")
}
@(objc_type=Menu, objc_name="initialize", objc_is_class_method=true)
Menu_initialize :: #force_inline proc "c" () {
msgSend(nil, Menu, "initialize")
}
@(objc_type=Menu, objc_name="new", objc_is_class_method=true)
Menu_new :: #force_inline proc "c" () -> ^Menu {
return msgSend(^Menu, Menu, "new")
}
@(objc_type=Menu, objc_name="allocWithZone", objc_is_class_method=true)
Menu_allocWithZone :: #force_inline proc "c" (zone: ^Zone) -> ^Menu {
return msgSend(^Menu, Menu, "allocWithZone:", zone)
}
@(objc_type=Menu, objc_name="alloc", objc_is_class_method=true)
Menu_alloc :: #force_inline proc "c" () -> ^Menu {
return msgSend(^Menu, Menu, "alloc")
}
@(objc_type=Menu, objc_name="copyWithZone", objc_is_class_method=true)
Menu_copyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id {
return msgSend(id, Menu, "copyWithZone:", zone)
}
@(objc_type=Menu, objc_name="mutableCopyWithZone", objc_is_class_method=true)
Menu_mutableCopyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id {
return msgSend(id, Menu, "mutableCopyWithZone:", zone)
}
@(objc_type=Menu, objc_name="instancesRespondToSelector", objc_is_class_method=true)
Menu_instancesRespondToSelector :: #force_inline proc "c" (aSelector: SEL) -> bool {
return msgSend(bool, Menu, "instancesRespondToSelector:", aSelector)
}
@(objc_type=Menu, objc_name="conformsToProtocol", objc_is_class_method=true)
Menu_conformsToProtocol :: #force_inline proc "c" (protocol: ^Protocol) -> bool {
return msgSend(bool, Menu, "conformsToProtocol:", protocol)
}
@(objc_type=Menu, objc_name="instanceMethodForSelector", objc_is_class_method=true)
Menu_instanceMethodForSelector :: #force_inline proc "c" (aSelector: SEL) -> IMP {
return msgSend(IMP, Menu, "instanceMethodForSelector:", aSelector)
}
// @(objc_type=Menu, objc_name="instanceMethodSignatureForSelector", objc_is_class_method=true)
// Menu_instanceMethodSignatureForSelector :: #force_inline proc "c" (aSelector: SEL) -> ^MethodSignature {
// return msgSend(^MethodSignature, Menu, "instanceMethodSignatureForSelector:", aSelector)
// }
@(objc_type=Menu, objc_name="isSubclassOfClass", objc_is_class_method=true)
Menu_isSubclassOfClass :: #force_inline proc "c" (aClass: Class) -> bool {
return msgSend(bool, Menu, "isSubclassOfClass:", aClass)
}
@(objc_type=Menu, objc_name="resolveClassMethod", objc_is_class_method=true)
Menu_resolveClassMethod :: #force_inline proc "c" (sel: SEL) -> bool {
return msgSend(bool, Menu, "resolveClassMethod:", sel)
}
@(objc_type=Menu, objc_name="resolveInstanceMethod", objc_is_class_method=true)
Menu_resolveInstanceMethod :: #force_inline proc "c" (sel: SEL) -> bool {
return msgSend(bool, Menu, "resolveInstanceMethod:", sel)
}
@(objc_type=Menu, objc_name="hash", objc_is_class_method=true)
Menu_hash :: #force_inline proc "c" () -> UInteger {
return msgSend(UInteger, Menu, "hash")
}
@(objc_type=Menu, objc_name="superclass", objc_is_class_method=true)
Menu_superclass :: #force_inline proc "c" () -> Class {
return msgSend(Class, Menu, "superclass")
}
@(objc_type=Menu, objc_name="class", objc_is_class_method=true)
Menu_class :: #force_inline proc "c" () -> Class {
return msgSend(Class, Menu, "class")
}
@(objc_type=Menu, objc_name="description", objc_is_class_method=true)
Menu_description :: #force_inline proc "c" () -> ^String {
return msgSend(^String, Menu, "description")
}
@(objc_type=Menu, objc_name="debugDescription", objc_is_class_method=true)
Menu_debugDescription :: #force_inline proc "c" () -> ^String {
return msgSend(^String, Menu, "debugDescription")
}
@(objc_type=Menu, objc_name="version", objc_is_class_method=true)
Menu_version :: #force_inline proc "c" () -> Integer {
return msgSend(Integer, Menu, "version")
}
@(objc_type=Menu, objc_name="setVersion", objc_is_class_method=true)
Menu_setVersion :: #force_inline proc "c" (aVersion: Integer) {
msgSend(nil, Menu, "setVersion:", aVersion)
}
@(objc_type=Menu, objc_name="poseAsClass", objc_is_class_method=true)
Menu_poseAsClass :: #force_inline proc "c" (aClass: Class) {
msgSend(nil, Menu, "poseAsClass:", aClass)
}
@(objc_type=Menu, objc_name="cancelPreviousPerformRequestsWithTarget_selector_object", objc_is_class_method=true)
Menu_cancelPreviousPerformRequestsWithTarget_selector_object :: #force_inline proc "c" (aTarget: id, aSelector: SEL, anArgument: id) {
msgSend(nil, Menu, "cancelPreviousPerformRequestsWithTarget:selector:object:", aTarget, aSelector, anArgument)
}
@(objc_type=Menu, objc_name="cancelPreviousPerformRequestsWithTarget_", objc_is_class_method=true)
Menu_cancelPreviousPerformRequestsWithTarget_ :: #force_inline proc "c" (aTarget: id) {
msgSend(nil, Menu, "cancelPreviousPerformRequestsWithTarget:", aTarget)
}
@(objc_type=Menu, objc_name="accessInstanceVariablesDirectly", objc_is_class_method=true)
Menu_accessInstanceVariablesDirectly :: #force_inline proc "c" () -> bool {
return msgSend(bool, Menu, "accessInstanceVariablesDirectly")
}
@(objc_type=Menu, objc_name="useStoredAccessor", objc_is_class_method=true)
Menu_useStoredAccessor :: #force_inline proc "c" () -> bool {
return msgSend(bool, Menu, "useStoredAccessor")
}
@(objc_type=Menu, objc_name="keyPathsForValuesAffectingValueForKey", objc_is_class_method=true)
Menu_keyPathsForValuesAffectingValueForKey :: #force_inline proc "c" (key: ^String) -> ^Set {
return msgSend(^Set, Menu, "keyPathsForValuesAffectingValueForKey:", key)
}
@(objc_type=Menu, objc_name="automaticallyNotifiesObserversForKey", objc_is_class_method=true)
Menu_automaticallyNotifiesObserversForKey :: #force_inline proc "c" (key: ^String) -> bool {
return msgSend(bool, Menu, "automaticallyNotifiesObserversForKey:", key)
}
@(objc_type=Menu, objc_name="setKeys", objc_is_class_method=true)
Menu_setKeys :: #force_inline proc "c" (keys: ^Array, dependentKey: ^String) {
msgSend(nil, Menu, "setKeys:triggerChangeNotificationsForDependentKey:", keys, dependentKey)
}
@(objc_type=Menu, objc_name="classFallbacksForKeyedArchiver", objc_is_class_method=true)
Menu_classFallbacksForKeyedArchiver :: #force_inline proc "c" () -> ^Array {
return msgSend(^Array, Menu, "classFallbacksForKeyedArchiver")
}
@(objc_type=Menu, objc_name="classForKeyedUnarchiver", objc_is_class_method=true)
Menu_classForKeyedUnarchiver :: #force_inline proc "c" () -> Class {
return msgSend(Class, Menu, "classForKeyedUnarchiver")
}
@(objc_type=Menu, objc_name="exposeBinding", objc_is_class_method=true)
Menu_exposeBinding :: #force_inline proc "c" (binding: ^String) {
msgSend(nil, Menu, "exposeBinding:", binding)
}
@(objc_type=Menu, objc_name="setDefaultPlaceholder", objc_is_class_method=true)
Menu_setDefaultPlaceholder :: #force_inline proc "c" (placeholder: id, marker: id, binding: ^String) {
msgSend(nil, Menu, "setDefaultPlaceholder:forMarker:withBinding:", placeholder, marker, binding)
}
@(objc_type=Menu, objc_name="defaultPlaceholderForMarker", objc_is_class_method=true)
Menu_defaultPlaceholderForMarker :: #force_inline proc "c" (marker: id, binding: ^String) -> id {
return msgSend(id, Menu, "defaultPlaceholderForMarker:withBinding:", marker, binding)
}
@(objc_type=Menu, objc_name="popUpContextMenu")
Menu_popUpContextMenu :: proc {
Menu_popUpContextMenu_withEvent_forView,
// Menu_popUpContextMenu_withEvent_forView_withFont,
}
@(objc_type=Menu, objc_name="paletteMenuWithColors")
Menu_paletteMenuWithColors :: proc {
Menu_paletteMenuWithColors_titles_selectionHandler,
// Menu_paletteMenuWithColors_titles_templateImage_selectionHandler,
}
@(objc_type=Menu, objc_name="cancelPreviousPerformRequestsWithTarget")
Menu_cancelPreviousPerformRequestsWithTarget :: proc {
Menu_cancelPreviousPerformRequestsWithTarget_selector_object,
Menu_cancelPreviousPerformRequestsWithTarget_,
}
@(objc_class="NSMenuDelegate")
MenuDelegate :: struct {using _: Object, using _: ObjectProtocol}
@(objc_type=MenuDelegate, objc_name="menuNeedsUpdate")
MenuDelegate_menuNeedsUpdate :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) {
msgSend(nil, self, "menuNeedsUpdate:", menu)
}
@(objc_type=MenuDelegate, objc_name="numberOfItemsInMenu")
MenuDelegate_numberOfItemsInMenu :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) -> Integer {
return msgSend(Integer, self, "numberOfItemsInMenu:", menu)
}
@(objc_type=MenuDelegate, objc_name="menu_updateItem_atIndex_shouldCancel")
MenuDelegate_menu_updateItem_atIndex_shouldCancel :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, item: ^MenuItem, index: Integer, shouldCancel: bool) -> bool {
return msgSend(bool, self, "menu:updateItem:atIndex:shouldCancel:", menu, item, index, shouldCancel)
}
@(objc_type=MenuDelegate, objc_name="menuHasKeyEquivalent")
MenuDelegate_menuHasKeyEquivalent :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, event: ^Event, target: ^id, action: ^SEL) -> bool {
return msgSend(bool, self, "menuHasKeyEquivalent:forEvent:target:action:", menu, event, target, action)
}
@(objc_type=MenuDelegate, objc_name="menuWillOpen")
MenuDelegate_menuWillOpen :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) {
msgSend(nil, self, "menuWillOpen:", menu)
}
@(objc_type=MenuDelegate, objc_name="menuDidClose")
MenuDelegate_menuDidClose :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) {
msgSend(nil, self, "menuDidClose:", menu)
}
@(objc_type=MenuDelegate, objc_name="menu_willHighlightItem")
MenuDelegate_menu_willHighlightItem :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, item: ^MenuItem) {
msgSend(nil, self, "menu:willHighlightItem:", menu, item)
}
@(objc_type=MenuDelegate, objc_name="confinementRectForMenu")
MenuDelegate_confinementRectForMenu :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, screen: ^Screen) -> Rect {
return msgSend(Rect, self, "confinementRectForMenu:onScreen:", menu, screen)
}
@(objc_type=MenuDelegate, objc_name="menu")
MenuDelegate_menu :: proc {
MenuDelegate_menu_updateItem_atIndex_shouldCancel,
MenuDelegate_menu_willHighlightItem,
}
+460
View File
@@ -0,0 +1,460 @@
package objc_Foundation
import "base:builtin"
import "base:intrinsics"
KeyEquivalentModifierFlag :: EventModifierFlag
KeyEquivalentModifierMask :: EventModifierFlags
// Used to retrieve only the device-independent modifier flags, allowing applications to mask off the device-dependent modifier flags, including event coalescing information.
KeyEventModifierFlagDeviceIndependentFlagsMask := transmute(KeyEquivalentModifierMask)_KeyEventModifierFlagDeviceIndependentFlagsMask
@(private) _KeyEventModifierFlagDeviceIndependentFlagsMask := UInteger(0xffff0000)
MenuItemCallback :: proc "c" (unused: rawptr, name: SEL, sender: ^Object)
@(objc_class="NSMenuItem")
MenuItem :: struct {using _: Object}
@(objc_type=MenuItem, objc_name="registerActionCallback", objc_is_class_method=true)
MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCallback) -> SEL {
s := string(name)
n := len(s)
sel: SEL
if n > 0 && s[n-1] != ':' {
col_name := intrinsics.alloca(n+2, 1)
builtin.copy(col_name[:n], s)
col_name[n] = ':'
col_name[n+1] = 0
sel = sel_registerName(cstring(col_name))
} else {
sel = sel_registerName(name)
}
if callback != nil {
class_addMethod(intrinsics.objc_find_class("NSObject"), sel, auto_cast callback, "v@:@")
}
return sel
}
@(objc_type=MenuItem, objc_name="init")
MenuItem_init :: proc "c" (self: ^MenuItem) -> ^MenuItem {
return msgSend(^MenuItem, self, "init")
}
@(objc_type=MenuItem, objc_name="separatorItem", objc_is_class_method=true)
MenuItem_separatorItem :: #force_inline proc "c" () -> ^MenuItem {
return msgSend(^MenuItem, MenuItem, "separatorItem")
}
@(objc_type=MenuItem, objc_name="sectionHeaderWithTitle", objc_is_class_method=true)
MenuItem_sectionHeaderWithTitle :: #force_inline proc "c" (title: ^String) -> ^MenuItem {
return msgSend(^MenuItem, MenuItem, "sectionHeaderWithTitle:", title)
}
@(objc_type=MenuItem, objc_name="initWithTitle")
MenuItem_initWithTitle :: #force_inline proc "c" (self: ^MenuItem, string: ^String, selector: SEL, charCode: ^String) -> ^MenuItem {
return msgSend(^MenuItem, self, "initWithTitle:action:keyEquivalent:", string, selector, charCode)
}
@(objc_type=MenuItem, objc_name="initWithCoder")
MenuItem_initWithCoder :: #force_inline proc "c" (self: ^MenuItem, coder: ^Coder) -> ^MenuItem {
return msgSend(^MenuItem, self, "initWithCoder:", coder)
}
@(objc_type=MenuItem, objc_name="usesUserKeyEquivalents", objc_is_class_method=true)
MenuItem_usesUserKeyEquivalents :: #force_inline proc "c" () -> bool {
return msgSend(bool, MenuItem, "usesUserKeyEquivalents")
}
@(objc_type=MenuItem, objc_name="setUsesUserKeyEquivalents", objc_is_class_method=true)
MenuItem_setUsesUserKeyEquivalents :: #force_inline proc "c" (usesUserKeyEquivalents: bool) {
msgSend(nil, MenuItem, "setUsesUserKeyEquivalents:", usesUserKeyEquivalents)
}
@(objc_type=MenuItem, objc_name="menu")
MenuItem_menu :: #force_inline proc "c" (self: ^MenuItem) -> ^Menu {
return msgSend(^Menu, self, "menu")
}
@(objc_type=MenuItem, objc_name="setMenu")
MenuItem_setMenu :: #force_inline proc "c" (self: ^MenuItem, menu: ^Menu) {
msgSend(nil, self, "setMenu:", menu)
}
@(objc_type=MenuItem, objc_name="hasSubmenu")
MenuItem_hasSubmenu :: #force_inline proc "c" (self: ^MenuItem) -> bool {
return msgSend(bool, self, "hasSubmenu")
}
@(objc_type=MenuItem, objc_name="submenu")
MenuItem_submenu :: #force_inline proc "c" (self: ^MenuItem) -> ^Menu {
return msgSend(^Menu, self, "submenu")
}
@(objc_type=MenuItem, objc_name="setSubmenu")
MenuItem_setSubmenu :: #force_inline proc "c" (self: ^MenuItem, submenu: ^Menu) {
msgSend(nil, self, "setSubmenu:", submenu)
}
@(objc_type=MenuItem, objc_name="parentItem")
MenuItem_parentItem :: #force_inline proc "c" (self: ^MenuItem) -> ^MenuItem {
return msgSend(^MenuItem, self, "parentItem")
}
@(objc_type=MenuItem, objc_name="title")
MenuItem_title :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
return msgSend(^String, self, "title")
}
@(objc_type=MenuItem, objc_name="setTitle")
MenuItem_setTitle :: #force_inline proc "c" (self: ^MenuItem, title: ^String) {
msgSend(nil, self, "setTitle:", title)
}
// @(objc_type=MenuItem, objc_name="attributedTitle")
// MenuItem_attributedTitle :: #force_inline proc "c" (self: ^MenuItem) -> ^AttributedString {
// return msgSend(^AttributedString, self, "attributedTitle")
// }
// @(objc_type=MenuItem, objc_name="setAttributedTitle")
// MenuItem_setAttributedTitle :: #force_inline proc "c" (self: ^MenuItem, attributedTitle: ^AttributedString) {
// msgSend(nil, self, "setAttributedTitle:", attributedTitle)
// }
@(objc_type=MenuItem, objc_name="subtitle")
MenuItem_subtitle :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
return msgSend(^String, self, "subtitle")
}
@(objc_type=MenuItem, objc_name="setSubtitle")
MenuItem_setSubtitle :: #force_inline proc "c" (self: ^MenuItem, subtitle: ^String) {
msgSend(nil, self, "setSubtitle:", subtitle)
}
@(objc_type=MenuItem, objc_name="isSeparatorItem")
MenuItem_isSeparatorItem :: #force_inline proc "c" (self: ^MenuItem) -> bool {
return msgSend(bool, self, "isSeparatorItem")
}
@(objc_type=MenuItem, objc_name="isSectionHeader")
MenuItem_isSectionHeader :: #force_inline proc "c" (self: ^MenuItem) -> bool {
return msgSend(bool, self, "isSectionHeader")
}
@(objc_type=MenuItem, objc_name="keyEquivalent")
MenuItem_keyEquivalent :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
return msgSend(^String, self, "keyEquivalent")
}
@(objc_type=MenuItem, objc_name="setKeyEquivalent")
MenuItem_setKeyEquivalent :: #force_inline proc "c" (self: ^MenuItem, keyEquivalent: ^String) {
msgSend(nil, self, "setKeyEquivalent:", keyEquivalent)
}
@(objc_type=MenuItem, objc_name="keyEquivalentModifierMask")
MenuItem_keyEquivalentModifierMask :: #force_inline proc "c" (self: ^MenuItem) -> EventModifierFlags {
return msgSend(EventModifierFlags, self, "keyEquivalentModifierMask")
}
@(objc_type=MenuItem, objc_name="setKeyEquivalentModifierMask")
MenuItem_setKeyEquivalentModifierMask :: #force_inline proc "c" (self: ^MenuItem, keyEquivalentModifierMask: EventModifierFlags) {
msgSend(nil, self, "setKeyEquivalentModifierMask:", keyEquivalentModifierMask)
}
@(objc_type=MenuItem, objc_name="userKeyEquivalent")
MenuItem_userKeyEquivalent :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
return msgSend(^String, self, "userKeyEquivalent")
}
@(objc_type=MenuItem, objc_name="allowsKeyEquivalentWhenHidden")
MenuItem_allowsKeyEquivalentWhenHidden :: #force_inline proc "c" (self: ^MenuItem) -> bool {
return msgSend(bool, self, "allowsKeyEquivalentWhenHidden")
}
@(objc_type=MenuItem, objc_name="setAllowsKeyEquivalentWhenHidden")
MenuItem_setAllowsKeyEquivalentWhenHidden :: #force_inline proc "c" (self: ^MenuItem, allowsKeyEquivalentWhenHidden: bool) {
msgSend(nil, self, "setAllowsKeyEquivalentWhenHidden:", allowsKeyEquivalentWhenHidden)
}
@(objc_type=MenuItem, objc_name="allowsAutomaticKeyEquivalentLocalization")
MenuItem_allowsAutomaticKeyEquivalentLocalization :: #force_inline proc "c" (self: ^MenuItem) -> bool {
return msgSend(bool, self, "allowsAutomaticKeyEquivalentLocalization")
}
@(objc_type=MenuItem, objc_name="setAllowsAutomaticKeyEquivalentLocalization")
MenuItem_setAllowsAutomaticKeyEquivalentLocalization :: #force_inline proc "c" (self: ^MenuItem, allowsAutomaticKeyEquivalentLocalization: bool) {
msgSend(nil, self, "setAllowsAutomaticKeyEquivalentLocalization:", allowsAutomaticKeyEquivalentLocalization)
}
@(objc_type=MenuItem, objc_name="allowsAutomaticKeyEquivalentMirroring")
MenuItem_allowsAutomaticKeyEquivalentMirroring :: #force_inline proc "c" (self: ^MenuItem) -> bool {
return msgSend(bool, self, "allowsAutomaticKeyEquivalentMirroring")
}
@(objc_type=MenuItem, objc_name="setAllowsAutomaticKeyEquivalentMirroring")
MenuItem_setAllowsAutomaticKeyEquivalentMirroring :: #force_inline proc "c" (self: ^MenuItem, allowsAutomaticKeyEquivalentMirroring: bool) {
msgSend(nil, self, "setAllowsAutomaticKeyEquivalentMirroring:", allowsAutomaticKeyEquivalentMirroring)
}
// @(objc_type=MenuItem, objc_name="image")
// MenuItem_image :: #force_inline proc "c" (self: ^MenuItem) -> ^Image {
// return msgSend(^Image, self, "image")
// }
// @(objc_type=MenuItem, objc_name="setImage")
// MenuItem_setImage :: #force_inline proc "c" (self: ^MenuItem, image: ^Image) {
// msgSend(nil, self, "setImage:", image)
// }
// @(objc_type=MenuItem, objc_name="state")
// MenuItem_state :: #force_inline proc "c" (self: ^MenuItem) -> ControlStateValue {
// return msgSend(ControlStateValue, self, "state")
// }
// @(objc_type=MenuItem, objc_name="setState")
// MenuItem_setState :: #force_inline proc "c" (self: ^MenuItem, state: ControlStateValue) {
// msgSend(nil, self, "setState:", state)
// }
// @(objc_type=MenuItem, objc_name="onStateImage")
// MenuItem_onStateImage :: #force_inline proc "c" (self: ^MenuItem) -> ^Image {
// return msgSend(^Image, self, "onStateImage")
// }
// @(objc_type=MenuItem, objc_name="setOnStateImage")
// MenuItem_setOnStateImage :: #force_inline proc "c" (self: ^MenuItem, onStateImage: ^Image) {
// msgSend(nil, self, "setOnStateImage:", onStateImage)
// }
// @(objc_type=MenuItem, objc_name="offStateImage")
// MenuItem_offStateImage :: #force_inline proc "c" (self: ^MenuItem) -> ^Image {
// return msgSend(^Image, self, "offStateImage")
// }
// @(objc_type=MenuItem, objc_name="setOffStateImage")
// MenuItem_setOffStateImage :: #force_inline proc "c" (self: ^MenuItem, offStateImage: ^Image) {
// msgSend(nil, self, "setOffStateImage:", offStateImage)
// }
// @(objc_type=MenuItem, objc_name="mixedStateImage")
// MenuItem_mixedStateImage :: #force_inline proc "c" (self: ^MenuItem) -> ^Image {
// return msgSend(^Image, self, "mixedStateImage")
// }
// @(objc_type=MenuItem, objc_name="setMixedStateImage")
// MenuItem_setMixedStateImage :: #force_inline proc "c" (self: ^MenuItem, mixedStateImage: ^Image) {
// msgSend(nil, self, "setMixedStateImage:", mixedStateImage)
// }
@(objc_type=MenuItem, objc_name="isEnabled")
MenuItem_isEnabled :: #force_inline proc "c" (self: ^MenuItem) -> bool {
return msgSend(bool, self, "isEnabled")
}
@(objc_type=MenuItem, objc_name="setEnabled")
MenuItem_setEnabled :: #force_inline proc "c" (self: ^MenuItem, enabled: bool) {
msgSend(nil, self, "setEnabled:", enabled)
}
@(objc_type=MenuItem, objc_name="isAlternate")
MenuItem_isAlternate :: #force_inline proc "c" (self: ^MenuItem) -> bool {
return msgSend(bool, self, "isAlternate")
}
@(objc_type=MenuItem, objc_name="setAlternate")
MenuItem_setAlternate :: #force_inline proc "c" (self: ^MenuItem, alternate: bool) {
msgSend(nil, self, "setAlternate:", alternate)
}
@(objc_type=MenuItem, objc_name="indentationLevel")
MenuItem_indentationLevel :: #force_inline proc "c" (self: ^MenuItem) -> Integer {
return msgSend(Integer, self, "indentationLevel")
}
@(objc_type=MenuItem, objc_name="setIndentationLevel")
MenuItem_setIndentationLevel :: #force_inline proc "c" (self: ^MenuItem, indentationLevel: Integer) {
msgSend(nil, self, "setIndentationLevel:", indentationLevel)
}
@(objc_type=MenuItem, objc_name="target")
MenuItem_target :: #force_inline proc "c" (self: ^MenuItem) -> id {
return msgSend(id, self, "target")
}
@(objc_type=MenuItem, objc_name="setTarget")
MenuItem_setTarget :: #force_inline proc "c" (self: ^MenuItem, target: id) {
msgSend(nil, self, "setTarget:", target)
}
@(objc_type=MenuItem, objc_name="action")
MenuItem_action :: #force_inline proc "c" (self: ^MenuItem) -> SEL {
return msgSend(SEL, self, "action")
}
@(objc_type=MenuItem, objc_name="setAction")
MenuItem_setAction :: #force_inline proc "c" (self: ^MenuItem, action: SEL) {
msgSend(nil, self, "setAction:", action)
}
@(objc_type=MenuItem, objc_name="tag")
MenuItem_tag :: #force_inline proc "c" (self: ^MenuItem) -> Integer {
return msgSend(Integer, self, "tag")
}
@(objc_type=MenuItem, objc_name="setTag")
MenuItem_setTag :: #force_inline proc "c" (self: ^MenuItem, tag: Integer) {
msgSend(nil, self, "setTag:", tag)
}
@(objc_type=MenuItem, objc_name="representedObject")
MenuItem_representedObject :: #force_inline proc "c" (self: ^MenuItem) -> id {
return msgSend(id, self, "representedObject")
}
@(objc_type=MenuItem, objc_name="setRepresentedObject")
MenuItem_setRepresentedObject :: #force_inline proc "c" (self: ^MenuItem, representedObject: id) {
msgSend(nil, self, "setRepresentedObject:", representedObject)
}
@(objc_type=MenuItem, objc_name="view")
MenuItem_view :: #force_inline proc "c" (self: ^MenuItem) -> ^View {
return msgSend(^View, self, "view")
}
@(objc_type=MenuItem, objc_name="setView")
MenuItem_setView :: #force_inline proc "c" (self: ^MenuItem, view: ^View) {
msgSend(nil, self, "setView:", view)
}
@(objc_type=MenuItem, objc_name="isHighlighted")
MenuItem_isHighlighted :: #force_inline proc "c" (self: ^MenuItem) -> bool {
return msgSend(bool, self, "isHighlighted")
}
@(objc_type=MenuItem, objc_name="isHidden")
MenuItem_isHidden :: #force_inline proc "c" (self: ^MenuItem) -> bool {
return msgSend(bool, self, "isHidden")
}
@(objc_type=MenuItem, objc_name="setHidden")
MenuItem_setHidden :: #force_inline proc "c" (self: ^MenuItem, hidden: bool) {
msgSend(nil, self, "setHidden:", hidden)
}
@(objc_type=MenuItem, objc_name="isHiddenOrHasHiddenAncestor")
MenuItem_isHiddenOrHasHiddenAncestor :: #force_inline proc "c" (self: ^MenuItem) -> bool {
return msgSend(bool, self, "isHiddenOrHasHiddenAncestor")
}
@(objc_type=MenuItem, objc_name="toolTip")
MenuItem_toolTip :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
return msgSend(^String, self, "toolTip")
}
@(objc_type=MenuItem, objc_name="setToolTip")
MenuItem_setToolTip :: #force_inline proc "c" (self: ^MenuItem, toolTip: ^String) {
msgSend(nil, self, "setToolTip:", toolTip)
}
// @(objc_type=MenuItem, objc_name="badge")
// MenuItem_badge :: #force_inline proc "c" (self: ^MenuItem) -> ^MenuItemBadge {
// return msgSend(^MenuItemBadge, self, "badge")
// }
// @(objc_type=MenuItem, objc_name="setBadge")
// MenuItem_setBadge :: #force_inline proc "c" (self: ^MenuItem, badge: ^MenuItemBadge) {
// msgSend(nil, self, "setBadge:", badge)
// }
@(objc_type=MenuItem, objc_name="setMnemonicLocation")
MenuItem_setMnemonicLocation :: #force_inline proc "c" (self: ^MenuItem, location: UInteger) {
msgSend(nil, self, "setMnemonicLocation:", location)
}
@(objc_type=MenuItem, objc_name="mnemonicLocation")
MenuItem_mnemonicLocation :: #force_inline proc "c" (self: ^MenuItem) -> UInteger {
return msgSend(UInteger, self, "mnemonicLocation")
}
@(objc_type=MenuItem, objc_name="mnemonic")
MenuItem_mnemonic :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
return msgSend(^String, self, "mnemonic")
}
@(objc_type=MenuItem, objc_name="setTitleWithMnemonic")
MenuItem_setTitleWithMnemonic :: #force_inline proc "c" (self: ^MenuItem, stringWithAmpersand: ^String) {
msgSend(nil, self, "setTitleWithMnemonic:", stringWithAmpersand)
}
@(objc_type=MenuItem, objc_name="load", objc_is_class_method=true)
MenuItem_load :: #force_inline proc "c" () {
msgSend(nil, MenuItem, "load")
}
@(objc_type=MenuItem, objc_name="initialize", objc_is_class_method=true)
MenuItem_initialize :: #force_inline proc "c" () {
msgSend(nil, MenuItem, "initialize")
}
@(objc_type=MenuItem, objc_name="new", objc_is_class_method=true)
MenuItem_new :: #force_inline proc "c" () -> ^MenuItem {
return msgSend(^MenuItem, MenuItem, "new")
}
@(objc_type=MenuItem, objc_name="allocWithZone", objc_is_class_method=true)
MenuItem_allocWithZone :: #force_inline proc "c" (zone: ^Zone) -> ^MenuItem {
return msgSend(^MenuItem, MenuItem, "allocWithZone:", zone)
}
@(objc_type=MenuItem, objc_name="alloc", objc_is_class_method=true)
MenuItem_alloc :: #force_inline proc "c" () -> ^MenuItem {
return msgSend(^MenuItem, MenuItem, "alloc")
}
@(objc_type=MenuItem, objc_name="copyWithZone", objc_is_class_method=true)
MenuItem_copyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id {
return msgSend(id, MenuItem, "copyWithZone:", zone)
}
@(objc_type=MenuItem, objc_name="mutableCopyWithZone", objc_is_class_method=true)
MenuItem_mutableCopyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id {
return msgSend(id, MenuItem, "mutableCopyWithZone:", zone)
}
@(objc_type=MenuItem, objc_name="instancesRespondToSelector", objc_is_class_method=true)
MenuItem_instancesRespondToSelector :: #force_inline proc "c" (aSelector: SEL) -> bool {
return msgSend(bool, MenuItem, "instancesRespondToSelector:", aSelector)
}
@(objc_type=MenuItem, objc_name="conformsToProtocol", objc_is_class_method=true)
MenuItem_conformsToProtocol :: #force_inline proc "c" (protocol: ^Protocol) -> bool {
return msgSend(bool, MenuItem, "conformsToProtocol:", protocol)
}
@(objc_type=MenuItem, objc_name="instanceMethodForSelector", objc_is_class_method=true)
MenuItem_instanceMethodForSelector :: #force_inline proc "c" (aSelector: SEL) -> IMP {
return msgSend(IMP, MenuItem, "instanceMethodForSelector:", aSelector)
}
// @(objc_type=MenuItem, objc_name="instanceMethodSignatureForSelector", objc_is_class_method=true)
// MenuItem_instanceMethodSignatureForSelector :: #force_inline proc "c" (aSelector: SEL) -> ^MethodSignature {
// return msgSend(^MethodSignature, MenuItem, "instanceMethodSignatureForSelector:", aSelector)
// }
@(objc_type=MenuItem, objc_name="isSubclassOfClass", objc_is_class_method=true)
MenuItem_isSubclassOfClass :: #force_inline proc "c" (aClass: Class) -> bool {
return msgSend(bool, MenuItem, "isSubclassOfClass:", aClass)
}
@(objc_type=MenuItem, objc_name="resolveClassMethod", objc_is_class_method=true)
MenuItem_resolveClassMethod :: #force_inline proc "c" (sel: SEL) -> bool {
return msgSend(bool, MenuItem, "resolveClassMethod:", sel)
}
@(objc_type=MenuItem, objc_name="resolveInstanceMethod", objc_is_class_method=true)
MenuItem_resolveInstanceMethod :: #force_inline proc "c" (sel: SEL) -> bool {
return msgSend(bool, MenuItem, "resolveInstanceMethod:", sel)
}
@(objc_type=MenuItem, objc_name="hash", objc_is_class_method=true)
MenuItem_hash :: #force_inline proc "c" () -> UInteger {
return msgSend(UInteger, MenuItem, "hash")
}
@(objc_type=MenuItem, objc_name="superclass", objc_is_class_method=true)
MenuItem_superclass :: #force_inline proc "c" () -> Class {
return msgSend(Class, MenuItem, "superclass")
}
@(objc_type=MenuItem, objc_name="class", objc_is_class_method=true)
MenuItem_class :: #force_inline proc "c" () -> Class {
return msgSend(Class, MenuItem, "class")
}
@(objc_type=MenuItem, objc_name="description", objc_is_class_method=true)
MenuItem_description :: #force_inline proc "c" () -> ^String {
return msgSend(^String, MenuItem, "description")
}
@(objc_type=MenuItem, objc_name="debugDescription", objc_is_class_method=true)
MenuItem_debugDescription :: #force_inline proc "c" () -> ^String {
return msgSend(^String, MenuItem, "debugDescription")
}
@(objc_type=MenuItem, objc_name="version", objc_is_class_method=true)
MenuItem_version :: #force_inline proc "c" () -> Integer {
return msgSend(Integer, MenuItem, "version")
}
@(objc_type=MenuItem, objc_name="setVersion", objc_is_class_method=true)
MenuItem_setVersion :: #force_inline proc "c" (aVersion: Integer) {
msgSend(nil, MenuItem, "setVersion:", aVersion)
}
@(objc_type=MenuItem, objc_name="poseAsClass", objc_is_class_method=true)
MenuItem_poseAsClass :: #force_inline proc "c" (aClass: Class) {
msgSend(nil, MenuItem, "poseAsClass:", aClass)
}
@(objc_type=MenuItem, objc_name="cancelPreviousPerformRequestsWithTarget_selector_object", objc_is_class_method=true)
MenuItem_cancelPreviousPerformRequestsWithTarget_selector_object :: #force_inline proc "c" (aTarget: id, aSelector: SEL, anArgument: id) {
msgSend(nil, MenuItem, "cancelPreviousPerformRequestsWithTarget:selector:object:", aTarget, aSelector, anArgument)
}
@(objc_type=MenuItem, objc_name="cancelPreviousPerformRequestsWithTarget_", objc_is_class_method=true)
MenuItem_cancelPreviousPerformRequestsWithTarget_ :: #force_inline proc "c" (aTarget: id) {
msgSend(nil, MenuItem, "cancelPreviousPerformRequestsWithTarget:", aTarget)
}
@(objc_type=MenuItem, objc_name="accessInstanceVariablesDirectly", objc_is_class_method=true)
MenuItem_accessInstanceVariablesDirectly :: #force_inline proc "c" () -> bool {
return msgSend(bool, MenuItem, "accessInstanceVariablesDirectly")
}
@(objc_type=MenuItem, objc_name="useStoredAccessor", objc_is_class_method=true)
MenuItem_useStoredAccessor :: #force_inline proc "c" () -> bool {
return msgSend(bool, MenuItem, "useStoredAccessor")
}
@(objc_type=MenuItem, objc_name="keyPathsForValuesAffectingValueForKey", objc_is_class_method=true)
MenuItem_keyPathsForValuesAffectingValueForKey :: #force_inline proc "c" (key: ^String) -> ^Set {
return msgSend(^Set, MenuItem, "keyPathsForValuesAffectingValueForKey:", key)
}
@(objc_type=MenuItem, objc_name="automaticallyNotifiesObserversForKey", objc_is_class_method=true)
MenuItem_automaticallyNotifiesObserversForKey :: #force_inline proc "c" (key: ^String) -> bool {
return msgSend(bool, MenuItem, "automaticallyNotifiesObserversForKey:", key)
}
@(objc_type=MenuItem, objc_name="setKeys", objc_is_class_method=true)
MenuItem_setKeys :: #force_inline proc "c" (keys: ^Array, dependentKey: ^String) {
msgSend(nil, MenuItem, "setKeys:triggerChangeNotificationsForDependentKey:", keys, dependentKey)
}
@(objc_type=MenuItem, objc_name="classFallbacksForKeyedArchiver", objc_is_class_method=true)
MenuItem_classFallbacksForKeyedArchiver :: #force_inline proc "c" () -> ^Array {
return msgSend(^Array, MenuItem, "classFallbacksForKeyedArchiver")
}
@(objc_type=MenuItem, objc_name="classForKeyedUnarchiver", objc_is_class_method=true)
MenuItem_classForKeyedUnarchiver :: #force_inline proc "c" () -> Class {
return msgSend(Class, MenuItem, "classForKeyedUnarchiver")
}
@(objc_type=MenuItem, objc_name="exposeBinding", objc_is_class_method=true)
MenuItem_exposeBinding :: #force_inline proc "c" (binding: ^String) {
msgSend(nil, MenuItem, "exposeBinding:", binding)
}
@(objc_type=MenuItem, objc_name="setDefaultPlaceholder", objc_is_class_method=true)
MenuItem_setDefaultPlaceholder :: #force_inline proc "c" (placeholder: id, marker: id, binding: ^String) {
msgSend(nil, MenuItem, "setDefaultPlaceholder:forMarker:withBinding:", placeholder, marker, binding)
}
@(objc_type=MenuItem, objc_name="defaultPlaceholderForMarker", objc_is_class_method=true)
MenuItem_defaultPlaceholderForMarker :: #force_inline proc "c" (marker: id, binding: ^String) -> id {
return msgSend(id, MenuItem, "defaultPlaceholderForMarker:withBinding:", marker, binding)
}
@(objc_type=MenuItem, objc_name="cancelPreviousPerformRequestsWithTarget")
MenuItem_cancelPreviousPerformRequestsWithTarget :: proc {
MenuItem_cancelPreviousPerformRequestsWithTarget_selector_object,
MenuItem_cancelPreviousPerformRequestsWithTarget_,
}
+136
View File
@@ -0,0 +1,136 @@
package objc_Foundation
import "base:runtime"
import "base:intrinsics"
Subclasser_Proc :: proc(cls: Class, vtable: rawptr)
Object_VTable_Info :: struct {
vtable: rawptr,
size: uint,
impl: Subclasser_Proc,
}
Class_VTable_Info :: struct {
_context: runtime.Context,
super_vtable: rawptr,
protocol_vtable: rawptr,
}
@(require_results)
class_get_metaclass :: #force_inline proc "contextless" (cls: Class) -> Class {
return (^Class)(cls)^
}
@(require_results)
object_get_vtable_info :: proc "contextless" (obj: id) -> ^Class_VTable_Info {
return (^Class_VTable_Info)(object_getIndexedIvars(obj))
}
@(require_results)
make_subclasser :: #force_inline proc(vtable: ^$T, impl: proc(cls: Class, vt: ^T)) -> Object_VTable_Info {
return Object_VTable_Info{
vtable = vtable,
size = size_of(T),
impl = (Subclasser_Proc)(impl),
}
}
@(require_results)
register_subclass :: proc(
class_name: cstring,
superclass: Class,
superclass_overrides: Maybe(Object_VTable_Info) = nil,
protocol: Maybe(Object_VTable_Info) = nil,
_context: Maybe(runtime.Context) = nil,
) -> Class {
assert(superclass != nil)
super_size: uint
proto_size: uint
if superclass_overrides != nil {
// Align to 8-byte boundary
super_size = (superclass_overrides.?.size + 7)/8 * 8
}
if protocol != nil {
// Align to 8-byte boundary
proto_size = (protocol.?.size + 7)/8 * 8
}
cls := objc_lookUpClass(class_name)
if cls != nil {
return cls
}
extra_size := uint(size_of(Class_VTable_Info)) + 8 + super_size + proto_size
cls = objc_allocateClassPair(superclass, class_name, extra_size)
assert(cls != nil)
if s, ok := superclass_overrides.?; ok {
s.impl(cls, s.vtable)
}
if p, ok := protocol.?; ok {
p.impl(cls, p.vtable)
}
objc_registerClassPair(cls)
meta_cls := class_get_metaclass(cls)
meta_size := uint(class_getInstanceSize(meta_cls))
// Offsets are always aligned to 8-byte boundary
info_offset := (meta_size + 7) / 8 * 8
super_vtable_offset := (info_offset + size_of(Class_VTable_Info) + 7) / 8 * 8
ptoto_vtable_offset := super_vtable_offset + super_size
p_info := (^Class_VTable_Info)(([^]u8)(cls)[info_offset:])
p_super_vtable := ([^]u8)(cls)[super_vtable_offset:]
p_proto_vtable := ([^]u8)(cls)[ptoto_vtable_offset:]
intrinsics.mem_zero(p_info, size_of(Class_VTable_Info))
// Assign the context
p_info._context = _context.? or_else context
if s, ok := superclass_overrides.?; ok {
p_info.super_vtable = p_super_vtable
intrinsics.mem_copy(p_super_vtable, s.vtable, super_size)
}
if p, ok := protocol.?; ok {
p_info.protocol_vtable = p_proto_vtable
intrinsics.mem_copy(p_proto_vtable, p.vtable, p.size)
}
return cls
}
@(require_results)
class_get_vtable_info :: proc "contextless" (cls: Class) -> ^Class_VTable_Info {
meta_cls := class_get_metaclass(cls)
meta_size := uint(class_getInstanceSize(meta_cls))
// Align to 8-byte boundary
info_offset := (meta_size+7) / 8 * 8
p_cls := ([^]u8)(cls)[info_offset:]
ctx := (^Class_VTable_Info)(p_cls)
return ctx
}
@(require_results)
alloc_user_object :: proc "contextless" (cls: Class, _context: Maybe(runtime.Context) = nil) -> id {
info := class_get_vtable_info(cls)
obj := class_createInstance(cls, size_of(Class_VTable_Info))
obj_info := (^Class_VTable_Info)(object_getIndexedIvars(obj))
obj_info^ = info^
if _context != nil {
obj_info._context = _context.?
}
return obj
}
+67
View File
@@ -0,0 +1,67 @@
package darwin
import "core:sys/posix"
copyfile_state_t :: distinct rawptr
copyfile_flags :: bit_set[enum {
ACL,
STAT,
XATTR,
DATA,
RECURSIVE = 15,
CHECK,
EXCL,
NOFOLLOW_SRC,
NOFOLLOW_DST,
MOVE,
UNLINK,
PACK,
UNPACK,
CLONE,
CLONE_FORCE,
RUN_IN_PLACE,
DATA_SPARSE,
PRESERVE_DST_TRACKED,
VERBOSE = 30,
}; u32]
COPYFILE_SECURITY :: copyfile_flags{.STAT, .ACL}
COPYFILE_METADATA :: COPYFILE_SECURITY + copyfile_flags{.XATTR}
COPYFILE_ALL :: COPYFILE_METADATA + copyfile_flags{.DATA}
COPYFILE_NOFOLLOW :: copyfile_flags{.NOFOLLOW_SRC, .NOFOLLOW_DST}
copyfile_state_flag :: enum u32 {
SRC_FD = 1,
SRC_FILENAME,
DST_FD,
DST_FILENAME,
QUARANTINE,
STATUS_CB,
STATUS_CTX,
COPIED,
XATTRNAME,
WAS_CLONED,
SRC_BSIZE,
DST_BSIZE,
BSIZE,
FORBID_CROSS_MOUNT,
NOCPROTECT,
PRESERVE_SUID,
RECURSIVE_SRC_FTSENT,
FORBID_DST_EXISTING_SYMLINKS,
}
foreign system {
copyfile :: proc(from, to: cstring, state: copyfile_state_t, flags: copyfile_flags) -> i32 ---
fcopyfile :: proc(from, to: posix.FD, state: copyfile_state_t, flags: copyfile_flags) -> i32 ---
copyfile_state_alloc :: proc() -> copyfile_state_t ---
copyfile_state_free :: proc(state: copyfile_state_t) -> posix.result ---
copyfile_state_get :: proc(state: copyfile_state_t, flag: copyfile_state_flag, dst: rawptr) -> posix.result ---
copyfile_state_set :: proc(state: copyfile_state_t, flag: copyfile_state_flag, src: rawptr) -> posix.result ---
}
+1
View File
@@ -3,6 +3,7 @@ package darwin
import "core:c" import "core:c"
@(export)
foreign import system "system:System.framework" foreign import system "system:System.framework"
Bool :: b8 Bool :: b8
-2
View File
@@ -1,7 +1,5 @@
package darwin package darwin
foreign import system "system:System.framework"
// #define OS_WAIT_ON_ADDR_AVAILABILITY \ // #define OS_WAIT_ON_ADDR_AVAILABILITY \
// __API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4)) // __API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4))
when ODIN_OS == .Darwin { when ODIN_OS == .Darwin {
@@ -19,16 +19,6 @@ X_OK :: c.int((1 << 0)) /* test for execute or search permission */
W_OK :: c.int((1 << 1)) /* test for write permission */ W_OK :: c.int((1 << 1)) /* test for write permission */
R_OK :: c.int((1 << 2)) /* test for read permission */ R_OK :: c.int((1 << 2)) /* test for read permission */
/* copyfile flags */
COPYFILE_ACL :: (1 << 0)
COPYFILE_STAT :: (1 << 1)
COPYFILE_XATTR :: (1 << 2)
COPYFILE_DATA :: (1 << 3)
COPYFILE_SECURITY :: (COPYFILE_STAT | COPYFILE_ACL)
COPYFILE_METADATA :: (COPYFILE_SECURITY | COPYFILE_XATTR)
COPYFILE_ALL :: (COPYFILE_METADATA | COPYFILE_DATA)
/* syslimits.h */ /* syslimits.h */
PATH_MAX :: 1024 /* max bytes in pathname */ PATH_MAX :: 1024 /* max bytes in pathname */
+4 -2
View File
@@ -579,7 +579,7 @@ Inotify_Event_Bits :: enum u32 {
/* /*
Bits for Mem_Protection bitfield Bits for Mem_Protection bitfield
*/ */
Mem_Protection_Bits :: enum{ Mem_Protection_Bits :: enum {
READ = 0, READ = 0,
WRITE = 1, WRITE = 1,
EXEC = 2, EXEC = 2,
@@ -594,11 +594,13 @@ Mem_Protection_Bits :: enum{
/* /*
Bits for Map_Flags Bits for Map_Flags
See `constants.odin` for `MAP_SHARED_VALIDATE` and `MAP_HUGE_16KB`, et al.
*/ */
Map_Flags_Bits :: enum { Map_Flags_Bits :: enum {
SHARED = 0, SHARED = 0,
PRIVATE = 1, PRIVATE = 1,
SHARED_VALIDATE = 2, DROPPABLE = 3,
FIXED = 4, FIXED = 4,
ANONYMOUS = 5, ANONYMOUS = 5,
// platform-dependent section start // platform-dependent section start
+19
View File
@@ -373,3 +373,22 @@ PTRACE_SECCOMP_GET_FILTER :: PTrace_Seccomp_Get_Filter_Type(.SECCOMP_GET_FIL
PTRACE_SECCOMP_GET_METADATA :: PTrace_Seccomp_Get_Metadata_Type(.SECCOMP_GET_METADATA) PTRACE_SECCOMP_GET_METADATA :: PTrace_Seccomp_Get_Metadata_Type(.SECCOMP_GET_METADATA)
PTRACE_GET_SYSCALL_INFO :: PTrace_Get_Syscall_Info_Type(.GET_SYSCALL_INFO) PTRACE_GET_SYSCALL_INFO :: PTrace_Get_Syscall_Info_Type(.GET_SYSCALL_INFO)
PTRACE_GET_RSEQ_CONFIGURATION :: PTrace_Get_RSeq_Configuration_Type(.GET_RSEQ_CONFIGURATION) PTRACE_GET_RSEQ_CONFIGURATION :: PTrace_Get_RSeq_Configuration_Type(.GET_RSEQ_CONFIGURATION)
MAP_SHARED_VALIDATE :: Map_Flags{.SHARED, .PRIVATE}
MAP_HUGE_SHIFT :: 26
MAP_HUGE_MASK :: 63
MAP_HUGE_16KB :: transmute(Map_Flags)(u32(14) << MAP_HUGE_SHIFT)
MAP_HUGE_64KB :: transmute(Map_Flags)(u32(16) << MAP_HUGE_SHIFT)
MAP_HUGE_512KB :: transmute(Map_Flags)(u32(19) << MAP_HUGE_SHIFT)
MAP_HUGE_1MB :: transmute(Map_Flags)(u32(20) << MAP_HUGE_SHIFT)
MAP_HUGE_2MB :: transmute(Map_Flags)(u32(21) << MAP_HUGE_SHIFT)
MAP_HUGE_8MB :: transmute(Map_Flags)(u32(23) << MAP_HUGE_SHIFT)
MAP_HUGE_16MB :: transmute(Map_Flags)(u32(24) << MAP_HUGE_SHIFT)
MAP_HUGE_32MB :: transmute(Map_Flags)(u32(25) << MAP_HUGE_SHIFT)
MAP_HUGE_256MB :: transmute(Map_Flags)(u32(28) << MAP_HUGE_SHIFT)
MAP_HUGE_512MB :: transmute(Map_Flags)(u32(29) << MAP_HUGE_SHIFT)
MAP_HUGE_1GB :: transmute(Map_Flags)(u32(30) << MAP_HUGE_SHIFT)
MAP_HUGE_2GB :: transmute(Map_Flags)(u32(31) << MAP_HUGE_SHIFT)
MAP_HUGE_16GB :: transmute(Map_Flags)(u32(34) << MAP_HUGE_SHIFT)
+3 -1
View File
@@ -288,7 +288,7 @@ Rename_Flags :: bit_set[Rename_Flags_Bits; u32]
/* /*
Directory entry record. Directory entry record.
Recommended iterate these with `dirent_iterator()`, Recommended to iterate these with `dirent_iterate_buf()`,
and obtain the name via `dirent_name()`. and obtain the name via `dirent_name()`.
*/ */
Dirent :: struct { Dirent :: struct {
@@ -368,6 +368,8 @@ Mem_Protection :: bit_set[Mem_Protection_Bits; i32]
/* /*
Flags for mmap. Flags for mmap.
See `constants.odin` for `MAP_SHARED_VALIDATE` and `MAP_HUGE_16KB`, et al.
*/ */
Map_Flags :: bit_set[Map_Flags_Bits; i32] Map_Flags :: bit_set[Map_Flags_Bits; i32]
+50 -18
View File
@@ -54,22 +54,45 @@ WCOREDUMP :: #force_inline proc "contextless" (s: u32) -> bool {
// TODO: sigaddset etc // TODO: sigaddset etc
/// Iterate the results of getdents /*
/// Only iterates as much data as loaded in the buffer Iterate the results of `getdents()`.
/// In case you need to iterate *all* files in a directory
/// consider using dirent_get_iterate This procedure extracts a directory entry from `buf` at the offset `offs`.
/// `offs` will be modified to store an offset to the possible next directory entry
/// Example of using dirent_iterate_buf in `buf`. The procedure only iterates as much data as loaded in the buffer and
/// // Get dirents into a buffer does not automatically make a request for the buffer to be refilled.
/// buf: [128]u8
/// sys.getdents(dirfd, buf[:]) Inputs:
/// // Print the names of the files - buf: A byte buffer with data from `getdents()`
/// for dir in sys.dirent_iterate_buf(buf[:], &offs) { - offs: An offset to the next possible directory entry in `buf`
/// name := sys.dirent_name(dir)
/// fmt.println(name) Returns:
/// } - A pointer to a directory entry in `buf`, or `nil`
/// This function doesn't automatically make a request - A bool value denoting if a valid directory entry is returned
/// for the buffer to be refilled
Example:
import "core:fmt"
import "core:sys/linux"
print_names :: proc(dirfd: linux.Fd) {
// Get dirents into a buffer.
buf: [128]u8
// Loop until there are no more entries.
for {
written, err := linux.getdents(dirfd, buf[:])
if err != .NONE || written == 0 {
break
}
// Print the names of the files.
offset : int
for dir in linux.dirent_iterate_buf(buf[:written], &offset) {
name := linux.dirent_name(dir)
fmt.println(name)
}
}
}
*/
dirent_iterate_buf :: proc "contextless" (buf: []u8, offs: ^int) -> (d: ^Dirent, cont: bool) { dirent_iterate_buf :: proc "contextless" (buf: []u8, offs: ^int) -> (d: ^Dirent, cont: bool) {
// Stopped iterating when there's no space left // Stopped iterating when there's no space left
if offs^ >= len(buf) { if offs^ >= len(buf) {
@@ -82,8 +105,17 @@ dirent_iterate_buf :: proc "contextless" (buf: []u8, offs: ^int) -> (d: ^Dirent,
return dirent, true return dirent, true
} }
/// Obtain the name of dirent as a string /*
/// The lifetime of the string is bound to the lifetime of the provided dirent structure Obtain the name of dirent as a string.
The lifetime of the returned string is bound to the lifetime of the provided dirent structure.
Inputs:
- dirent: A directory entry
Returns:
- A name of the entry
*/
dirent_name :: proc "contextless" (dirent: ^Dirent) -> string #no_bounds_check { dirent_name :: proc "contextless" (dirent: ^Dirent) -> string #no_bounds_check {
str := ([^]u8)(&dirent.name) str := ([^]u8)(&dirent.name)
// Dirents are aligned to 8 bytes, so there is guaranteed to be a null // Dirents are aligned to 8 bytes, so there is guaranteed to be a null
+172
View File
@@ -0,0 +1,172 @@
#+build windows
package sys_windows
// Win32 scan codes for QWERTY layout
// https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#scan-codes
KB_SYS_POWERDOWN :: 0xE05E
KB_SYS_SLEEP :: 0xE05F
KB_SYS_WAKEUP :: 0xE063
KB_ERR_ROLLOVER :: 0x00FF
KB_A :: 0x001E
KB_B :: 0x0030
KB_C :: 0x002E
KB_D :: 0x0020
KB_E :: 0x0012
KB_F :: 0x0021
KB_G :: 0x0022
KB_H :: 0x0023
KB_I :: 0x0017
KB_J :: 0x0024
KB_K :: 0x0025
KB_L :: 0x0026
KB_M :: 0x0032
KB_N :: 0x0031
KB_O :: 0x0018
KB_P :: 0x0019
KB_Q :: 0x0010
KB_R :: 0x0013
KB_S :: 0x001F
KB_T :: 0x0014
KB_U :: 0x0016
KB_V :: 0x002F
KB_W :: 0x0011
KB_X :: 0x002D
KB_Y :: 0x0015
KB_Z :: 0x002C
KB_1_BANG :: 0x0002
KB_2_AT :: 0x0003
KB_3_HASH :: 0x0004
KB_4_DOLLAR :: 0x0005
KB_5_PERCENT :: 0x0006
KB_6_CARET :: 0x0007
KB_7_AMPERSAND :: 0x0008
KB_8_STAR :: 0x0009
KB_9_LEFTBRACKET :: 0x000A
KB_0_RIGHTBRACKET :: 0x000B
KB_RETURN_ENTER :: 0x001C
KB_ESCAPE :: 0x0001
KB_DELETE :: 0x000E
KB_TAB :: 0x000F
KB_SPACEBAR :: 0x0039
KB_DASH_UNDERSCORE :: 0x000C
KB_EQUALS_PLUS :: 0x000D
KB_LEFTBRACE :: 0x001A
KB_RIGHTBRACE :: 0x001B
KB_PIPE_SLASH :: 0x002B
KB_NONUS :: 0x002B
KB_SEMICOLON_COLON :: 0x0027
KB_APOSTR_DOUBLEQUOT :: 0x0028
KB_GRAVEACC_TILDE :: 0x0029
KB_COMMA :: 0x0033
KB_PERIOD :: 0x0034
KB_QUESTIONMARK :: 0x0035
KB_CAPSLOCK :: 0x003A
KB_F1 :: 0x003B
KB_F2 :: 0x003C
KB_F3 :: 0x003D
KB_F4 :: 0x003E
KB_F5 :: 0x003F
KB_F6 :: 0x0040
KB_F7 :: 0x0041
KB_F8 :: 0x0042
KB_F9 :: 0x0043
KB_F10 :: 0x0044
KB_F11 :: 0x0057
KB_F12 :: 0x0058
KB_PRINTSCREEN :: 0xE037
KB_SCROLLLOCK :: 0x0046
KB_PAUSE :: 0xE11D45
KB_INSERT :: 0xE052
KB_HOME :: 0xE047
KB_PAGEUP :: 0xE049
KB_DELETEFORWARD :: 0xE053
KB_END :: 0xE04F
KB_PAGEDOWN :: 0xE051
KB_RIGHTARROW :: 0xE04D
KB_LEFTARROW :: 0xE04B
KB_DOWNARROW :: 0xE050
KB_UPARROW :: 0xE048
KP_NUMLOCK_CLEAR :: 0x0045
KP_FORWARDSLASH :: 0xE035
KP_STAR :: 0x0037
KP_DASH :: 0x004A
KP_PLUS :: 0x004E
KP_ENTER :: 0xE01C
KP_1_END :: 0x004F
KP_2_DOWNARROW :: 0x0050
KP_3_PAGEDN :: 0x0051
KP_4_LEFTARROW :: 0x004B
KP_5 :: 0x004C
KP_6_RIGHTARROW :: 0x004D
KP_7_HOME :: 0x0047
KP_8_UPARROW :: 0x0048
KP_9_PAGEUP :: 0x0049
KP_0_INSERT :: 0x0052
KP_PERIOD :: 0x0053
KB_NONUS_SLASHBAR :: 0x0056
KB_APPLICATION :: 0xE05D
KB_POWER :: 0xE05E
KB_EQUALS :: 0x0059
KB_F13 :: 0x0064
KB_F14 :: 0x0065
KB_F15 :: 0x0066
KB_F16 :: 0x0067
KB_F17 :: 0x0068
KB_F18 :: 0x0069
KB_F19 :: 0x006A
KB_F20 :: 0x006B
KB_F21 :: 0x006C
KB_F22 :: 0x006D
KB_F23 :: 0x006E
KB_F24 :: 0x0076
KP_COMMA :: 0x007E
KB_INTERNATIONAL1 :: 0x0073
KB_INTERNATIONAL2 :: 0x0070
KB_INTERNATIONAL3 :: 0x007D
KB_INTERNATIONAL4 :: 0x0079
KB_INTERNATIONAL5 :: 0x007B
KB_INTERNATIONAL6 :: 0x005C
KB_LANG1 :: 0x0072
KB_LANG2 :: 0x0071
KB_LANG3 :: 0x0078
KB_LANG4 :: 0x0077
KB_LANG5 :: 0x0076
KB_LEFTCONTROL :: 0x001D
KB_LEFTSHIFT :: 0x002A
KB_LEFTALT :: 0x0038
KB_LEFTGUI :: 0xE05B
KB_RIGHTCONTROL :: 0xE01D
KB_RIGHTSHIFT :: 0x0036
KB_RIGHTALT :: 0xE038
KB_RIGHTGUI :: 0xE05C
FN_SCANNEXTTRACK :: 0xE019
FN_SCANPREVTRACK :: 0xE010
FN_STOP :: 0xE024
FN_PLAY_PAUSE :: 0xE022
FN_MUTE :: 0xE020
FN_VOLUMEINC :: 0xE030
FN_VOLUMEDEC :: 0xE02E
FN_AL_CONSUMERCTRLCONFIG :: 0xE06D
FN_AL_EMAILREADER :: 0xE06C
FN_AL_CALCULATOR :: 0xE021
FN_AL_LOCALMACHINEBROWSER :: 0xE06B
FN_AC_SEARCH :: 0xE065
FN_AC_HOME :: 0xE032
FN_AC_BACK :: 0xE06A
FN_AC_FORWARD :: 0xE069
FN_AC_STOP :: 0xE068
FN_AC_REFRESH :: 0xE067
FN_AC_BOOKMARKS :: 0xE066
+4
View File
@@ -0,0 +1,4 @@
/*
This package is for interacting with the command line interface of the system.
*/
package terminal
+87
View File
@@ -0,0 +1,87 @@
#+private
package terminal
import "core:os"
import "core:strings"
// Reference documentation:
//
// - [[ https://no-color.org/ ]]
// - [[ https://github.com/termstandard/colors ]]
// - [[ https://invisible-island.net/ncurses/terminfo.src.html ]]
get_no_color :: proc() -> bool {
if no_color, ok := os.lookup_env("NO_COLOR"); ok {
defer delete(no_color)
return no_color != ""
}
return false
}
get_environment_color :: proc() -> Color_Depth {
// `COLORTERM` is non-standard but widespread and unambiguous.
if colorterm, ok := os.lookup_env("COLORTERM"); ok {
defer delete(colorterm)
// These are the only values that are typically advertised that have
// anything to do with color depth.
if colorterm == "truecolor" || colorterm == "24bit" {
return .True_Color
}
}
if term, ok := os.lookup_env("TERM"); ok {
defer delete(term)
if strings.contains(term, "-truecolor") {
return .True_Color
}
if strings.contains(term, "-256color") {
return .Eight_Bit
}
if strings.contains(term, "-16color") {
return .Four_Bit
}
// The `terminfo` database, which is stored in binary on *nix
// platforms, has an undocumented format that is not guaranteed to be
// portable, so beyond this point, we can only make safe assumptions.
//
// This section should only be necessary for terminals that do not
// define any of the previous environment values.
//
// Only a small sampling of some common values are checked here.
switch term {
case "ansi": fallthrough
case "konsole": fallthrough
case "putty": fallthrough
case "rxvt": fallthrough
case "rxvt-color": fallthrough
case "screen": fallthrough
case "st": fallthrough
case "tmux": fallthrough
case "vte": fallthrough
case "xterm": fallthrough
case "xterm-color":
return .Three_Bit
}
}
return .None
}
@(init)
init_terminal :: proc() {
_init_terminal()
// We respect `NO_COLOR` specifically as a color-disabler but not as a
// blanket ban on any terminal manipulation codes, hence why this comes
// after `_init_terminal` which will allow Windows to enable Virtual
// Terminal Processing for non-color control sequences.
if !get_no_color() {
color_enabled = color_depth > .None
}
}
@(fini)
fini_terminal :: proc() {
_fini_terminal()
}
+36
View File
@@ -0,0 +1,36 @@
package terminal
import "core:os"
/*
This describes the range of colors that a terminal is capable of supporting.
*/
Color_Depth :: enum {
None, // No color support
Three_Bit, // 8 colors
Four_Bit, // 16 colors
Eight_Bit, // 256 colors
True_Color, // 24-bit true color
}
/*
Returns true if the file `handle` is attached to a terminal.
This is normally true for `os.stdout` and `os.stderr` unless they are
redirected to a file.
*/
@(require_results)
is_terminal :: proc(handle: os.Handle) -> bool {
return _is_terminal(handle)
}
/*
This is true if the terminal is accepting any form of colored text output.
*/
color_enabled: bool
/*
This value reports the color depth support as reported by the terminal at the
start of the program.
*/
color_depth: Color_Depth
+16
View File
@@ -0,0 +1,16 @@
#+private
#+build linux, darwin, netbsd, openbsd, freebsd, haiku
package terminal
import "core:os"
import "core:sys/posix"
_is_terminal :: proc(handle: os.Handle) -> bool {
return bool(posix.isatty(posix.FD(handle)))
}
_init_terminal :: proc() {
color_depth = get_environment_color()
}
_fini_terminal :: proc() { }
+60
View File
@@ -0,0 +1,60 @@
#+private
package terminal
import "core:os"
import "core:sys/windows"
_is_terminal :: proc(handle: os.Handle) -> bool {
is_tty := windows.GetFileType(windows.HANDLE(handle)) == windows.FILE_TYPE_CHAR
return is_tty
}
old_modes: [2]struct{
handle: windows.DWORD,
mode: windows.DWORD,
} = {
{windows.STD_OUTPUT_HANDLE, 0},
{windows.STD_ERROR_HANDLE, 0},
}
@(init)
_init_terminal :: proc() {
vtp_enabled: bool
for &v in old_modes {
handle := windows.GetStdHandle(v.handle)
if handle == windows.INVALID_HANDLE || handle == nil {
return
}
if windows.GetConsoleMode(handle, &v.mode) {
windows.SetConsoleMode(handle, v.mode | windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
new_mode: windows.DWORD
windows.GetConsoleMode(handle, &new_mode)
if new_mode & (windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0 {
vtp_enabled = true
}
}
}
if vtp_enabled {
// This color depth is available on Windows 10 since build 10586.
color_depth = .Four_Bit
} else {
// The user may be on a non-default terminal emulator.
color_depth = get_environment_color()
}
}
@(fini)
_fini_terminal :: proc() {
for v in old_modes {
handle := windows.GetStdHandle(v.handle)
if handle == windows.INVALID_HANDLE || handle == nil {
return
}
windows.SetConsoleMode(handle, v.mode)
}
}
+1 -1
View File
@@ -10,12 +10,12 @@ package testing
*/ */
import "base:runtime" import "base:runtime"
import "core:encoding/ansi"
import "core:fmt" import "core:fmt"
import "core:io" import "core:io"
import "core:mem" import "core:mem"
import "core:path/filepath" import "core:path/filepath"
import "core:strings" import "core:strings"
import "core:terminal/ansi"
// Definitions of colors for use in the test runner. // Definitions of colors for use in the test runner.
SGR_RESET :: ansi.CSI + ansi.RESET + ansi.SGR SGR_RESET :: ansi.CSI + ansi.RESET + ansi.SGR
+93 -65
View File
@@ -13,7 +13,6 @@ package testing
import "base:intrinsics" import "base:intrinsics"
import "base:runtime" import "base:runtime"
import "core:bytes" import "core:bytes"
import "core:encoding/ansi"
@require import "core:encoding/base64" @require import "core:encoding/base64"
@require import "core:encoding/json" @require import "core:encoding/json"
import "core:fmt" import "core:fmt"
@@ -25,6 +24,8 @@ import "core:os"
import "core:slice" import "core:slice"
@require import "core:strings" @require import "core:strings"
import "core:sync/chan" import "core:sync/chan"
import "core:terminal"
import "core:terminal/ansi"
import "core:thread" import "core:thread"
import "core:time" import "core:time"
@@ -44,6 +45,7 @@ PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_S
// The format is: `package.test_name,test_name_only,...` // The format is: `package.test_name,test_name_only,...`
TEST_NAMES : string : #config(ODIN_TEST_NAMES, "") TEST_NAMES : string : #config(ODIN_TEST_NAMES, "")
// Show the fancy animated progress report. // Show the fancy animated progress report.
// This requires terminal color support, as well as STDOUT to not be redirected to a file.
FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true) FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true)
// Copy failed tests to the clipboard when done. // Copy failed tests to the clipboard when done.
USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false) USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false)
@@ -70,6 +72,9 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level {
} }
} }
@(private) global_log_colors_disabled: bool
@(private) global_ansi_disabled: bool
JSON :: struct { JSON :: struct {
total: int, total: int,
success: int, success: int,
@@ -129,11 +134,16 @@ run_test_task :: proc(task: thread.Task) {
context.assertion_failure_proc = test_assertion_failure_proc context.assertion_failure_proc = test_assertion_failure_proc
logger_options := Default_Test_Logger_Opts
if global_log_colors_disabled {
logger_options -= {.Terminal_Color}
}
context.logger = { context.logger = {
procedure = test_logger_proc, procedure = test_logger_proc,
data = &data.t, data = &data.t,
lowest_level = get_log_level(), lowest_level = get_log_level(),
options = Default_Test_Logger_Opts, options = logger_options,
} }
random_generator_state: runtime.Default_Random_State random_generator_state: runtime.Default_Random_State
@@ -204,13 +214,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
} }
} }
when ODIN_OS == .Windows {
console_ansi_init()
}
stdout := io.to_writer(os.stream_from_handle(os.stdout)) stdout := io.to_writer(os.stream_from_handle(os.stdout))
stderr := io.to_writer(os.stream_from_handle(os.stderr)) stderr := io.to_writer(os.stream_from_handle(os.stderr))
// The animations are only ever shown through STDOUT;
// STDERR is used exclusively for logging regardless of error level.
global_log_colors_disabled = !terminal.color_enabled || !terminal.is_terminal(os.stderr)
global_ansi_disabled = !terminal.is_terminal(os.stdout)
should_show_animations := FANCY_OUTPUT && terminal.color_enabled && !global_ansi_disabled
// -- Prepare test data. // -- Prepare test data.
alloc_error: mem.Allocator_Error alloc_error: mem.Allocator_Error
@@ -268,12 +281,12 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
total_done_count := 0 total_done_count := 0
total_test_count := len(internal_tests) total_test_count := len(internal_tests)
when !FANCY_OUTPUT {
// This is strictly for updating the window title when the progress // This is strictly for updating the window title when the progress
// report is disabled. We're otherwise able to depend on the call to // report is disabled. We're otherwise able to depend on the call to
// `needs_to_redraw`. // `needs_to_redraw`.
last_done_count := -1 last_done_count := -1
}
if total_test_count == 0 { if total_test_count == 0 {
// Exit early. // Exit early.
@@ -342,31 +355,31 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
fmt.assertf(alloc_error == nil, "Error allocating memory for test report: %v", alloc_error) fmt.assertf(alloc_error == nil, "Error allocating memory for test report: %v", alloc_error)
defer destroy_report(&report) defer destroy_report(&report)
when FANCY_OUTPUT {
// We cannot make use of the ANSI save/restore cursor codes, because they
// work by absolute screen coordinates. This will cause unnecessary
// scrollback if we print at the bottom of someone's terminal.
ansi_redraw_string := fmt.aprintf(
// ANSI for "go up N lines then erase the screen from the cursor forward."
ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED +
// We'll combine this with the window title format string, since it
// can be printed at the same time.
"%s",
// 1 extra line for the status bar.
1 + len(report.packages), OSC_WINDOW_TITLE)
assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.")
defer delete(ansi_redraw_string)
thread_count_status_string: string = --- // We cannot make use of the ANSI save/restore cursor codes, because they
{ // work by absolute screen coordinates. This will cause unnecessary
PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH // scrollback if we print at the bottom of someone's terminal.
ansi_redraw_string := fmt.aprintf(
// ANSI for "go up N lines then erase the screen from the cursor forward."
ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED +
// We'll combine this with the window title format string, since it
// can be printed at the same time.
"%s",
// 1 extra line for the status bar.
1 + len(report.packages), OSC_WINDOW_TITLE)
assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.")
defer delete(ansi_redraw_string)
unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s") thread_count_status_string: string = ---
thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING) {
assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.") PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH
}
defer delete(thread_count_status_string) unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s")
thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING)
assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.")
} }
defer delete(thread_count_status_string)
task_data_slots: []Task_Data = --- task_data_slots: []Task_Data = ---
task_data_slots, alloc_error = make([]Task_Data, thread_count) task_data_slots, alloc_error = make([]Task_Data, thread_count)
@@ -442,11 +455,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
// digging through the source to divine everywhere it is used for that. // digging through the source to divine everywhere it is used for that.
shared_log_allocator := context.allocator shared_log_allocator := context.allocator
logger_options := Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure}
if global_log_colors_disabled {
logger_options -= {.Terminal_Color}
}
context.logger = { context.logger = {
procedure = runner_logger_proc, procedure = runner_logger_proc,
data = &log_messages, data = &log_messages,
lowest_level = get_log_level(), lowest_level = get_log_level(),
options = Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure}, options = logger_options,
} }
run_index: int run_index: int
@@ -481,11 +499,13 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
setup_signal_handler() setup_signal_handler()
fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE) if !global_ansi_disabled {
fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE)
}
when FANCY_OUTPUT { signals_were_raised := false
signals_were_raised := false
if should_show_animations {
redraw_report(stdout, report) redraw_report(stdout, report)
draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count) draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count)
} }
@@ -703,22 +723,22 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
break main_loop break main_loop
} }
when FANCY_OUTPUT {
// Because the bounds checking procs send directly to STDERR with // Because the bounds checking procs send directly to STDERR with
// no way to redirect or handle them, we need to at least try to // no way to redirect or handle them, we need to at least try to
// let the user see those messages when using the animated progress // let the user see those messages when using the animated progress
// report. This flag may be set by the block of code below if a // report. This flag may be set by the block of code below if a
// signal is raised. // signal is raised.
// //
// It'll be purely by luck if the output is interleaved properly, // It'll be purely by luck if the output is interleaved properly,
// given the nature of non-thread-safe printing. // given the nature of non-thread-safe printing.
// //
// At worst, if Odin did not print any error for this signal, we'll // At worst, if Odin did not print any error for this signal, we'll
// just re-display the progress report. The fatal log error message // just re-display the progress report. The fatal log error message
// should be enough to clue the user in that something dire has // should be enough to clue the user in that something dire has
// occurred. // occurred.
bypass_progress_overwrite := false bypass_progress_overwrite := false
}
if test_index, reason, ok := should_stop_test(); ok { if test_index, reason, ok := should_stop_test(); ok {
#no_bounds_check report.all_test_states[test_index] = .Failed #no_bounds_check report.all_test_states[test_index] = .Failed
@@ -752,7 +772,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason) log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason)
} }
when FANCY_OUTPUT { if should_show_animations {
bypass_progress_overwrite = true bypass_progress_overwrite = true
signals_were_raised = true signals_were_raised = true
} }
@@ -766,7 +786,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
// -- Redraw. // -- Redraw.
when FANCY_OUTPUT { if should_show_animations {
if len(log_messages) == 0 && !needs_to_redraw(report) { if len(log_messages) == 0 && !needs_to_redraw(report) {
continue main_loop continue main_loop
} }
@@ -776,7 +796,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
} }
} else { } else {
if total_done_count != last_done_count { if total_done_count != last_done_count {
fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count) if !global_ansi_disabled {
fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count)
}
last_done_count = total_done_count last_done_count = total_done_count
} }
@@ -801,7 +823,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
clear(&log_messages) clear(&log_messages)
bytes.buffer_reset(&batch_buffer) bytes.buffer_reset(&batch_buffer)
when FANCY_OUTPUT { if should_show_animations {
redraw_report(batch_writer, report) redraw_report(batch_writer, report)
draw_status_bar(batch_writer, thread_count_status_string, total_done_count, total_test_count) draw_status_bar(batch_writer, thread_count_status_string, total_done_count, total_test_count)
fmt.wprint(stdout, bytes.buffer_to_string(&batch_buffer)) fmt.wprint(stdout, bytes.buffer_to_string(&batch_buffer))
@@ -822,7 +844,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
finished_in := time.since(start_time) finished_in := time.since(start_time)
when !FANCY_OUTPUT { if !should_show_animations || !terminal.is_terminal(os.stderr) {
// One line to space out the results, since we don't have the status // One line to space out the results, since we don't have the status
// bar in plain mode. // bar in plain mode.
fmt.wprintln(batch_writer) fmt.wprintln(batch_writer)
@@ -836,24 +858,28 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
if total_done_count != total_test_count { if total_done_count != total_test_count {
not_run_count := total_test_count - total_done_count not_run_count := total_test_count - total_done_count
message := " %i %s left undone." if global_log_colors_disabled else " " + SGR_READY + "%i" + SGR_RESET + " %s left undone."
fmt.wprintf(batch_writer, fmt.wprintf(batch_writer,
" " + SGR_READY + "%i" + SGR_RESET + " %s left undone.", message,
not_run_count, not_run_count,
"test was" if not_run_count == 1 else "tests were") "test was" if not_run_count == 1 else "tests were")
} }
if total_success_count == total_test_count { if total_success_count == total_test_count {
message := " %s successful." if global_log_colors_disabled else " %s " + SGR_SUCCESS + "successful." + SGR_RESET
fmt.wprintfln(batch_writer, fmt.wprintfln(batch_writer,
" %s " + SGR_SUCCESS + "successful." + SGR_RESET, message,
"The test was" if total_test_count == 1 else "All tests were") "The test was" if total_test_count == 1 else "All tests were")
} else if total_failure_count > 0 { } else if total_failure_count > 0 {
if total_failure_count == total_test_count { if total_failure_count == total_test_count {
message := " %s failed." if global_log_colors_disabled else " %s " + SGR_FAILED + "failed." + SGR_RESET
fmt.wprintfln(batch_writer, fmt.wprintfln(batch_writer,
" %s " + SGR_FAILED + "failed." + SGR_RESET, message,
"The test" if total_test_count == 1 else "All tests") "The test" if total_test_count == 1 else "All tests")
} else { } else {
message := " %i test%s failed." if global_log_colors_disabled else " " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed."
fmt.wprintfln(batch_writer, fmt.wprintfln(batch_writer,
" " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed.", message,
total_failure_count, total_failure_count,
"" if total_failure_count == 1 else "s") "" if total_failure_count == 1 else "s")
} }
@@ -907,9 +933,11 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
} }
} }
fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW) if !global_ansi_disabled {
fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW)
}
when FANCY_OUTPUT { if should_show_animations {
if signals_were_raised { if signals_were_raised {
fmt.wprintln(batch_writer, ` fmt.wprintln(batch_writer, `
Signals were raised during this test run. Log messages are likely to have collided with each other. Signals were raised during this test run. Log messages are likely to have collided with each other.
-22
View File
@@ -1,22 +0,0 @@
#+private
package testing
import win32 "core:sys/windows"
console_ansi_init :: proc() {
stdout := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
if stdout != win32.INVALID_HANDLE && stdout != nil {
old_console_mode: u32
if win32.GetConsoleMode(stdout, &old_console_mode) {
win32.SetConsoleMode(stdout, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
}
}
stderr := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
if stderr != win32.INVALID_HANDLE && stderr != nil {
old_console_mode: u32
if win32.GetConsoleMode(stderr, &old_console_mode) {
win32.SetConsoleMode(stderr, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
}
}
}
+7 -5
View File
@@ -12,9 +12,9 @@ package testing
import "base:intrinsics" import "base:intrinsics"
import "core:c/libc" import "core:c/libc"
import "core:encoding/ansi"
import "core:sync"
import "core:os" import "core:os"
import "core:sync"
import "core:terminal/ansi"
@(private="file") stop_runner_flag: libc.sig_atomic_t @(private="file") stop_runner_flag: libc.sig_atomic_t
@@ -63,9 +63,11 @@ stop_test_callback :: proc "c" (sig: libc.int) {
// NOTE(Feoramund): Using these write calls in a signal handler is // NOTE(Feoramund): Using these write calls in a signal handler is
// undefined behavior in C99 but possibly tolerated in POSIX 2008. // undefined behavior in C99 but possibly tolerated in POSIX 2008.
// Either way, we may as well try to salvage what we can. // Either way, we may as well try to salvage what we can.
show_cursor := ansi.CSI + ansi.DECTCEM_SHOW if !global_ansi_disabled {
libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout) show_cursor := ansi.CSI + ansi.DECTCEM_SHOW
libc.fflush(libc.stdout) libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout)
libc.fflush(libc.stdout)
}
// This is an attempt at being compliant by avoiding printf. // This is an attempt at being compliant by avoiding printf.
sigbuf: [8]byte sigbuf: [8]byte
+4
View File
@@ -3,4 +3,8 @@ package all
import linux "core:sys/linux" import linux "core:sys/linux"
import xlib "vendor:x11/xlib"
_ :: linux _ :: linux
_ :: xlib
+5 -2
View File
@@ -58,7 +58,6 @@ import trace "core:debug/trace"
import dynlib "core:dynlib" import dynlib "core:dynlib"
import net "core:net" import net "core:net"
import ansi "core:encoding/ansi"
import base32 "core:encoding/base32" import base32 "core:encoding/base32"
import base64 "core:encoding/base64" import base64 "core:encoding/base64"
import cbor "core:encoding/cbor" import cbor "core:encoding/cbor"
@@ -129,6 +128,9 @@ import strings "core:strings"
import sync "core:sync" import sync "core:sync"
import testing "core:testing" import testing "core:testing"
import terminal "core:terminal"
import ansi "core:terminal/ansi"
import edit "core:text/edit" import edit "core:text/edit"
import i18n "core:text/i18n" import i18n "core:text/i18n"
import match "core:text/match" import match "core:text/match"
@@ -201,7 +203,6 @@ _ :: pe
_ :: trace _ :: trace
_ :: dynlib _ :: dynlib
_ :: net _ :: net
_ :: ansi
_ :: base32 _ :: base32
_ :: base64 _ :: base64
_ :: csv _ :: csv
@@ -257,6 +258,8 @@ _ :: strconv
_ :: strings _ :: strings
_ :: sync _ :: sync
_ :: testing _ :: testing
_ :: terminal
_ :: ansi
_ :: scanner _ :: scanner
_ :: i18n _ :: i18n
_ :: match _ :: match
-4
View File
@@ -28,8 +28,6 @@ import nvg "vendor:nanovg"
import nvg_gl "vendor:nanovg/gl" import nvg_gl "vendor:nanovg/gl"
import fontstash "vendor:fontstash" import fontstash "vendor:fontstash"
import xlib "vendor:x11/xlib"
_ :: cgltf _ :: cgltf
// _ :: commonmark // _ :: commonmark
_ :: ENet _ :: ENet
@@ -57,8 +55,6 @@ _ :: nvg
_ :: nvg_gl _ :: nvg_gl
_ :: fontstash _ :: fontstash
_ :: xlib
// NOTE: needed for doc generator // NOTE: needed for doc generator
+2
View File
@@ -3,8 +3,10 @@ package all
import wgpu "vendor:wgpu" import wgpu "vendor:wgpu"
import b2 "vendor:box2d" import b2 "vendor:box2d"
import game_input "vendor:windows/GameInput" import game_input "vendor:windows/GameInput"
import XAudio2 "vendor:windows/XAudio2"
_ :: wgpu _ :: wgpu
_ :: b2 _ :: b2
_ :: game_input _ :: game_input
_ :: XAudio2
+1 -1
View File
@@ -9,5 +9,5 @@
int main(int arg_count, char const **arg_ptr) { int main(int arg_count, char const **arg_ptr) {
time_t t = time(NULL); time_t t = time(NULL);
struct tm* now = localtime(&t); struct tm* now = localtime(&t);
printf("%04d%02d%02d", now->tm_year + 1900, now->tm_mon + 1, now->tm_mday); printf("%04d-%02d-%02d", now->tm_year + 1900, now->tm_mon + 1, now->tm_mday);
} }
+7 -1
View File
@@ -667,8 +667,14 @@ gb_internal void print_bug_report_help() {
gb_printf("-nightly"); gb_printf("-nightly");
#endif #endif
String version = {};
#ifdef GIT_SHA #ifdef GIT_SHA
gb_printf(":%s", GIT_SHA); version.text = cast(u8 *)GIT_SHA;
version.len = gb_strlen(GIT_SHA);
if (version != "") {
gb_printf(":%.*s", LIT(version));
}
#endif #endif
gb_printf("\n"); gb_printf("\n");
+26 -8
View File
@@ -459,6 +459,7 @@ struct BuildContext {
bool ignore_unknown_attributes; bool ignore_unknown_attributes;
bool no_bounds_check; bool no_bounds_check;
bool no_type_assert; bool no_type_assert;
bool dynamic_literals; // Opt-in to `#+feature dynamic-literals` project-wide.
bool no_output_files; bool no_output_files;
bool no_crt; bool no_crt;
bool no_rpath; bool no_rpath;
@@ -1915,12 +1916,6 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
} }
// TODO: Static map calls are bugged on `amd64sysv` abi.
if (bc->metrics.os != TargetOs_windows && bc->metrics.arch == TargetArch_amd64) {
// ENFORCE DYNAMIC MAP CALLS
bc->dynamic_map_calls = true;
}
bc->ODIN_VALGRIND_SUPPORT = false; bc->ODIN_VALGRIND_SUPPORT = false;
if (build_context.metrics.os != TargetOs_windows) { if (build_context.metrics.os != TargetOs_windows) {
switch (bc->metrics.arch) { switch (bc->metrics.arch) {
@@ -2214,10 +2209,33 @@ gb_internal bool init_build_paths(String init_filename) {
while (output_name.len > 0 && (output_name[output_name.len-1] == '/' || output_name[output_name.len-1] == '\\')) { while (output_name.len > 0 && (output_name[output_name.len-1] == '/' || output_name[output_name.len-1] == '\\')) {
output_name.len -= 1; output_name.len -= 1;
} }
// Only trim the extension if it's an Odin source file.
// This lets people build folders with extensions or files beginning with dots.
if (path_extension(output_name) == ".odin" && !path_is_directory(output_name)) {
output_name = remove_extension_from_path(output_name);
}
output_name = remove_directory_from_path(output_name); output_name = remove_directory_from_path(output_name);
output_name = remove_extension_from_path(output_name);
output_name = copy_string(ha, string_trim_whitespace(output_name)); output_name = copy_string(ha, string_trim_whitespace(output_name));
output_path = path_from_string(ha, output_name); // This is `path_from_string` without the extension trimming.
Path res = {};
if (output_name.len > 0) {
String fullpath = path_to_full_path(ha, output_name);
defer (gb_free(ha, fullpath.text));
res.basename = directory_from_path(fullpath);
res.basename = copy_string(ha, res.basename);
if (path_is_directory(fullpath)) {
if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') {
res.basename.len--;
}
} else {
isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len;
res.name = substring(fullpath, name_start, fullpath.len);
res.name = copy_string(ha, res.name);
}
}
output_path = res;
// Note(Dragos): This is a fix for empty filenames // Note(Dragos): This is a fix for empty filenames
// Turn the trailing folder into the file name // Turn the trailing folder into the file name
+325 -2
View File
@@ -223,9 +223,9 @@ gb_internal void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_t
data.kind = kind; data.kind = kind;
data.proc_type = alloc_type_proc(scope, params, param_types.count, results, results->Tuple.variables.count, false, ProcCC_CDecl); data.proc_type = alloc_type_proc(scope, params, param_types.count, results, results->Tuple.variables.count, false, ProcCC_CDecl);
mutex_lock(&c->info->objc_types_mutex); mutex_lock(&c->info->objc_objc_msgSend_mutex);
map_set(&c->info->objc_msgSend_types, call, data); map_set(&c->info->objc_msgSend_types, call, data);
mutex_unlock(&c->info->objc_types_mutex); mutex_unlock(&c->info->objc_objc_msgSend_mutex);
try_to_add_package_dependency(c, "runtime", "objc_msgSend"); try_to_add_package_dependency(c, "runtime", "objc_msgSend");
try_to_add_package_dependency(c, "runtime", "objc_msgSend_fpret"); try_to_add_package_dependency(c, "runtime", "objc_msgSend_fpret");
@@ -387,6 +387,59 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan
try_to_add_package_dependency(c, "runtime", "objc_allocateClassPair"); try_to_add_package_dependency(c, "runtime", "objc_allocateClassPair");
return true; return true;
} break; } break;
case BuiltinProc_objc_ivar_get:
{
Type *self_type = nullptr;
Operand self = {};
check_expr_or_type(c, &self, ce->args[0]);
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", LIT(builtin_name), e, t);
gb_string_free(t);
gb_string_free(e);
return false;
} else if (!is_type_pointer(self.type)) {
gbString e = expr_to_string(self.expr);
gbString t = type_to_string(self.type);
error(self.expr, "'%.*s' expected a pointer of a 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;
}
self_type = type_deref(self.type);
if (!(self_type->kind == Type_Named &&
self_type->Named.type_name != nullptr &&
self_type->Named.type_name->TypeName.objc_class_name != "")) {
gbString t = type_to_string(self_type);
error(self.expr, "'%.*s' expected a named type with the attribute @(objc_class=<string>) , got type %s", LIT(builtin_name), t);
gb_string_free(t);
return false;
}
Type *ivar_type = self_type->Named.type_name->TypeName.objc_ivar;
if (ivar_type == nullptr) {
gbString t = type_to_string(self_type);
error(self.expr, "'%.*s' requires that type %s have the attribute @(objc_ivar=<ivar_type_name>).", LIT(builtin_name), t);
gb_string_free(t);
return false;
}
if (type_hint != nullptr && type_hint->kind == Type_Pointer && type_hint->Pointer.elem == ivar_type) {
operand->type = type_hint;
} else {
operand->type = alloc_type_pointer(ivar_type);
}
operand->mode = Addressing_Value;
return true;
} break;
} }
} }
@@ -2168,6 +2221,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
case BuiltinProc_objc_find_class: case BuiltinProc_objc_find_class:
case BuiltinProc_objc_register_selector: case BuiltinProc_objc_register_selector:
case BuiltinProc_objc_register_class: case BuiltinProc_objc_register_class:
case BuiltinProc_objc_ivar_get:
return check_builtin_objc_procedure(c, operand, call, id, type_hint); return check_builtin_objc_procedure(c, operand, call, id, type_hint);
case BuiltinProc___entry_point: case BuiltinProc___entry_point:
@@ -3189,6 +3243,194 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
break; break;
} }
case BuiltinProc_compress_values: {
Operand *ops = gb_alloc_array(temporary_allocator(), Operand, ce->args.count);
isize value_count = 0;
for_array(i, ce->args) {
Ast *arg = ce->args[i];
Operand *op = &ops[i];
check_multi_expr(c, op, arg);
if (op->mode == Addressing_Invalid) {
return false;
}
if (op->type == nullptr || op->type == t_invalid) {
gbString s = expr_to_string(op->expr);
error(op->expr, "Invalid expression to '%.*s', got %s", LIT(builtin_name), s);
gb_string_free(s);
}
if (is_type_tuple(op->type)) {
value_count += op->type->Tuple.variables.count;
} else {
value_count += 1;
}
}
GB_ASSERT(value_count >= 1);
if (value_count == 1) {
*operand = ops[0];
break;
}
if (type_hint != nullptr) {
Type *th = base_type(type_hint);
if (th->kind == Type_Struct) {
if (value_count == th->Struct.fields.count) {
isize index = 0;
for_array(i, ce->args) {
Operand *op = &ops[i];
if (is_type_tuple(op->type)) {
for (Entity *v : op->type->Tuple.variables) {
Operand x = {};
x.mode = Addressing_Value;
x.type = v->type;
check_assignment(c, &x, th->Struct.fields[index++]->type, builtin_name);
if (x.mode == Addressing_Invalid) {
return false;
}
}
} else {
check_assignment(c, op, th->Struct.fields[index++]->type, builtin_name);
if (op->mode == Addressing_Invalid) {
return false;
}
}
}
operand->type = type_hint;
operand->mode = Addressing_Value;
break;
}
} else if (is_type_array_like(th)) {
if (cast(i64)value_count == get_array_type_count(th)) {
Type *elem = base_array_type(th);
for_array(i, ce->args) {
Operand *op = &ops[i];
if (is_type_tuple(op->type)) {
for (Entity *v : op->type->Tuple.variables) {
Operand x = {};
x.mode = Addressing_Value;
x.type = v->type;
check_assignment(c, &x, elem, builtin_name);
if (x.mode == Addressing_Invalid) {
return false;
}
}
} else {
check_assignment(c, op, elem, builtin_name);
if (op->mode == Addressing_Invalid) {
return false;
}
}
}
operand->type = type_hint;
operand->mode = Addressing_Value;
break;
}
}
}
bool all_types_the_same = true;
Type *last_type = nullptr;
for_array(i, ce->args) {
Operand *op = &ops[i];
if (is_type_tuple(op->type)) {
if (last_type == nullptr) {
op->type->Tuple.variables[0]->type;
}
for (Entity *v : op->type->Tuple.variables) {
if (!are_types_identical(last_type, v->type)) {
all_types_the_same = false;
break;
}
last_type = v->type;
}
} else {
if (last_type == nullptr) {
last_type = op->type;
} else {
if (!are_types_identical(last_type, op->type)) {
all_types_the_same = false;
break;
}
last_type = op->type;
}
}
}
if (all_types_the_same) {
Type *elem_type = default_type(last_type);
if (is_type_untyped(elem_type)) {
gbString s = expr_to_string(call);
error(call, "Invalid use of '%s' in '%.*s'", s, LIT(builtin_name));
gb_string_free(s);
return false;
}
operand->type = alloc_type_array(elem_type, value_count);
operand->mode = Addressing_Value;
} else {
Type *st = alloc_type_struct_complete();
st->Struct.fields = slice_make<Entity *>(permanent_allocator(), value_count);
st->Struct.tags = gb_alloc_array(permanent_allocator(), String, value_count);
st->Struct.offsets = gb_alloc_array(permanent_allocator(), i64, value_count);
Scope *scope = create_scope(c->info, nullptr);
Token token = {};
token.kind = Token_Ident;
token.pos = ast_token(call).pos;
isize index = 0;
for_array(i, ce->args) {
Operand *op = &ops[i];
if (is_type_tuple(op->type)) {
for (Entity *v : op->type->Tuple.variables) {
Type *t = default_type(v->type);
if (is_type_untyped(t)) {
gbString s = expr_to_string(op->expr);
error(op->expr, "Invalid use of '%s' in '%.*s'", s, LIT(builtin_name));
gb_string_free(s);
return false;
}
gbString s = gb_string_make_reserve(permanent_allocator(), 32);
s = gb_string_append_fmt(s, "v%lld", cast(long long)index);
token.string = make_string_c(s);
Entity *e = alloc_entity_field(scope, token, t, false, cast(i32)index, EntityState_Resolved);
st->Struct.fields[index++] = e;
}
} else {
Type *t = default_type(op->type);
if (is_type_untyped(t)) {
gbString s = expr_to_string(op->expr);
error(op->expr, "Invalid use of '%s' in '%.*s'", s, LIT(builtin_name));
gb_string_free(s);
return false;
}
gbString s = gb_string_make_reserve(permanent_allocator(), 32);
s = gb_string_append_fmt(s, "v%lld", cast(long long)index);
token.string = make_string_c(s);
Entity *e = alloc_entity_field(scope, token, t, false, cast(i32)index, EntityState_Resolved);
st->Struct.fields[index++] = e;
}
}
gb_unused(type_size_of(st));
operand->type = st;
operand->mode = Addressing_Value;
}
break;
}
case BuiltinProc_min: { case BuiltinProc_min: {
// min :: proc($T: typeid) -> ordered // min :: proc($T: typeid) -> ordered
// min :: proc(a: ..ordered) -> ordered // min :: proc(a: ..ordered) -> ordered
@@ -5635,6 +5877,87 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
} }
operand->mode = Addressing_Type; operand->mode = Addressing_Type;
break; break;
case BuiltinProc_type_integer_to_unsigned:
if (operand->mode != Addressing_Type) {
error(operand->expr, "Expected a type for '%.*s'", LIT(builtin_name));
return false;
}
if (is_type_polymorphic(operand->type)) {
gbString t = type_to_string(operand->type);
error(operand->expr, "Expected a non-polymorphic type for '%.*s', got %s", LIT(builtin_name), t);
gb_string_free(t);
return false;
}
{
Type *bt = base_type(operand->type);
if (bt->kind != Type_Basic ||
(bt->Basic.flags & BasicFlag_Unsigned) != 0 ||
(bt->Basic.flags & BasicFlag_Integer) == 0) {
gbString t = type_to_string(operand->type);
error(operand->expr, "Expected a signed integer type for '%.*s', got %s", LIT(builtin_name), t);
gb_string_free(t);
return false;
}
if ((bt->Basic.flags & BasicFlag_Untyped) != 0) {
gbString t = type_to_string(operand->type);
error(operand->expr, "Expected a non-untyped integer type for '%.*s', got %s", LIT(builtin_name), t);
gb_string_free(t);
return false;
}
Type *u_type = &basic_types[bt->Basic.kind + 1];
operand->type = u_type;
}
break;
case BuiltinProc_type_integer_to_signed:
if (operand->mode != Addressing_Type) {
error(operand->expr, "Expected a type for '%.*s'", LIT(builtin_name));
return false;
}
if (is_type_polymorphic(operand->type)) {
gbString t = type_to_string(operand->type);
error(operand->expr, "Expected a non-polymorphic type for '%.*s', got %s", LIT(builtin_name), t);
gb_string_free(t);
return false;
}
{
Type *bt = base_type(operand->type);
if (bt->kind != Type_Basic ||
(bt->Basic.flags & BasicFlag_Unsigned) == 0 ||
(bt->Basic.flags & BasicFlag_Integer) == 0) {
gbString t = type_to_string(operand->type);
error(operand->expr, "Expected an unsigned integer type for '%.*s', got %s", LIT(builtin_name), t);
gb_string_free(t);
return false;
}
if ((bt->Basic.flags & BasicFlag_Untyped) != 0) {
gbString t = type_to_string(operand->type);
error(operand->expr, "Expected a non-untyped integer type for '%.*s', got %s", LIT(builtin_name), t);
gb_string_free(t);
return false;
}
if (bt->Basic.kind == Basic_uintptr) {
gbString t = type_to_string(operand->type);
error(operand->expr, "Type %s does not have a signed integer mapping for '%.*s'", t, LIT(builtin_name));
gb_string_free(t);
return false;
}
Type *u_type = &basic_types[bt->Basic.kind - 1];
operand->type = u_type;
}
break;
case BuiltinProc_type_merge: case BuiltinProc_type_merge:
{ {
operand->mode = Addressing_Type; operand->mode = Addressing_Type;
+177 -36
View File
@@ -524,12 +524,90 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr,
if (decl != nullptr) { if (decl != nullptr) {
AttributeContext ac = {}; AttributeContext ac = {};
check_decl_attributes(ctx, decl->attributes, type_decl_attribute, &ac); check_decl_attributes(ctx, decl->attributes, type_decl_attribute, &ac);
if (e->kind == Entity_TypeName && ac.objc_class != "") { if (e->kind == Entity_TypeName && ac.objc_class != "") {
e->TypeName.objc_class_name = ac.objc_class; e->TypeName.objc_class_name = ac.objc_class;
if (ac.objc_is_implementation) {
e->TypeName.objc_is_implementation = ac.objc_is_implementation;
e->TypeName.objc_superclass = ac.objc_superclass;
e->TypeName.objc_ivar = ac.objc_ivar;
e->TypeName.objc_context_provider = ac.objc_context_provider;
mutex_lock(&ctx->info->objc_class_name_mutex);
bool class_exists = string_set_update(&ctx->info->obcj_class_name_set, ac.objc_class);
mutex_unlock(&ctx->info->objc_class_name_mutex);
if (class_exists) {
error(e->token, "@(objc_class) name '%.*s' has already been used elsewhere", LIT(ac.objc_class));
}
mpsc_enqueue(&ctx->info->objc_class_implementations, e);
GB_ASSERT(e->TypeName.objc_ivar == nullptr || e->TypeName.objc_ivar->kind == Type_Named);
// Enqueue the contex_provider proc to be checked after it is resolved
if (e->TypeName.objc_context_provider != nullptr) {
mpsc_enqueue(&ctx->checker->procs_with_objc_context_provider_to_check, e);
}
// TODO(harold): I think there's a Check elsewhere in the checker for checking cycles.
// See about moving this to the right location.
// Ensure superclass hierarchy are all Objective-C classes and does not cycle
// NOTE(harold): We check for superclass unconditionally (before checking if super is null)
// because this should be the case 99.99% of the time. Not subclassing something that
// is, or is the child of, NSObject means the objc runtime messaging will not properly work on this type.
TypeSet super_set{};
type_set_init(&super_set, 8);
defer (type_set_destroy(&super_set));
type_set_update(&super_set, e->type);
Type *super = ac.objc_superclass;
while (super != nullptr) {
if (type_set_update(&super_set, super)) {
error(e->token, "@(objc_superclass) Superclass hierarchy cycle encountered");
break;
}
check_single_global_entity(ctx->checker, super->Named.type_name, super->Named.type_name->decl_info);
if (super->kind != Type_Named) {
error(e->token, "@(objc_superclass) Referenced type must be a named struct");
break;
}
Type* named_type = base_named_type(super);
GB_ASSERT(named_type->kind == Type_Named);
if (!is_type_objc_object(named_type)) {
error(e->token, "@(objc_superclass) Superclass '%.*s' must be an Objective-C class", LIT(named_type->Named.name));
break;
}
if (named_type->Named.type_name->TypeName.objc_class_name == "") {
error(e->token, "@(objc_superclass) Superclass '%.*s' must have a valid @(objc_class) attribute", LIT(named_type->Named.name));
break;
}
super = named_type->Named.type_name->TypeName.objc_superclass;
}
} else {
if (ac.objc_superclass != nullptr) {
error(e->token, "@(objc_superclass) may only be applied when the @(obj_implement) attribute is also applied");
} else if (ac.objc_ivar != nullptr) {
error(e->token, "@(objc_ivar) may only be applied when the @(obj_implement) attribute is also applied");
} else if (ac.objc_context_provider != nullptr) {
error(e->token, "@(objc_context_provider) may only be applied when the @(obj_implement) attribute is also applied");
}
}
if (type_size_of(e->type) > 0) { if (type_size_of(e->type) > 0) {
error(e->token, "@(objc_class) marked type must be of zero size"); error(e->token, "@(objc_class) marked type must be of zero size");
} }
} else if (ac.objc_is_implementation) {
error(e->token, "@(objc_implement) may only be applied when the @(objc_class) attribute is also applied");
} }
} }
@@ -922,7 +1000,7 @@ gb_internal String handle_link_name(CheckerContext *ctx, Token token, String lin
} }
gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeContext const &ac) { gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeContext &ac) {
if (!(ac.objc_name.len || ac.objc_is_class_method || ac.objc_type)) { if (!(ac.objc_name.len || ac.objc_is_class_method || ac.objc_type)) {
return; return;
} }
@@ -934,48 +1012,107 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon
error(e->token, "@(objc_name) is required with @(objc_type)"); error(e->token, "@(objc_name) is required with @(objc_type)");
} else { } else {
Type *t = ac.objc_type; Type *t = ac.objc_type;
if (t->kind == Type_Named) {
Entity *tn = t->Named.type_name;
GB_ASSERT(tn->kind == Entity_TypeName); GB_ASSERT(t->kind == Type_Named); // NOTE(harold): This is already checked for at the attribute resolution stage.
Entity *tn = t->Named.type_name;
if (tn->scope != e->scope) { GB_ASSERT(tn->kind == Entity_TypeName);
error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope");
} else {
mutex_lock(&global_type_name_objc_metadata_mutex);
defer (mutex_unlock(&global_type_name_objc_metadata_mutex));
if (!tn->TypeName.objc_metadata) { if (tn->scope != e->scope) {
tn->TypeName.objc_metadata = create_type_name_obj_c_metadata(); error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope");
} } else {
auto *md = tn->TypeName.objc_metadata;
mutex_lock(md->mutex);
defer (mutex_unlock(md->mutex));
if (!ac.objc_is_class_method) { // Enable implementation by default if the class is an implementer too and
bool ok = true; // @objc_implement was not set to false explicitly in this proc.
for (TypeNameObjCMetadataEntry const &entry : md->value_entries) { bool implement = tn->TypeName.objc_is_implementation;
if (entry.name == ac.objc_name) { if (ac.objc_is_disabled_implement) {
error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name)); implement = false;
ok = false; }
break;
} if (implement) {
} GB_ASSERT(e->kind == Entity_Procedure);
if (ok) {
array_add(&md->value_entries, TypeNameObjCMetadataEntry{ac.objc_name, e}); auto &proc = e->type->Proc;
} Type *first_param = proc.param_count > 0 ? proc.params->Tuple.variables[0]->type : t_untyped_nil;
if (!tn->TypeName.objc_is_implementation) {
error(e->token, "@(objc_is_implement) attribute may only be applied to procedures whose class also have @(objc_is_implement) applied");
} else if (!ac.objc_is_class_method && !(first_param->kind == Type_Pointer && internal_check_is_assignable_to(t, first_param->Pointer.elem))) {
error(e->token, "Objective-C instance methods implementations require the first parameter to be a pointer to the class type set by @(objc_type)");
} else if (proc.calling_convention == ProcCC_Odin && !tn->TypeName.objc_context_provider) {
error(e->token, "Objective-C methods with Odin calling convention can only be used with classes that have @(objc_context_provider) set");
} else if (ac.objc_is_class_method && proc.calling_convention != ProcCC_CDecl) {
error(e->token, "Objective-C class methods (objc_is_class_method=true) that have @objc_is_implementation can only use \"c\" calling convention");
} else if (proc.result_count > 1) {
error(e->token, "Objective-C method implementations may return at most 1 value");
} else { } else {
bool ok = true; // Always export unconditionally
for (TypeNameObjCMetadataEntry const &entry : md->type_entries) { // NOTE(harold): This means check_objc_methods() MUST be called before
if (entry.name == ac.objc_name) { // e->Procedure.is_export is set in check_proc_decl()!
error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name)); if (ac.is_export) {
ok = false; error(e->token, "Explicit export not allowed when @(objc_implement) is set. It set exported implicitly");
break;
}
} }
if (ok) { if (ac.link_name != "") {
array_add(&md->type_entries, TypeNameObjCMetadataEntry{ac.objc_name, e}); error(e->token, "Explicit linkage not allowed when @(objc_implement) is set. It set to \"strong\" implicitly");
} }
ac.is_export = true;
ac.linkage = STR_LIT("strong");
auto method = ObjcMethodData{ ac, e };
method.ac.objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name;
CheckerInfo *info = ctx->info;
mutex_lock(&info->objc_method_mutex);
defer (mutex_unlock(&info->objc_method_mutex));
Array<ObjcMethodData>* method_list = map_get(&info->objc_method_implementations, t);
if (method_list) {
array_add(method_list, method);
} else {
auto list = array_make<ObjcMethodData>(permanent_allocator(), 1, 8);
list[0] = method;
map_set(&info->objc_method_implementations, t, list);
}
}
} else if (ac.objc_selector != "") {
error(e->token, "@(objc_selector) may only be applied to procedures that are Objective-C implementations.");
}
mutex_lock(&global_type_name_objc_metadata_mutex);
defer (mutex_unlock(&global_type_name_objc_metadata_mutex));
if (!tn->TypeName.objc_metadata) {
tn->TypeName.objc_metadata = create_type_name_obj_c_metadata();
}
auto *md = tn->TypeName.objc_metadata;
mutex_lock(md->mutex);
defer (mutex_unlock(md->mutex));
if (!ac.objc_is_class_method) {
bool ok = true;
for (TypeNameObjCMetadataEntry const &entry : md->value_entries) {
if (entry.name == ac.objc_name) {
error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name));
ok = false;
break;
}
}
if (ok) {
array_add(&md->value_entries, TypeNameObjCMetadataEntry{ac.objc_name, e});
}
} else {
bool ok = true;
for (TypeNameObjCMetadataEntry const &entry : md->type_entries) {
if (entry.name == ac.objc_name) {
error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name));
ok = false;
break;
}
}
if (ok) {
array_add(&md->type_entries, TypeNameObjCMetadataEntry{ac.objc_name, e});
} }
} }
} }
@@ -1145,6 +1282,9 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
break; break;
} }
// NOTE(harold): For Objective-C method implementations, this must happen after
// check_objc_methods() is called as it re-sets ac.is_export to true unconditionally.
// The same is true for the linkage, set below.
e->Procedure.entry_point_only = ac.entry_point_only; e->Procedure.entry_point_only = ac.entry_point_only;
e->Procedure.is_export = ac.is_export; e->Procedure.is_export = ac.is_export;
@@ -1245,6 +1385,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
} }
} }
// NOTE(harold): See export/linkage note above(where is_export is assigned) regarding Objective-C method implementations
bool is_foreign = e->Procedure.is_foreign; bool is_foreign = e->Procedure.is_foreign;
bool is_export = e->Procedure.is_export; bool is_export = e->Procedure.is_export;
+42 -16
View File
@@ -643,7 +643,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E
gb_internal bool check_polymorphic_procedure_assignment(CheckerContext *c, Operand *operand, Type *type, Ast *poly_def_node, PolyProcData *poly_proc_data) { gb_internal bool check_polymorphic_procedure_assignment(CheckerContext *c, Operand *operand, Type *type, Ast *poly_def_node, PolyProcData *poly_proc_data) {
if (operand->expr == nullptr) return false; if (operand->expr == nullptr) return false;
Entity *base_entity = entity_of_node(operand->expr); Entity *base_entity = entity_from_expr(operand->expr);
if (base_entity == nullptr) return false; if (base_entity == nullptr) return false;
return find_or_generate_polymorphic_procedure(c, base_entity, type, nullptr, poly_def_node, poly_proc_data); return find_or_generate_polymorphic_procedure(c, base_entity, type, nullptr, poly_def_node, poly_proc_data);
} }
@@ -995,14 +995,34 @@ gb_internal i64 assign_score_function(i64 distance, bool is_variadic=false) {
gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *operand, Type *type, i64 *score_, bool is_variadic=false, bool allow_array_programming=true) { gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *operand, Type *type, i64 *score_, bool is_variadic=false, bool allow_array_programming=true) {
i64 score = 0; if (c == nullptr) {
i64 distance = check_distance_between_types(c, operand, type, allow_array_programming); GB_ASSERT(operand->mode == Addressing_Value);
bool ok = distance >= 0; GB_ASSERT(is_type_typed(operand->type));
if (ok) {
score = assign_score_function(distance, is_variadic);
} }
if (score_) *score_ = score; if (operand->mode == Addressing_Invalid || type == t_invalid) {
return ok; if (score_) *score_ = 0;
return false;
}
// Handle polymorphic procedure used as default parameter
if (operand->mode == Addressing_Value && is_type_proc(type) && is_type_proc(operand->type)) {
Entity *e = entity_from_expr(operand->expr);
if (e != nullptr && e->kind == Entity_Procedure && is_type_polymorphic(e->type) && !is_type_polymorphic(type)) {
// Special case: Allow a polymorphic procedure to be used as default value for concrete proc type
// during the initial check. It will be properly instantiated when actually used.
if (score_) *score_ = assign_score_function(1);
return true;
}
}
i64 score = check_distance_between_types(c, operand, type, allow_array_programming);
if (score >= 0) {
if (score_) *score_ = assign_score_function(score, is_variadic);
return true;
}
if (score_) *score_ = 0;
return false;
} }
@@ -5441,8 +5461,18 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
} }
} }
if (operand->type && is_type_simd_vector(type_deref(operand->type))) {
String field_name = selector->Ident.token.string;
if (field_name.len == 1) {
error(op_expr, "Extracting an element from a #simd array using .%.*s syntax is disallowed, prefer `simd.extract`", LIT(field_name));
} else {
error(op_expr, "Extracting elements from a #simd array using .%.*s syntax is disallowed, prefer `swizzle`", LIT(field_name));
}
return nullptr;
}
if (entity == nullptr && selector->kind == Ast_Ident && operand->type != nullptr && if (entity == nullptr && selector->kind == Ast_Ident && operand->type != nullptr &&
(is_type_array(type_deref(operand->type)) || is_type_simd_vector(type_deref(operand->type)))) { (is_type_array(type_deref(operand->type)))) {
String field_name = selector->Ident.token.string; String field_name = selector->Ident.token.string;
if (1 < field_name.len && field_name.len <= 4) { if (1 < field_name.len && field_name.len <= 4) {
u8 swizzles_xyzw[4] = {'x', 'y', 'z', 'w'}; u8 swizzles_xyzw[4] = {'x', 'y', 'z', 'w'};
@@ -5497,7 +5527,7 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
Type *original_type = operand->type; Type *original_type = operand->type;
Type *array_type = base_type(type_deref(original_type)); Type *array_type = base_type(type_deref(original_type));
GB_ASSERT(array_type->kind == Type_Array || array_type->kind == Type_SimdVector); GB_ASSERT(array_type->kind == Type_Array);
i64 array_count = get_array_type_count(array_type); i64 array_count = get_array_type_count(array_type);
@@ -5538,10 +5568,6 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
break; break;
} }
if (array_type->kind == Type_SimdVector) {
operand->mode = Addressing_Value;
}
Entity *swizzle_entity = alloc_entity_variable(nullptr, make_token_ident(field_name), operand->type, EntityState_Resolved); Entity *swizzle_entity = alloc_entity_variable(nullptr, make_token_ident(field_name), operand->type, EntityState_Resolved);
add_type_and_value(c, operand->expr, operand->mode, operand->type, operand->value); add_type_and_value(c, operand->expr, operand->mode, operand->type, operand->value);
return swizzle_entity; return swizzle_entity;
@@ -9413,7 +9439,7 @@ gb_internal bool is_expr_inferred_fixed_array(Ast *type_expr) {
} }
gb_internal bool check_for_dynamic_literals(CheckerContext *c, Ast *node, AstCompoundLit *cl) { gb_internal bool check_for_dynamic_literals(CheckerContext *c, Ast *node, AstCompoundLit *cl) {
if (cl->elems.count > 0 && (check_feature_flags(c, node) & OptInFeatureFlag_DynamicLiterals) == 0) { if (cl->elems.count > 0 && (check_feature_flags(c, node) & OptInFeatureFlag_DynamicLiterals) == 0 && !build_context.dynamic_literals) {
ERROR_BLOCK(); ERROR_BLOCK();
error(node, "Compound literals of dynamic types are disabled by default"); error(node, "Compound literals of dynamic types are disabled by default");
error_line("\tSuggestion: If you want to enable them for this specific file, add '#+feature dynamic-literals' at the top of the file\n"); error_line("\tSuggestion: If you want to enable them for this specific file, add '#+feature dynamic-literals' at the top of the file\n");
@@ -10996,7 +11022,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast
return kind; return kind;
case_end; case_end;
case_ast_node(i, Implicit, node) case_ast_node(i, Implicit, node);
switch (i->kind) { switch (i->kind) {
case Token_context: case Token_context:
{ {
+2
View File
@@ -2108,10 +2108,12 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f
if (init_type == nullptr) { if (init_type == nullptr) {
init_type = t_invalid; init_type = t_invalid;
} else if (is_type_polymorphic(base_type(init_type))) { } else if (is_type_polymorphic(base_type(init_type))) {
/* DISABLED: This error seems too aggressive for instantiated generic types.
gbString str = type_to_string(init_type); gbString str = type_to_string(init_type);
error(vd->type, "Invalid use of a polymorphic type '%s' in variable declaration", str); error(vd->type, "Invalid use of a polymorphic type '%s' in variable declaration", str);
gb_string_free(str); gb_string_free(str);
init_type = t_invalid; init_type = t_invalid;
*/
} }
if (init_type == t_invalid && entity_count == 1 && (mod_flags & (Stmt_BreakAllowed|Stmt_FallthroughAllowed))) { if (init_type == t_invalid && entity_count == 1 && (mod_flags & (Stmt_BreakAllowed|Stmt_FallthroughAllowed))) {
Entity *e = entities[0]; Entity *e = entities[0];
+10 -1
View File
@@ -1910,9 +1910,18 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
case ParameterValue_Location: case ParameterValue_Location:
case ParameterValue_Expression: case ParameterValue_Expression:
case ParameterValue_Value: case ParameterValue_Value:
// Special case for polymorphic procedures as default values
if (param_value.ast_value != nullptr) {
Entity *e = entity_from_expr(param_value.ast_value);
if (e != nullptr && e->kind == Entity_Procedure && is_type_polymorphic(e->type)) {
// Allow polymorphic procedures as default parameter values
// The type will be correctly determined at call site
break;
}
}
gbString str = type_to_string(type); gbString str = type_to_string(type);
error(params[i], "A default value for a parameter must not be a polymorphic constant type, got %s", str); error(params[i], "A default value for a parameter must not be a polymorphic constant type, got %s", str);
gb_string_free(str); gb_string_free(str);
break; break;
} }
} }
+135 -2
View File
@@ -728,12 +728,17 @@ gb_internal void check_scope_usage_internal(Checker *c, Scope *scope, u64 vet_fl
bool is_unused = false; bool is_unused = false;
if (vet_unused && check_vet_unused(c, e, &ve_unused)) { if (vet_unused && check_vet_unused(c, e, &ve_unused)) {
is_unused = true; is_unused = true;
} else if (vet_unused_procedures && } else if (vet_unused_procedures && e->kind == Entity_Procedure) {
e->kind == Entity_Procedure) {
if (e->flags&EntityFlag_Used) { if (e->flags&EntityFlag_Used) {
is_unused = false; is_unused = false;
} else if (e->flags & EntityFlag_Require) { } else if (e->flags & EntityFlag_Require) {
is_unused = false; is_unused = false;
} else if (e->flags & EntityFlag_Init) {
is_unused = false;
} else if (e->flags & EntityFlag_Fini) {
is_unused = false;
} else if (e->Procedure.is_export) {
is_unused = false;
} else if (e->pkg && e->pkg->kind == Package_Init && e->token.string == "main") { } else if (e->pkg && e->pkg->kind == Package_Init && e->token.string == "main") {
is_unused = false; is_unused = false;
} else { } else {
@@ -1351,10 +1356,12 @@ gb_internal void init_universal(void) {
t_objc_object = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_object"), alloc_type_struct_complete()); t_objc_object = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_object"), alloc_type_struct_complete());
t_objc_selector = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_selector"), alloc_type_struct_complete()); t_objc_selector = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_selector"), alloc_type_struct_complete());
t_objc_class = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_class"), alloc_type_struct_complete()); t_objc_class = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_class"), alloc_type_struct_complete());
t_objc_ivar = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_ivar"), alloc_type_struct_complete());
t_objc_id = alloc_type_pointer(t_objc_object); t_objc_id = alloc_type_pointer(t_objc_object);
t_objc_SEL = alloc_type_pointer(t_objc_selector); t_objc_SEL = alloc_type_pointer(t_objc_selector);
t_objc_Class = alloc_type_pointer(t_objc_class); t_objc_Class = alloc_type_pointer(t_objc_class);
t_objc_Ivar = alloc_type_pointer(t_objc_ivar);
} }
} }
@@ -1387,6 +1394,10 @@ gb_internal void init_checker_info(CheckerInfo *i) {
array_init(&i->defineables, a); array_init(&i->defineables, a);
map_init(&i->objc_msgSend_types); map_init(&i->objc_msgSend_types);
mpsc_init(&i->objc_class_implementations, a);
string_set_init(&i->obcj_class_name_set, 0);
map_init(&i->objc_method_implementations);
string_map_init(&i->load_file_cache); string_map_init(&i->load_file_cache);
array_init(&i->all_procedures, heap_allocator()); array_init(&i->all_procedures, heap_allocator());
@@ -1497,6 +1508,8 @@ gb_internal void init_checker(Checker *c) {
TIME_SECTION("init proc queues"); TIME_SECTION("init proc queues");
mpsc_init(&c->procs_with_deferred_to_check, a); //, 1<<10); mpsc_init(&c->procs_with_deferred_to_check, a); //, 1<<10);
mpsc_init(&c->procs_with_objc_context_provider_to_check, a);
// NOTE(bill): 1 Mi elements should be enough on average // NOTE(bill): 1 Mi elements should be enough on average
array_init(&c->procs_to_check, heap_allocator(), 0, 1<<20); array_init(&c->procs_to_check, heap_allocator(), 0, 1<<20);
@@ -3662,6 +3675,33 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
} }
} }
return true; return true;
} else if (name == "objc_implement") {
ExactValue ev = check_decl_attribute_value(c, value);
if (ev.kind == ExactValue_Bool) {
ac->objc_is_implementation = ev.value_bool;
if (!ac->objc_is_implementation) {
ac->objc_is_disabled_implement = true;
}
} else if (ev.kind == ExactValue_Invalid) {
ac->objc_is_implementation = true;
} else {
error(elem, "Expected a boolean value, or no value, for '%.*s'", LIT(name));
}
return true;
} else if (name == "objc_selector") {
ExactValue ev = check_decl_attribute_value(c, value);
if (ev.kind == ExactValue_String) {
if (string_is_valid_identifier(ev.value_string)) {
ac->objc_selector = ev.value_string;
} else {
error(elem, "Invalid identifier for '%.*s', got '%.*s'", LIT(name), LIT(ev.value_string));
}
} else {
error(elem, "Expected a string value for '%.*s'", LIT(name));
}
return true;
} else if (name == "require_target_feature") { } else if (name == "require_target_feature") {
ExactValue ev = check_decl_attribute_value(c, value); ExactValue ev = check_decl_attribute_value(c, value);
if (ev.kind == ExactValue_String) { if (ev.kind == ExactValue_String) {
@@ -3907,6 +3947,51 @@ gb_internal DECL_ATTRIBUTE_PROC(type_decl_attribute) {
ac->objc_class = ev.value_string; ac->objc_class = ev.value_string;
} }
return true; return true;
} else if (name == "objc_implement") {
ExactValue ev = check_decl_attribute_value(c, value);
if (ev.kind == ExactValue_Bool) {
ac->objc_is_implementation = ev.value_bool;
} else if (ev.kind == ExactValue_Invalid) {
ac->objc_is_implementation = true;
} else {
error(elem, "Expected a boolean value, or no value, for '%.*s'", LIT(name));
}
return true;
} else if (name == "objc_superclass") {
Type *objc_superclass = check_type(c, value);
if (objc_superclass != nullptr) {
ac->objc_superclass = objc_superclass;
} else {
error(value, "'%.*s' expected a named type", LIT(name));
}
return true;
} else if (name == "objc_ivar") {
Type *objc_ivar = check_type(c, value);
if (objc_ivar != nullptr && objc_ivar->kind == Type_Named) {
ac->objc_ivar = objc_ivar;
} else {
error(value, "'%.*s' expected a named type", LIT(name));
}
return true;
} else if (name == "objc_context_provider") {
Operand o = {};
check_expr(c, &o, value);
Entity *e = entity_of_node(o.expr);
if (e != nullptr) {
if (ac->objc_context_provider != nullptr) {
error(elem, "Previous usage of a 'objc_context_provider' attribute");
}
if (e->kind != Entity_Procedure) {
error(elem, "'objc_context_provider' must refer to a procedure");
} else {
ac->objc_context_provider = e;
}
return true;
}
} }
return false; return false;
} }
@@ -6240,6 +6325,12 @@ gb_internal void check_deferred_procedures(Checker *c) {
continue; continue;
} }
if (dst->flags & EntityFlag_Disabled) {
// Prevent procedures that have been disabled from acting as deferrals.
src->Procedure.deferred_procedure = {};
continue;
}
GB_ASSERT(is_type_proc(src->type)); GB_ASSERT(is_type_proc(src->type));
GB_ASSERT(is_type_proc(dst->type)); GB_ASSERT(is_type_proc(dst->type));
Type *src_params = base_type(src->type)->Proc.params; Type *src_params = base_type(src->type)->Proc.params;
@@ -6395,6 +6486,44 @@ gb_internal void check_deferred_procedures(Checker *c) {
} }
gb_internal void check_objc_context_provider_procedures(Checker *c) {
for (Entity *e = nullptr; mpsc_dequeue(&c->procs_with_objc_context_provider_to_check, &e); /**/) {
GB_ASSERT(e->kind == Entity_TypeName);
Entity *proc_entity = e->TypeName.objc_context_provider;
GB_ASSERT(proc_entity->kind == Entity_Procedure);
auto &proc = proc_entity->type->Proc;
Type *return_type = proc.result_count != 1 ? t_untyped_nil : base_named_type(proc.results->Tuple.variables[0]->type);
if (return_type != t_context) {
error(proc_entity->token, "The @(objc_context_provider) procedure must only return a context.");
}
const char *self_param_err = "The @(objc_context_provider) procedure must take as a parameter a single pointer to the @(objc_type) value.";
if (proc.param_count != 1) {
error(proc_entity->token, self_param_err);
}
Type *self_param = base_type(proc.params->Tuple.variables[0]->type);
if (self_param->kind != Type_Pointer) {
error(proc_entity->token, self_param_err);
}
Type *self_type = base_named_type(self_param->Pointer.elem);
if (!internal_check_is_assignable_to(self_type, e->type) &&
!(e->TypeName.objc_ivar && internal_check_is_assignable_to(self_type, e->TypeName.objc_ivar))) {
error(proc_entity->token, self_param_err);
}
if (proc.calling_convention != ProcCC_CDecl && proc.calling_convention != ProcCC_Contextless) {
error(e->token, self_param_err);
}
if (proc.is_polymorphic) {
error(e->token, self_param_err);
}
}
}
gb_internal void check_unique_package_names(Checker *c) { gb_internal void check_unique_package_names(Checker *c) {
ERROR_BLOCK(); ERROR_BLOCK();
@@ -6555,6 +6684,7 @@ gb_internal void check_update_dependency_tree_for_procedures(Checker *c) {
} }
} }
gb_internal void check_parsed_files(Checker *c) { gb_internal void check_parsed_files(Checker *c) {
TIME_SECTION("map full filepaths to scope"); TIME_SECTION("map full filepaths to scope");
add_type_info_type(&c->builtin_ctx, t_invalid); add_type_info_type(&c->builtin_ctx, t_invalid);
@@ -6664,6 +6794,9 @@ gb_internal void check_parsed_files(Checker *c) {
TIME_SECTION("check deferred procedures"); TIME_SECTION("check deferred procedures");
check_deferred_procedures(c); check_deferred_procedures(c);
TIME_SECTION("check objc context provider procedures");
check_objc_context_provider_procedures(c);
TIME_SECTION("calculate global init order"); TIME_SECTION("calculate global init order");
calculate_global_init_order(c); calculate_global_init_order(c);
+22 -2
View File
@@ -149,8 +149,14 @@ struct AttributeContext {
String objc_class; String objc_class;
String objc_name; String objc_name;
bool objc_is_class_method; String objc_selector;
Type * objc_type; Type * objc_type;
Type * objc_superclass;
Type * objc_ivar;
Entity *objc_context_provider;
bool objc_is_class_method;
bool objc_is_implementation; // This struct or proc provides a class/method implementation, not a binding to an existing type.
bool objc_is_disabled_implement; // This means the method explicitly set @objc_implement to false so it won't be inferred from the class' attribute.
String require_target_feature; // required by the target micro-architecture String require_target_feature; // required by the target micro-architecture
String enable_target_feature; // will be enabled for the procedure only String enable_target_feature; // will be enabled for the procedure only
@@ -366,6 +372,11 @@ struct ObjcMsgData {
Type *proc_type; Type *proc_type;
}; };
struct ObjcMethodData {
AttributeContext ac;
Entity *proc_entity;
};
enum LoadFileTier { enum LoadFileTier {
LoadFileTier_Invalid, LoadFileTier_Invalid,
LoadFileTier_Exists, LoadFileTier_Exists,
@@ -477,9 +488,17 @@ struct CheckerInfo {
MPSCQueue<Ast *> intrinsics_entry_point_usage; MPSCQueue<Ast *> intrinsics_entry_point_usage;
BlockingMutex objc_types_mutex; BlockingMutex objc_objc_msgSend_mutex;
PtrMap<Ast *, ObjcMsgData> objc_msgSend_types; PtrMap<Ast *, ObjcMsgData> objc_msgSend_types;
BlockingMutex objc_class_name_mutex;
StringSet obcj_class_name_set;
MPSCQueue<Entity *> objc_class_implementations;
BlockingMutex objc_method_mutex;
PtrMap<Type *, Array<ObjcMethodData>> objc_method_implementations;
BlockingMutex load_file_mutex; BlockingMutex load_file_mutex;
StringMap<LoadFileCache *> load_file_cache; StringMap<LoadFileCache *> load_file_cache;
@@ -556,6 +575,7 @@ struct Checker {
CheckerContext builtin_ctx; CheckerContext builtin_ctx;
MPSCQueue<Entity *> procs_with_deferred_to_check; MPSCQueue<Entity *> procs_with_deferred_to_check;
MPSCQueue<Entity *> procs_with_objc_context_provider_to_check;
Array<ProcInfo *> procs_to_check; Array<ProcInfo *> procs_to_check;
BlockingMutex nested_proc_lits_mutex; BlockingMutex nested_proc_lits_mutex;
+10
View File
@@ -26,6 +26,7 @@ enum BuiltinProcId {
BuiltinProc_conj, BuiltinProc_conj,
BuiltinProc_expand_values, BuiltinProc_expand_values,
BuiltinProc_compress_values,
BuiltinProc_min, BuiltinProc_min,
BuiltinProc_max, BuiltinProc_max,
@@ -234,6 +235,9 @@ BuiltinProc__type_begin,
BuiltinProc_type_convert_variants_to_pointers, BuiltinProc_type_convert_variants_to_pointers,
BuiltinProc_type_merge, BuiltinProc_type_merge,
BuiltinProc_type_integer_to_unsigned,
BuiltinProc_type_integer_to_signed,
BuiltinProc__type_simple_boolean_begin, BuiltinProc__type_simple_boolean_begin,
BuiltinProc_type_is_boolean, BuiltinProc_type_is_boolean,
BuiltinProc_type_is_integer, BuiltinProc_type_is_integer,
@@ -338,6 +342,7 @@ BuiltinProc__type_end,
BuiltinProc_objc_find_class, BuiltinProc_objc_find_class,
BuiltinProc_objc_register_selector, BuiltinProc_objc_register_selector,
BuiltinProc_objc_register_class, BuiltinProc_objc_register_class,
BuiltinProc_objc_ivar_get,
BuiltinProc_constant_utf16_cstring, BuiltinProc_constant_utf16_cstring,
@@ -375,6 +380,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
{STR_LIT("conj"), 1, false, Expr_Expr, BuiltinProcPkg_builtin}, {STR_LIT("conj"), 1, false, Expr_Expr, BuiltinProcPkg_builtin},
{STR_LIT("expand_values"), 1, false, Expr_Expr, BuiltinProcPkg_builtin}, {STR_LIT("expand_values"), 1, false, Expr_Expr, BuiltinProcPkg_builtin},
{STR_LIT("compress_values"), 1, true, Expr_Expr, BuiltinProcPkg_builtin},
{STR_LIT("min"), 1, true, Expr_Expr, BuiltinProcPkg_builtin}, {STR_LIT("min"), 1, true, Expr_Expr, BuiltinProcPkg_builtin},
{STR_LIT("max"), 1, true, Expr_Expr, BuiltinProcPkg_builtin}, {STR_LIT("max"), 1, true, Expr_Expr, BuiltinProcPkg_builtin},
@@ -582,6 +588,9 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
{STR_LIT("type_convert_variants_to_pointers"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_convert_variants_to_pointers"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_merge"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_merge"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_integer_to_unsigned"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_integer_to_signed"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
{STR_LIT("type_is_boolean"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_boolean"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_is_integer"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_integer"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
@@ -686,6 +695,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
{STR_LIT("objc_find_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("objc_find_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("objc_register_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("objc_register_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
{STR_LIT("objc_register_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("objc_register_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
{STR_LIT("objc_ivar_get"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
{STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+4
View File
@@ -235,6 +235,10 @@ struct Entity {
Type * type_parameter_specialization; Type * type_parameter_specialization;
String ir_mangled_name; String ir_mangled_name;
bool is_type_alias; bool is_type_alias;
bool objc_is_implementation;
Type* objc_superclass;
Type* objc_ivar;
Entity*objc_context_provider;
String objc_class_name; String objc_class_name;
TypeNameObjCMetadata *objc_metadata; TypeNameObjCMetadata *objc_metadata;
} TypeName; } TypeName;
+14 -1
View File
@@ -769,7 +769,17 @@ try_cross_linking:;
gbString platform_lib_str = gb_string_make(heap_allocator(), ""); gbString platform_lib_str = gb_string_make(heap_allocator(), "");
defer (gb_string_free(platform_lib_str)); defer (gb_string_free(platform_lib_str));
if (build_context.metrics.os == TargetOs_darwin) { if (build_context.metrics.os == TargetOs_darwin) {
platform_lib_str = gb_string_appendc(platform_lib_str, "-Wl,-syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -L/usr/local/lib "); // Get the MacOSX SDK path.
gbString darwin_sdk_path = gb_string_make(temporary_allocator(), "");
if (!system_exec_command_line_app_output("xcrun --sdk macosx --show-sdk-path", &darwin_sdk_path)) {
darwin_sdk_path = gb_string_set(darwin_sdk_path, "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk");
} else {
// Trim the trailing newline.
darwin_sdk_path = gb_string_trim_space(darwin_sdk_path);
}
platform_lib_str = gb_string_append_fmt(platform_lib_str, "--sysroot %s ", darwin_sdk_path);
platform_lib_str = gb_string_appendc(platform_lib_str, "-L/usr/local/lib ");
// Homebrew's default library path, checking if it exists to avoid linking warnings. // Homebrew's default library path, checking if it exists to avoid linking warnings.
if (gb_file_exists("/opt/homebrew/lib")) { if (gb_file_exists("/opt/homebrew/lib")) {
@@ -791,6 +801,9 @@ try_cross_linking:;
// This points the linker to where the entry point is // This points the linker to where the entry point is
link_settings = gb_string_appendc(link_settings, "-e _main "); link_settings = gb_string_appendc(link_settings, "-e _main ");
} }
} else if (build_context.metrics.os == TargetOs_freebsd) {
// FreeBSD pkg installs third-party shared libraries in /usr/local/lib.
platform_lib_str = gb_string_appendc(platform_lib_str, "-Wl,-L/usr/local/lib ");
} else if (build_context.metrics.os == TargetOs_openbsd) { } else if (build_context.metrics.os == TargetOs_openbsd) {
// OpenBSD ports install shared libraries in /usr/local/lib. Also, we must explicitly link libpthread. // OpenBSD ports install shared libraries in /usr/local/lib. Also, we must explicitly link libpthread.
platform_lib_str = gb_string_appendc(platform_lib_str, "-lpthread -Wl,-L/usr/local/lib "); platform_lib_str = gb_string_appendc(platform_lib_str, "-lpthread -Wl,-L/usr/local/lib ");
+14 -28
View File
@@ -977,7 +977,7 @@ namespace lbAbiAmd64SysV {
return types[0]; return types[0];
} }
return LLVMStructTypeInContext(c, types.data, cast(unsigned)types.count, sz == 0); return LLVMStructTypeInContext(c, types.data, cast(unsigned)types.count, false);
} }
gb_internal void classify_with(LLVMTypeRef t, Array<RegClass> *cls, i64 ix, i64 off) { gb_internal void classify_with(LLVMTypeRef t, Array<RegClass> *cls, i64 ix, i64 off) {
@@ -1231,38 +1231,24 @@ namespace lbAbiArm64 {
} }
} else { } else {
i64 size = lb_sizeof(return_type); i64 size = lb_sizeof(return_type);
if (size <= 16) { if (size > 16) {
LLVMTypeRef cast_type = nullptr;
if (size == 0) {
cast_type = LLVMStructTypeInContext(c, nullptr, 0, false);
} else if (size <= 8) {
cast_type = LLVMIntTypeInContext(c, cast(unsigned)(size*8));
} else {
unsigned count = cast(unsigned)((size+7)/8);
LLVMTypeRef llvm_i64 = LLVMIntTypeInContext(c, 64);
LLVMTypeRef *types = gb_alloc_array(temporary_allocator(), LLVMTypeRef, count);
i64 size_copy = size;
for (unsigned i = 0; i < count; i++) {
if (size_copy >= 8) {
types[i] = llvm_i64;
} else {
types[i] = LLVMIntTypeInContext(c, 8*cast(unsigned)size_copy);
}
size_copy -= 8;
}
GB_ASSERT(size_copy <= 0);
cast_type = LLVMStructTypeInContext(c, types, count, true);
}
return lb_arg_type_direct(return_type, cast_type, nullptr, nullptr);
} else {
LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO(); LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO();
LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type); LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type);
return lb_arg_type_indirect(return_type, attr); return lb_arg_type_indirect(return_type, attr);
} }
GB_ASSERT(size <= 16);
LLVMTypeRef cast_type = nullptr;
if (size == 0) {
cast_type = LLVMStructTypeInContext(c, nullptr, 0, false);
} else if (size <= 8) {
cast_type = LLVMIntTypeInContext(c, cast(unsigned)(size*8));
} else {
LLVMTypeRef llvm_i64 = LLVMIntTypeInContext(c, 64);
cast_type = llvm_array_type(llvm_i64, 2);
}
return lb_arg_type_direct(return_type, cast_type, nullptr, nullptr);
} }
} }
+667 -45
View File
@@ -1173,6 +1173,344 @@ gb_internal lbProcedure *lb_create_objc_names(lbModule *main_module) {
return p; return p;
} }
String lb_get_objc_type_encoding(Type *t, isize pointer_depth = 0) {
// NOTE(harold): See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100
// NOTE(harold): Darwin targets are always 64-bit. Should we drop this and assume "q" always?
#define INT_SIZE_ENCODING (build_context.metrics.int_size == 4 ? "i" : "q")
switch (t->kind) {
case Type_Basic: {
switch (t->Basic.kind) {
case Basic_Invalid:
return str_lit("?");
case Basic_llvm_bool:
case Basic_bool:
case Basic_b8:
return str_lit("B");
case Basic_b16:
return str_lit("C");
case Basic_b32:
return str_lit("I");
case Basic_b64:
return str_lit("q");
case Basic_i8:
return str_lit("c");
case Basic_u8:
return str_lit("C");
case Basic_i16:
case Basic_i16le:
case Basic_i16be:
return str_lit("s");
case Basic_u16:
case Basic_u16le:
case Basic_u16be:
return str_lit("S");
case Basic_i32:
case Basic_i32le:
case Basic_i32be:
return str_lit("i");
case Basic_u32le:
case Basic_u32:
case Basic_u32be:
return str_lit("I");
case Basic_i64:
case Basic_i64le:
case Basic_i64be:
return str_lit("q");
case Basic_u64:
case Basic_u64le:
case Basic_u64be:
return str_lit("Q");
case Basic_i128:
case Basic_i128le:
case Basic_i128be:
return str_lit("t");
case Basic_u128:
case Basic_u128le:
case Basic_u128be:
return str_lit("T");
case Basic_rune:
return str_lit("I");
case Basic_f16:
case Basic_f16le:
case Basic_f16be:
return str_lit("s"); // @harold: Closest we've got?
case Basic_f32:
case Basic_f32le:
case Basic_f32be:
return str_lit("f");
case Basic_f64:
case Basic_f64le:
case Basic_f64be:
return str_lit("d");
case Basic_complex32: return str_lit("{complex32=ss}"); // No f16 encoding, so fallback to i16, as above in Basic_f16*
case Basic_complex64: return str_lit("{complex64=ff}");
case Basic_complex128: return str_lit("{complex128=dd}");
case Basic_quaternion64: return str_lit("{quaternion64=ssss}");
case Basic_quaternion128: return str_lit("{quaternion128=ffff}");
case Basic_quaternion256: return str_lit("{quaternion256=dddd}");
case Basic_int:
return str_lit(INT_SIZE_ENCODING);
case Basic_uint:
return build_context.metrics.int_size == 4 ? str_lit("I") : str_lit("Q");
case Basic_uintptr:
case Basic_rawptr:
return str_lit("^v");
case Basic_string:
return build_context.metrics.int_size == 4 ? str_lit("{string=*i}") : str_lit("{string=*q}");
case Basic_cstring: return str_lit("*");
case Basic_any: return str_lit("{any=^v^v}"); // rawptr + ^Type_Info
case Basic_typeid:
GB_ASSERT(t->Basic.size == 8);
return str_lit("q");
// Untyped types
case Basic_UntypedBool:
case Basic_UntypedInteger:
case Basic_UntypedFloat:
case Basic_UntypedComplex:
case Basic_UntypedQuaternion:
case Basic_UntypedString:
case Basic_UntypedRune:
case Basic_UntypedNil:
case Basic_UntypedUninit:
GB_PANIC("Untyped types cannot be @encoded()");
return str_lit("?");
}
break;
}
case Type_Named:
case Type_Struct:
case Type_Union: {
Type* base = t;
if (base->kind == Type_Named) {
base = base_type(base);
if(base->kind != Type_Struct && base->kind != Type_Union) {
return lb_get_objc_type_encoding(base, pointer_depth);
}
}
const bool is_union = base->kind == Type_Union;
if (!is_union) {
// Check for objc_SEL
if (internal_check_is_assignable_to(base, t_objc_SEL)) {
return str_lit(":");
}
// Check for objc_Class
if (internal_check_is_assignable_to(base, t_objc_SEL)) {
return str_lit("#");
}
// Treat struct as an Objective-C Class?
if (has_type_got_objc_class_attribute(base) && pointer_depth == 0) {
return str_lit("#");
}
}
if (is_type_objc_object(base)) {
return str_lit("@");
}
gbString s = gb_string_make_reserve(temporary_allocator(), 16);
s = gb_string_append_length(s, is_union ? "(" :"{", 1);
if (t->kind == Type_Named) {
s = gb_string_append_length(s, t->Named.name.text, t->Named.name.len);
}
// Write fields
if (pointer_depth < 2) {
s = gb_string_append_length(s, "=", 1);
if (!is_union) {
for( auto& f : base->Struct.fields ) {
String field_type = lb_get_objc_type_encoding(f->type, pointer_depth);
s = gb_string_append_length(s, field_type.text, field_type.len);
}
} else {
for( auto& v : base->Union.variants ) {
String variant_type = lb_get_objc_type_encoding(v, pointer_depth);
s = gb_string_append_length(s, variant_type.text, variant_type.len);
}
}
}
s = gb_string_append_length(s, is_union ? ")" :"}", 1);
return make_string_c(s);
}
case Type_Generic:
GB_PANIC("Generic types cannot be @encoded()");
return str_lit("?");
case Type_Pointer: {
String pointee = lb_get_objc_type_encoding(t->Pointer.elem, pointer_depth +1);
// Special case for Objective-C Objects
if (pointer_depth == 0 && pointee == "@") {
return pointee;
}
return concatenate_strings(temporary_allocator(), str_lit("^"), pointee);
}
case Type_MultiPointer:
return concatenate_strings(temporary_allocator(), str_lit("^"), lb_get_objc_type_encoding(t->Pointer.elem, pointer_depth +1));
case Type_Array: {
String type_str = lb_get_objc_type_encoding(t->Array.elem, pointer_depth);
gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 8);
s = gb_string_append_fmt(s, "[%lld%.*s]", t->Array.count, LIT(type_str));
return make_string_c(s);
}
case Type_EnumeratedArray: {
String type_str = lb_get_objc_type_encoding(t->EnumeratedArray.elem, pointer_depth);
gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 8);
s = gb_string_append_fmt(s, "[%lld%.*s]", t->EnumeratedArray.count, LIT(type_str));
return make_string_c(s);
}
case Type_Slice: {
String type_str = lb_get_objc_type_encoding(t->Slice.elem, pointer_depth);
gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 8);
s = gb_string_append_fmt(s, "{slice=^%.*s%s}", LIT(type_str), INT_SIZE_ENCODING);
return make_string_c(s);
}
case Type_DynamicArray: {
String type_str = lb_get_objc_type_encoding(t->DynamicArray.elem, pointer_depth);
gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 8);
s = gb_string_append_fmt(s, "{dynamic=^%.*s%s%sAllocator={?^v}}", LIT(type_str), INT_SIZE_ENCODING, INT_SIZE_ENCODING);
return make_string_c(s);
}
case Type_Map:
return str_lit("{^v^v{Allocator=?^v}}");
case Type_Enum:
return lb_get_objc_type_encoding(t->Enum.base_type, pointer_depth);
case Type_Tuple:
// NOTE(harold): Is this type allowed here?
return str_lit("?");
case Type_Proc:
return str_lit("?");
case Type_BitSet:
return lb_get_objc_type_encoding(t->BitSet.underlying, pointer_depth);
case Type_SimdVector: {
String type_str = lb_get_objc_type_encoding(t->SimdVector.elem, pointer_depth);
gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 5);
gb_string_append_fmt(s, "[%lld%.*s]", t->SimdVector.count, LIT(type_str));
return make_string_c(s);
}
case Type_Matrix: {
String type_str = lb_get_objc_type_encoding(t->Matrix.elem, pointer_depth);
gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 5);
i64 element_count = t->Matrix.column_count * t->Matrix.row_count;
gb_string_append_fmt(s, "[%lld%.*s]", element_count, LIT(type_str));
return make_string_c(s);
}
case Type_BitField:
return lb_get_objc_type_encoding(t->BitField.backing_type, pointer_depth);
case Type_SoaPointer: {
gbString s = gb_string_make_reserve(temporary_allocator(), 8);
s = gb_string_append_fmt(s, "{=^v%s}", INT_SIZE_ENCODING);
return make_string_c(s);
}
} // End switch t->kind
#undef INT_SIZE_ENCODING
GB_PANIC("Unreachable");
return str_lit("");
}
struct lbObjCGlobalClass {
lbObjCGlobal g;
lbValue class_value; // Local registered class value
};
gb_internal void lb_register_objc_thing(
StringSet &handled,
lbModule *m,
Array<lbValue> &args,
Array<lbObjCGlobalClass> &class_impls,
StringMap<lbObjCGlobalClass> &class_map,
lbProcedure *p,
lbObjCGlobal const &g,
char const *call
) {
if (string_set_update(&handled, g.name)) {
return;
}
lbAddr addr = {};
lbValue *found = string_map_get(&m->members, g.global_name);
if (found) {
addr = lb_addr(*found);
} else {
lbValue v = {};
LLVMTypeRef t = lb_type(m, g.type);
v.value = LLVMAddGlobal(m->mod, t, g.global_name);
v.type = alloc_type_pointer(g.type);
addr = lb_addr(v);
LLVMSetInitializer(v.value, LLVMConstNull(t));
}
lbValue class_ptr = {};
lbValue class_name = lb_const_value(m, t_cstring, exact_value_string(g.name));
// If this class requires an implementation, save it for registration below.
if (g.class_impl_type != nullptr) {
// Make sure the superclass has been initialized before us
lbValue superclass_value = lb_const_nil(m, t_objc_Class);
auto &tn = g.class_impl_type->Named.type_name->TypeName;
Type *superclass = tn.objc_superclass;
if (superclass != nullptr) {
auto& superclass_global = string_map_must_get(&class_map, superclass->Named.type_name->TypeName.objc_class_name);
lb_register_objc_thing(handled, m, args, class_impls, class_map, p, superclass_global.g, call);
GB_ASSERT(superclass_global.class_value.value);
superclass_value = superclass_global.class_value;
}
args.count = 3;
args[0] = superclass_value;
args[1] = class_name;
args[2] = lb_const_int(m, t_uint, 0);
class_ptr = lb_emit_runtime_call(p, "objc_allocateClassPair", args);
array_add(&class_impls, lbObjCGlobalClass{g, class_ptr});
}
else {
args.count = 1;
args[0] = class_name;
class_ptr = lb_emit_runtime_call(p, call, args);
}
lb_addr_store(p, addr, class_ptr);
lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name);
if (class_global != nullptr) {
class_global->class_value = class_ptr;
}
}
gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) { gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
if (p == nullptr) { if (p == nullptr) {
return; return;
@@ -1186,38 +1524,329 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
string_set_init(&handled); string_set_init(&handled);
defer (string_set_destroy(&handled)); defer (string_set_destroy(&handled));
auto args = array_make<lbValue>(temporary_allocator(), 1); auto args = array_make<lbValue>(temporary_allocator(), 3, 8);
auto class_impls = array_make<lbObjCGlobalClass>(temporary_allocator(), 0, 16);
// Register all class implementations unconditionally, even if not statically referenced
for (Entity *e = {}; mpsc_dequeue(&gen->info->objc_class_implementations, &e); /**/) {
GB_ASSERT(e->kind == Entity_TypeName && e->TypeName.objc_is_implementation);
lb_handle_objc_find_or_register_class(p, e->TypeName.objc_class_name, e->type);
}
// Ensure classes that have been implicitly referenced through
// the objc_superclass attribute have a global variable available for them.
TypeSet class_set{};
type_set_init(&class_set, gen->objc_classes.count+16);
defer (type_set_destroy(&class_set));
auto referenced_classes = array_make<lbObjCGlobal>(temporary_allocator());
for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) {
array_add(&referenced_classes, g);
Type *cls = g.class_impl_type;
while (cls) {
if (type_set_update(&class_set, cls)) {
break;
}
GB_ASSERT(cls->kind == Type_Named);
cls = cls->Named.type_name->TypeName.objc_superclass;
}
}
for (auto pair : class_set) {
auto& tn = pair.type->Named.type_name->TypeName;
Type *class_impl = !tn.objc_is_implementation ? nullptr : pair.type;
lb_handle_objc_find_or_register_class(p, tn.objc_class_name, class_impl);
}
for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) {
array_add( &referenced_classes, g );
}
// Add all class globals to a map so that we can look them up dynamically
// in order to resolve out-of-order because classes that are being implemented
// require their superclasses to be registered before them.
StringMap<lbObjCGlobalClass> global_class_map{};
string_map_init(&global_class_map, (usize)gen->objc_classes.count);
defer (string_map_destroy(&global_class_map));
for (lbObjCGlobal g :referenced_classes) {
string_map_set(&global_class_map, g.name, lbObjCGlobalClass{g});
}
LLVMSetLinkage(p->value, LLVMInternalLinkage); LLVMSetLinkage(p->value, LLVMInternalLinkage);
lb_begin_procedure_body(p); lb_begin_procedure_body(p);
auto register_thing = [&handled, &m, &args](lbProcedure *p, lbObjCGlobal const &g, char const *call) { // Register class globals, gathering classes that must be implemented
if (!string_set_update(&handled, g.name)) { for (auto& kv : global_class_map) {
lbAddr addr = {}; lb_register_objc_thing(handled, m, args, class_impls, global_class_map, p, kv.value.g, "objc_lookUpClass");
lbValue *found = string_map_get(&m->members, g.global_name);
if (found) {
addr = lb_addr(*found);
} else {
lbValue v = {};
LLVMTypeRef t = lb_type(m, g.type);
v.value = LLVMAddGlobal(m->mod, t, g.global_name);
v.type = alloc_type_pointer(g.type);
addr = lb_addr(v);
LLVMSetInitializer(v.value, LLVMConstNull(t));
}
args[0] = lb_const_value(m, t_cstring, exact_value_string(g.name));
lbValue ptr = lb_emit_runtime_call(p, call, args);
lb_addr_store(p, addr, ptr);
}
};
for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) {
register_thing(p, g, "objc_lookUpClass");
} }
// Prefetch selectors for implemented methods so that they can also be registered.
for (const auto& cd : class_impls) {
auto& g = cd.g;
Type *class_type = g.class_impl_type;
Array<ObjcMethodData>* methods = map_get(&m->info->objc_method_implementations, class_type);
if (!methods) {
continue;
}
for (const ObjcMethodData& md : *methods) {
lb_handle_objc_find_or_register_selector(p, md.ac.objc_selector);
}
}
// Now we can register all referenced selectors
for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_selectors, &g); /**/) { for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_selectors, &g); /**/) {
register_thing(p, g, "sel_registerName"); lb_register_objc_thing(handled, m, args, class_impls, global_class_map, p, g, "sel_registerName");
}
// Emit method wrapper implementations and registration
auto wrapper_args = array_make<Type *>(temporary_allocator(), 2, 8);
auto get_context_args = array_make<lbValue>(temporary_allocator(), 1);
PtrMap<Type *, lbObjCGlobal> ivar_map{};
map_init(&ivar_map, gen->objc_ivars.count);
for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_ivars, &g); /**/) {
map_set(&ivar_map, g.class_impl_type, g);
}
for (const auto &cd : class_impls) {
auto &g = cd.g;
Type *class_type = g.class_impl_type;
Type *class_ptr_type = alloc_type_pointer(class_type);
lbValue class_value = cd.class_value;
Type *ivar_type = class_type->Named.type_name->TypeName.objc_ivar;
Entity *context_provider = class_type->Named.type_name->TypeName.objc_context_provider;
Type *contex_provider_self_ptr_type = nullptr;
Type *contex_provider_self_named_type = nullptr;
bool is_context_provider_ivar = false;
lbValue context_provider_proc_value{};
if (context_provider) {
context_provider_proc_value = lb_find_procedure_value_from_entity(m, context_provider);
contex_provider_self_ptr_type = base_type(context_provider->type->Proc.params->Tuple.variables[0]->type);
GB_ASSERT(contex_provider_self_ptr_type->kind == Type_Pointer);
contex_provider_self_named_type = base_named_type(type_deref(contex_provider_self_ptr_type));
is_context_provider_ivar = ivar_type != nullptr && internal_check_is_assignable_to(contex_provider_self_named_type, ivar_type);
}
Array<ObjcMethodData> *methods = map_get(&m->info->objc_method_implementations, class_type);
if (!methods) {
continue;
}
for (const ObjcMethodData &md : *methods) {
GB_ASSERT( md.proc_entity->kind == Entity_Procedure);
Type *method_type = md.proc_entity->type;
String proc_name = make_string_c("__$objc_method::");
proc_name = concatenate_strings(temporary_allocator(), proc_name, g.name);
proc_name = concatenate_strings(temporary_allocator(), proc_name, str_lit("::"));
proc_name = concatenate_strings( permanent_allocator(), proc_name, md.ac.objc_name);
wrapper_args.count = 2;
wrapper_args[0] = md.ac.objc_is_class_method ? t_objc_Class : class_ptr_type;
wrapper_args[1] = t_objc_SEL;
isize method_param_count = method_type->Proc.param_count;
isize method_param_offset = 0;
if (!md.ac.objc_is_class_method) {
GB_ASSERT(method_param_count >= 1);
method_param_count -= 1;
method_param_offset = 1;
}
for (isize i = 0; i < method_param_count; i++) {
array_add(&wrapper_args, method_type->Proc.params->Tuple.variables[method_param_offset+i]->type);
}
Type *wrapper_args_tuple = alloc_type_tuple_from_field_types(wrapper_args.data, wrapper_args.count, false, true);
Type *wrapper_results_tuple = nullptr;
if (method_type->Proc.result_count > 0) {
GB_ASSERT(method_type->Proc.result_count == 1);
wrapper_results_tuple = alloc_type_tuple_from_field_types(&method_type->Proc.results->Tuple.variables[0]->type, 1, false, true);
}
Type *wrapper_proc_type = alloc_type_proc(nullptr, wrapper_args_tuple, wrapper_args_tuple->Tuple.variables.count,
wrapper_results_tuple, method_type->Proc.result_count, false, ProcCC_CDecl);
lbProcedure *wrapper_proc = lb_create_dummy_procedure(m, proc_name, wrapper_proc_type);
lb_add_attribute_to_proc(wrapper_proc->module, wrapper_proc->value, "nounwind");
// Emit the wrapper
LLVMSetLinkage(wrapper_proc->value, LLVMExternalLinkage);
lb_begin_procedure_body(wrapper_proc);
{
if (method_type->Proc.calling_convention == ProcCC_Odin) {
GB_ASSERT(context_provider);
// Emit the get odin context call
get_context_args[0] = lbValue {
wrapper_proc->raw_input_parameters[0],
contex_provider_self_ptr_type,
};
if (is_context_provider_ivar) {
// The context provider takes the ivar's type.
// Emit an objc_ivar_get call and use that pointer for 'self' instead.
lbValue real_self {
wrapper_proc->raw_input_parameters[0],
class_ptr_type
};
get_context_args[0] = lb_handle_objc_ivar_for_objc_object_pointer(wrapper_proc, real_self);
}
lbValue context = lb_emit_call(wrapper_proc, context_provider_proc_value, get_context_args);
lbAddr context_addr = lb_addr(lb_address_from_load_or_generate_local(wrapper_proc, context));
lb_push_context_onto_stack(wrapper_proc, context_addr);
}
auto method_call_args = array_make<lbValue>(temporary_allocator(), method_param_count + method_param_offset);
if (!md.ac.objc_is_class_method) {
method_call_args[0] = lbValue {
wrapper_proc->raw_input_parameters[0],
class_ptr_type,
};
}
for (isize i = 0; i < method_param_count; i++) {
method_call_args[i+method_param_offset] = lbValue {
wrapper_proc->raw_input_parameters[i+2],
method_type->Proc.params->Tuple.variables[i+method_param_offset]->type,
};
}
lbValue method_proc_value = lb_find_procedure_value_from_entity(m, md.proc_entity);
// Call real procedure for method from here, passing the parameters expected, if any.
lbValue return_value = lb_emit_call(wrapper_proc, method_proc_value, method_call_args);
if (wrapper_results_tuple != nullptr) {
auto &result_var = method_type->Proc.results->Tuple.variables[0];
return_value = lb_emit_conv(wrapper_proc, return_value, result_var->type);
lb_build_return_stmt_internal(wrapper_proc, return_value, result_var->token.pos);
}
}
lb_end_procedure_body(wrapper_proc);
// Add the method to the class
String method_encoding = str_lit("v");
// TODO (harold): Checker must ensure that objc_methods have a single return value or none!
GB_ASSERT(method_type->Proc.result_count <= 1);
if (method_type->Proc.result_count != 0) {
method_encoding = lb_get_objc_type_encoding(method_type->Proc.results->Tuple.variables[0]->type);
}
if (!md.ac.objc_is_class_method) {
method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("@:"));
} else {
method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("#:"));
}
for (isize i = method_param_offset; i < method_param_count; i++) {
Type *param_type = method_type->Proc.params->Tuple.variables[i]->type;
String param_encoding = lb_get_objc_type_encoding(param_type);
method_encoding = concatenate_strings(temporary_allocator(), method_encoding, param_encoding);
}
// Emit method registration
lbAddr* sel_address = string_map_get(&m->objc_selectors, md.ac.objc_selector);
GB_ASSERT(sel_address);
lbValue selector_value = lb_addr_load(p, *sel_address);
args.count = 4;
args[0] = class_value; // Class
args[1] = selector_value; // SEL
args[2] = lbValue { wrapper_proc->value, wrapper_proc->type };
args[3] = lb_const_value(m, t_cstring, exact_value_string(method_encoding));
// TODO(harold): Emit check BOOL result and panic if false.
lb_emit_runtime_call(p, "class_addMethod", args);
} // End methods
// Add ivar if we have one
if (ivar_type != nullptr) {
// Register a single ivar for this class
Type *ivar_base = ivar_type->Named.base;
// @note(harold): The alignment is supposed to be passed as log2(alignment): https://developer.apple.com/documentation/objectivec/class_addivar(_:_:_:_:_:)?language=objc
const i64 size = type_size_of(ivar_base);
const i64 alignment = (i64)floor_log2((u64)type_align_of(ivar_base));
// NOTE(harold): I've opted to not emit the type encoding for ivars in order to keep the data private.
// If there is desire in the future to emit the type encoding for introspection through the Obj-C runtime,
// then perhaps an option can be added for it then.
// Should we pass the actual type encoding? Might not be ideal for obfuscation.
String ivar_name = str_lit("__$ivar");
String ivar_types = str_lit("{= }"); //lb_get_objc_type_encoding(ivar_type);
args.count = 5;
args[0] = class_value;
args[1] = lb_const_value(m, t_cstring, exact_value_string(ivar_name));
args[2] = lb_const_value(m, t_uint, exact_value_u64((u64)size));
args[3] = lb_const_value(m, t_u8, exact_value_u64((u64)alignment));
args[4] = lb_const_value(m, t_cstring, exact_value_string(ivar_types));
lb_emit_runtime_call(p, "class_addIvar", args);
}
// Complete the class registration
args.count = 1;
args[0] = class_value;
lb_emit_runtime_call(p, "objc_registerClassPair", args);
}
// Register ivar offsets for any `objc_ivar_get` expressions emitted.
for (auto const& kv : ivar_map) {
lbObjCGlobal const& g = kv.value;
lbAddr ivar_addr = {};
lbValue *found = string_map_get(&m->members, g.global_name);
if (found) {
ivar_addr = lb_addr(*found);
GB_ASSERT(ivar_addr.addr.type == t_int_ptr);
} else {
// Defined in an external package, define it now in the main package
LLVMTypeRef t = lb_type(m, t_int);
lbValue global{};
global.value = LLVMAddGlobal(m->mod, t, g.global_name);
global.type = t_int_ptr;
LLVMSetInitializer(global.value, LLVMConstInt(t, 0, true));
ivar_addr = lb_addr(global);
}
String class_name = g.class_impl_type->Named.type_name->TypeName.objc_class_name;
lbValue class_value = string_map_must_get(&global_class_map, class_name).class_value;
args.count = 2;
args[0] = class_value;
args[1] = lb_const_value(m, t_cstring, exact_value_string(str_lit("__$ivar")));
lbValue ivar = lb_emit_runtime_call(p, "class_getInstanceVariable", args);
args.count = 1;
args[0] = ivar;
lbValue ivar_offset = lb_emit_runtime_call(p, "ivar_getOffset", args);
lbValue ivar_offset_int = lb_emit_conv(p, ivar_offset, t_int);
lb_addr_store(p, ivar_addr, ivar_offset_int);
} }
lb_end_procedure_body(p); lb_end_procedure_body(p);
@@ -1278,6 +1907,10 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc
lb_add_attribute_to_proc(p->module, p->value, "optnone"); lb_add_attribute_to_proc(p->module, p->value, "optnone");
lb_add_attribute_to_proc(p->module, p->value, "noinline"); lb_add_attribute_to_proc(p->module, p->value, "noinline");
// Make sure shared libraries call their own runtime startup on Linux.
LLVMSetVisibility(p->value, LLVMHiddenVisibility);
LLVMSetLinkage(p->value, LLVMWeakAnyLinkage);
lb_begin_procedure_body(p); lb_begin_procedure_body(p);
lb_setup_type_info_data(main_module); lb_setup_type_info_data(main_module);
@@ -1344,14 +1977,14 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc
gbString var_name = gb_string_make(permanent_allocator(), "__$global_any::"); gbString var_name = gb_string_make(permanent_allocator(), "__$global_any::");
gbString e_str = string_canonical_entity_name(temporary_allocator(), e); gbString e_str = string_canonical_entity_name(temporary_allocator(), e);
var_name = gb_string_append_length(var_name, e_str, gb_strlen(e_str)); var_name = gb_string_append_length(var_name, e_str, gb_strlen(e_str));
lbAddr g = lb_add_global_generated_with_name(main_module, var_type, var.init, make_string_c(var_name)); lbAddr g = lb_add_global_generated_with_name(main_module, var_type, {}, make_string_c(var_name));
lb_addr_store(p, g, var.init); lb_addr_store(p, g, var.init);
lbValue gp = lb_addr_get_ptr(p, g); lbValue gp = lb_addr_get_ptr(p, g);
lbValue data = lb_emit_struct_ep(p, var.var, 0); lbValue data = lb_emit_struct_ep(p, var.var, 0);
lbValue ti = lb_emit_struct_ep(p, var.var, 1); lbValue ti = lb_emit_struct_ep(p, var.var, 1);
lb_emit_store(p, data, lb_emit_conv(p, gp, t_rawptr)); lb_emit_store(p, data, lb_emit_conv(p, gp, t_rawptr));
lb_emit_store(p, ti, lb_type_info(p, var_type)); lb_emit_store(p, ti, lb_typeid(p->module, var_type));
} else { } else {
LLVMTypeRef vt = llvm_addr_type(p->module, var.var); LLVMTypeRef vt = llvm_addr_type(p->module, var.var);
lbValue src0 = lb_emit_conv(p, var.init, t); lbValue src0 = lb_emit_conv(p, var.init, t);
@@ -1387,6 +2020,10 @@ gb_internal lbProcedure *lb_create_cleanup_runtime(lbModule *main_module) { // C
lb_add_attribute_to_proc(p->module, p->value, "optnone"); lb_add_attribute_to_proc(p->module, p->value, "optnone");
lb_add_attribute_to_proc(p->module, p->value, "noinline"); lb_add_attribute_to_proc(p->module, p->value, "noinline");
// Make sure shared libraries call their own runtime cleanup on Linux.
LLVMSetVisibility(p->value, LLVMHiddenVisibility);
LLVMSetLinkage(p->value, LLVMWeakAnyLinkage);
lb_begin_procedure_body(p); lb_begin_procedure_body(p);
CheckerInfo *info = main_module->gen->info; CheckerInfo *info = main_module->gen->info;
@@ -2185,7 +2822,6 @@ gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) {
p->is_done = true; p->is_done = true;
m->curr_procedure = nullptr; m->curr_procedure = nullptr;
} }
lb_end_procedure(p);
// Add Flags // Add Flags
if (p->entity && p->entity->kind == Entity_Procedure && p->entity->Procedure.is_memcpy_like) { if (p->entity && p->entity->kind == Entity_Procedure && p->entity->Procedure.is_memcpy_like) {
@@ -2494,6 +3130,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
LLVMSetInitializer(g, LLVMConstNull(lb_type(m, t))); LLVMSetInitializer(g, LLVMConstNull(lb_type(m, t)));
LLVMSetLinkage(g, LLVMInternalLinkage); LLVMSetLinkage(g, LLVMInternalLinkage);
lb_make_global_private_const(g); lb_make_global_private_const(g);
lb_set_odin_rtti_section(g);
return lb_addr({g, alloc_type_pointer(t)}); return lb_addr({g, alloc_type_pointer(t)});
}; };
@@ -2565,24 +3202,9 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
lbValue g = {}; lbValue g = {};
g.value = LLVMAddGlobal(m->mod, lb_type(m, e->type), alloc_cstring(permanent_allocator(), name)); g.value = LLVMAddGlobal(m->mod, lb_type(m, e->type), alloc_cstring(permanent_allocator(), name));
g.type = alloc_type_pointer(e->type); g.type = alloc_type_pointer(e->type);
if (e->Variable.thread_local_model != "") {
LLVMSetThreadLocal(g.value, true);
String m = e->Variable.thread_local_model; lb_apply_thread_local_model(g.value, e->Variable.thread_local_model);
LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel;
if (m == "default") {
mode = LLVMGeneralDynamicTLSModel;
} else if (m == "localdynamic") {
mode = LLVMLocalDynamicTLSModel;
} else if (m == "initialexec") {
mode = LLVMInitialExecTLSModel;
} else if (m == "localexec") {
mode = LLVMLocalExecTLSModel;
} else {
GB_PANIC("Unhandled thread local mode %.*s", LIT(m));
}
LLVMSetThreadLocalMode(g.value, mode);
}
if (is_foreign) { if (is_foreign) {
LLVMSetLinkage(g.value, LLVMExternalLinkage); LLVMSetLinkage(g.value, LLVMExternalLinkage);
LLVMSetDLLStorageClass(g.value, LLVMDLLImportStorageClass); LLVMSetDLLStorageClass(g.value, LLVMDLLImportStorageClass);
+3 -1
View File
@@ -196,6 +196,7 @@ struct lbModule {
StringMap<lbAddr> objc_classes; StringMap<lbAddr> objc_classes;
StringMap<lbAddr> objc_selectors; StringMap<lbAddr> objc_selectors;
StringMap<lbAddr> objc_ivars;
PtrMap<u64/*type hash*/, lbAddr> map_cell_info_map; // address of runtime.Map_Info PtrMap<u64/*type hash*/, lbAddr> map_cell_info_map; // address of runtime.Map_Info
PtrMap<u64/*type hash*/, lbAddr> map_info_map; // address of runtime.Map_Cell_Info PtrMap<u64/*type hash*/, lbAddr> map_info_map; // address of runtime.Map_Cell_Info
@@ -219,6 +220,7 @@ struct lbObjCGlobal {
gbString global_name; gbString global_name;
String name; String name;
Type * type; Type * type;
Type * class_impl_type; // This is set when the class has the objc_implement attribute set to true.
}; };
struct lbGenerator : LinkerData { struct lbGenerator : LinkerData {
@@ -240,6 +242,7 @@ struct lbGenerator : LinkerData {
MPSCQueue<lbEntityCorrection> entities_to_correct_linkage; MPSCQueue<lbEntityCorrection> entities_to_correct_linkage;
MPSCQueue<lbObjCGlobal> objc_selectors; MPSCQueue<lbObjCGlobal> objc_selectors;
MPSCQueue<lbObjCGlobal> objc_classes; MPSCQueue<lbObjCGlobal> objc_classes;
MPSCQueue<lbObjCGlobal> objc_ivars;
MPSCQueue<String> raddebug_section_strings; MPSCQueue<String> raddebug_section_strings;
}; };
@@ -410,7 +413,6 @@ gb_internal LLVMAttributeRef lb_create_enum_attribute_with_type(LLVMContextRef c
gb_internal void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name, u64 value); gb_internal void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name, u64 value);
gb_internal void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name); gb_internal void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name);
gb_internal lbProcedure *lb_create_procedure(lbModule *module, Entity *entity, bool ignore_body=false); gb_internal lbProcedure *lb_create_procedure(lbModule *module, Entity *entity, bool ignore_body=false);
gb_internal void lb_end_procedure(lbProcedure *p);
gb_internal LLVMTypeRef lb_type(lbModule *m, Type *type); gb_internal LLVMTypeRef lb_type(lbModule *m, Type *type);
+4 -1
View File
@@ -533,7 +533,10 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb
Entity *e = entity_from_expr(expr); Entity *e = entity_from_expr(expr);
res = lb_find_procedure_value_from_entity(m, e); res = lb_find_procedure_value_from_entity(m, e);
} }
GB_ASSERT(res.value != nullptr); if (res.value == nullptr) {
// This is an unspecialized polymorphic procedure, return nil or dummy value
return lb_const_nil(m, original_type);
}
GB_ASSERT(LLVMGetValueKind(res.value) == LLVMFunctionValueKind); GB_ASSERT(LLVMGetValueKind(res.value) == LLVMFunctionValueKind);
if (LLVMGetIntrinsicID(res.value) == 0) { if (LLVMGetIntrinsicID(res.value) == 0) {
+56
View File
@@ -1374,3 +1374,59 @@ gb_internal void lb_add_debug_info_for_global_constant_from_entity(lbGenerator *
} }
} }
} }
gb_internal void lb_add_debug_label(lbProcedure *p, Ast *label, lbBlock *target) {
// NOTE(tf2spi): LLVM-C DILabel API used only existed for major versions 20+
#if LLVM_VERSION_MAJOR >= 20
if (p == nullptr || p->debug_info == nullptr) {
return;
}
if (target == nullptr || label == nullptr || label->kind != Ast_Label) {
return;
}
Token label_token = label->Label.token;
if (is_blank_ident(label_token.string)) {
return;
}
lbModule *m = p->module;
if (m == nullptr) {
return;
}
AstFile *file = label->file();
LLVMMetadataRef llvm_file = lb_get_llvm_metadata(m, file);
if (llvm_file == nullptr) {
debugf("llvm file not found for label\n");
return;
}
LLVMMetadataRef llvm_scope = p->debug_info;
if(llvm_scope == nullptr) {
debugf("llvm scope not found for label\n");
return;
}
LLVMMetadataRef llvm_debug_loc = lb_debug_location_from_token_pos(p, label_token.pos);
LLVMBasicBlockRef llvm_block = target->block;
if (llvm_block == nullptr || llvm_debug_loc == nullptr) {
return;
}
LLVMMetadataRef llvm_label = LLVMDIBuilderCreateLabel(
m->debug_builder,
llvm_scope,
(const char *)label_token.string.text,
(size_t)label_token.string.len,
llvm_file,
label_token.pos.line,
// NOTE(tf2spi): Defaults to false in LLVM API, but I'd rather not take chances
// Always preserve the label no matter what when debugging
true
);
GB_ASSERT(llvm_label != nullptr);
(void)LLVMDIBuilderInsertLabelAtEnd(
m->debug_builder,
llvm_label,
llvm_debug_loc,
llvm_block
);
#endif
}
+17 -9
View File
@@ -4844,7 +4844,7 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) {
if (cl->elems.count == 0) { if (cl->elems.count == 0) {
break; break;
} }
GB_ASSERT(expr->file()->feature_flags & OptInFeatureFlag_DynamicLiterals); GB_ASSERT(expr->file()->feature_flags & OptInFeatureFlag_DynamicLiterals || build_context.dynamic_literals);
lbValue err = lb_dynamic_map_reserve(p, v.addr, 2*cl->elems.count, pos); lbValue err = lb_dynamic_map_reserve(p, v.addr, 2*cl->elems.count, pos);
gb_unused(err); gb_unused(err);
@@ -5154,8 +5154,6 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) {
return lb_build_addr(p, unparen_expr(se->selector)); return lb_build_addr(p, unparen_expr(se->selector));
} }
Type *type = base_type(tav.type);
if (tav.mode == Addressing_Type) { // Addressing_Type if (tav.mode == Addressing_Type) { // Addressing_Type
Selection sel = lookup_field(tav.type, selector, true); Selection sel = lookup_field(tav.type, selector, true);
if (sel.pseudo_field) { if (sel.pseudo_field) {
@@ -5190,18 +5188,29 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) {
return lb_addr_swizzle(a, type, swizzle_count, swizzle_indices); return lb_addr_swizzle(a, type, swizzle_count, swizzle_indices);
} }
Selection sel = lookup_field(type, selector, false); Selection sel = lookup_field(tav.type, selector, false);
GB_ASSERT(sel.entity != nullptr); GB_ASSERT(sel.entity != nullptr);
if (sel.pseudo_field) { if (sel.pseudo_field && (sel.entity->kind == Entity_Procedure || sel.entity->kind == Entity_ProcGroup)) {
GB_ASSERT(sel.entity->kind == Entity_Procedure || sel.entity->kind == Entity_ProcGroup);
Entity *e = entity_of_node(sel_node); Entity *e = entity_of_node(sel_node);
GB_ASSERT(e->kind == Entity_Procedure); GB_ASSERT(e->kind == Entity_Procedure);
return lb_addr(lb_find_value_from_entity(p->module, e)); return lb_addr(lb_find_value_from_entity(p->module, e));
} }
if (sel.is_bit_field) { lbAddr addr = lb_build_addr(p, se->expr);
lbAddr addr = lb_build_addr(p, se->expr);
// NOTE(harold): Only allow ivar pseudo field access on indirect selectors.
// It is incoherent otherwise as Objective-C objects are zero-sized.
Type *deref_type = type_deref(tav.type);
if (tav.type->kind == Type_Pointer && deref_type->kind == Type_Named && deref_type->Named.type_name->TypeName.objc_ivar) {
// NOTE(harold): We need to load the ivar from the current address and
// replace addr with the loaded ivar addr to apply the selector load properly.
addr = lb_addr(lb_emit_load(p, addr.addr));
lbValue ivar_ptr = lb_handle_objc_ivar_for_objc_object_pointer(p, addr.addr);
addr = lb_addr(ivar_ptr);
}
if (sel.is_bit_field) {
Selection sub_sel = sel; Selection sub_sel = sel;
sub_sel.index.count -= 1; sub_sel.index.count -= 1;
@@ -5227,7 +5236,6 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) {
} }
{ {
lbAddr addr = lb_build_addr(p, se->expr);
if (addr.kind == lbAddr_Map) { if (addr.kind == lbAddr_Map) {
lbValue v = lb_addr_load(p, addr); lbValue v = lb_addr_load(p, addr);
lbValue a = lb_address_from_load_or_generate_local(p, v); lbValue a = lb_address_from_load_or_generate_local(p, v);
+99 -24
View File
@@ -101,6 +101,7 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) {
string_map_init(&m->objc_classes); string_map_init(&m->objc_classes);
string_map_init(&m->objc_selectors); string_map_init(&m->objc_selectors);
string_map_init(&m->objc_ivars);
map_init(&m->map_info_map, 0); map_init(&m->map_info_map, 0);
map_init(&m->map_cell_info_map, 0); map_init(&m->map_cell_info_map, 0);
@@ -173,6 +174,7 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) {
mpsc_init(&gen->entities_to_correct_linkage, heap_allocator()); mpsc_init(&gen->entities_to_correct_linkage, heap_allocator());
mpsc_init(&gen->objc_selectors, heap_allocator()); mpsc_init(&gen->objc_selectors, heap_allocator());
mpsc_init(&gen->objc_classes, heap_allocator()); mpsc_init(&gen->objc_classes, heap_allocator());
mpsc_init(&gen->objc_ivars, heap_allocator());
mpsc_init(&gen->raddebug_section_strings, heap_allocator()); mpsc_init(&gen->raddebug_section_strings, heap_allocator());
return true; return true;
@@ -886,8 +888,8 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) {
Type *t = base_type(type_deref(addr.addr.type)); Type *t = base_type(type_deref(addr.addr.type));
GB_ASSERT(t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None); GB_ASSERT(t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None);
lbValue len = lb_soa_struct_len(p, addr.addr); lbValue len = lb_soa_struct_len(p, addr.addr);
if (addr.soa.index_expr != nullptr) { if (addr.soa.index_expr != nullptr && (!lb_is_const(addr.soa.index) || t->Struct.soa_kind != StructSoa_Fixed)) {
lb_emit_bounds_check(p, ast_token(addr.soa.index_expr), index, len); lb_emit_bounds_check(p, ast_token(addr.soa.index_expr), addr.soa.index, len);
} }
} }
@@ -2213,6 +2215,14 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) {
case Type_BitField: case Type_BitField:
return lb_type_internal(m, type->BitField.backing_type); return lb_type_internal(m, type->BitField.backing_type);
case Type_Generic:
if (type->Generic.specialized) {
return lb_type_internal(m, type->Generic.specialized);
} else {
// For unspecialized generics, use a pointer type as a placeholder
return LLVMPointerType(LLVMInt8TypeInContext(m->ctx), 0);
}
} }
GB_PANIC("Invalid type %s", type_to_string(type)); GB_PANIC("Invalid type %s", type_to_string(type));
@@ -2378,6 +2388,29 @@ gb_internal void lb_add_attribute_to_proc_with_string(lbModule *m, LLVMValueRef
} }
gb_internal bool lb_apply_thread_local_model(LLVMValueRef value, String model) {
if (model != "") {
LLVMSetThreadLocal(value, true);
LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel;
if (model == "default") {
mode = LLVMGeneralDynamicTLSModel;
} else if (model == "localdynamic") {
mode = LLVMLocalDynamicTLSModel;
} else if (model == "initialexec") {
mode = LLVMInitialExecTLSModel;
} else if (model == "localexec") {
mode = LLVMLocalExecTLSModel;
} else {
GB_PANIC("Unhandled thread local mode %.*s", LIT(model));
}
LLVMSetThreadLocalMode(value, mode);
return true;
}
return false;
}
gb_internal void lb_add_edge(lbBlock *from, lbBlock *to) { gb_internal void lb_add_edge(lbBlock *from, lbBlock *to) {
LLVMValueRef instr = LLVMGetLastInstruction(from->block); LLVMValueRef instr = LLVMGetLastInstruction(from->block);
@@ -2516,10 +2549,13 @@ general_end:;
} }
} }
src_size = align_formula(src_size, src_align); // NOTE(laytan): even though this logic seems sound, the Address Sanitizer does not
dst_size = align_formula(dst_size, dst_align); // want you to load/store the space of a value that is there for alignment.
#if 0
i64 aligned_src_size = align_formula(src_size, src_align);
i64 aligned_dst_size = align_formula(dst_size, dst_align);
if (LLVMIsALoadInst(val) && (src_size >= dst_size && src_align >= dst_align)) { if (LLVMIsALoadInst(val) && (aligned_src_size >= aligned_dst_size && src_align >= dst_align)) {
LLVMValueRef val_ptr = LLVMGetOperand(val, 0); LLVMValueRef val_ptr = LLVMGetOperand(val, 0);
val_ptr = LLVMBuildPointerCast(p->builder, val_ptr, LLVMPointerType(dst_type, 0), ""); val_ptr = LLVMBuildPointerCast(p->builder, val_ptr, LLVMPointerType(dst_type, 0), "");
LLVMValueRef loaded_val = OdinLLVMBuildLoad(p, dst_type, val_ptr); LLVMValueRef loaded_val = OdinLLVMBuildLoad(p, dst_type, val_ptr);
@@ -2527,8 +2563,57 @@ general_end:;
// LLVMSetAlignment(loaded_val, gb_min(src_align, dst_align)); // LLVMSetAlignment(loaded_val, gb_min(src_align, dst_align));
return loaded_val; return loaded_val;
}
#endif
if (src_size > dst_size) {
GB_ASSERT(p->decl_block != p->curr_block);
// NOTE(laytan): src is bigger than dst, need to memcpy the part of src we want.
LLVMValueRef val_ptr;
if (LLVMIsALoadInst(val)) {
val_ptr = LLVMGetOperand(val, 0);
} else if (LLVMIsAAllocaInst(val)) {
val_ptr = LLVMBuildPointerCast(p->builder, val, LLVMPointerType(src_type, 0), "");
} else {
// NOTE(laytan): we need a pointer to memcpy from.
LLVMValueRef val_copy = llvm_alloca(p, src_type, src_align);
val_ptr = LLVMBuildPointerCast(p->builder, val_copy, LLVMPointerType(src_type, 0), "");
LLVMBuildStore(p->builder, val, val_ptr);
}
i64 max_align = gb_max(lb_alignof(src_type), lb_alignof(dst_type));
max_align = gb_max(max_align, 16);
LLVMValueRef ptr = llvm_alloca(p, dst_type, max_align);
LLVMValueRef nptr = LLVMBuildPointerCast(p->builder, ptr, LLVMPointerType(dst_type, 0), "");
LLVMTypeRef types[3] = {
lb_type(p->module, t_rawptr),
lb_type(p->module, t_rawptr),
lb_type(p->module, t_int)
};
LLVMValueRef args[4] = {
nptr,
val_ptr,
LLVMConstInt(LLVMIntTypeInContext(p->module->ctx, 8*cast(unsigned)build_context.int_size), dst_size, 0),
LLVMConstInt(LLVMInt1TypeInContext(p->module->ctx), 0, 0),
};
lb_call_intrinsic(
p,
"llvm.memcpy.inline",
args,
gb_count_of(args),
types,
gb_count_of(types)
);
return OdinLLVMBuildLoad(p, dst_type, ptr);
} else { } else {
GB_ASSERT(p->decl_block != p->curr_block); GB_ASSERT(p->decl_block != p->curr_block);
GB_ASSERT(dst_size >= src_size);
i64 max_align = gb_max(lb_alignof(src_type), lb_alignof(dst_type)); i64 max_align = gb_max(lb_alignof(src_type), lb_alignof(dst_type));
max_align = gb_max(max_align, 16); max_align = gb_max(max_align, 16);
@@ -2729,6 +2814,14 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e)
ignore_body = other_module != m; ignore_body = other_module != m;
lbProcedure *missing_proc = lb_create_procedure(m, e, ignore_body); lbProcedure *missing_proc = lb_create_procedure(m, e, ignore_body);
if (missing_proc == nullptr) {
// This is an unspecialized polymorphic procedure, which should not be codegen'd
lbValue dummy = {};
dummy.value = nullptr;
dummy.type = nullptr;
return dummy;
}
if (ignore_body) { if (ignore_body) {
mutex_lock(&gen->anonymous_proc_lits_mutex); mutex_lock(&gen->anonymous_proc_lits_mutex);
defer (mutex_unlock(&gen->anonymous_proc_lits_mutex)); defer (mutex_unlock(&gen->anonymous_proc_lits_mutex));
@@ -2921,25 +3014,7 @@ gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e) {
lb_set_entity_from_other_modules_linkage_correctly(other_module, e, name); lb_set_entity_from_other_modules_linkage_correctly(other_module, e, name);
if (e->Variable.thread_local_model != "") { lb_apply_thread_local_model(g.value, e->Variable.thread_local_model);
LLVMSetThreadLocal(g.value, true);
String m = e->Variable.thread_local_model;
LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel;
if (m == "default") {
mode = LLVMGeneralDynamicTLSModel;
} else if (m == "localdynamic") {
mode = LLVMLocalDynamicTLSModel;
} else if (m == "initialexec") {
mode = LLVMInitialExecTLSModel;
} else if (m == "localexec") {
mode = LLVMLocalExecTLSModel;
} else {
GB_PANIC("Unhandled thread local mode %.*s", LIT(m));
}
LLVMSetThreadLocalMode(g.value, mode);
}
return g; return g;
} }
+76 -2
View File
@@ -67,6 +67,14 @@ gb_internal void lb_mem_copy_non_overlapping(lbProcedure *p, lbValue dst, lbValu
gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) { gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) {
GB_ASSERT(entity != nullptr); GB_ASSERT(entity != nullptr);
GB_ASSERT(entity->kind == Entity_Procedure); GB_ASSERT(entity->kind == Entity_Procedure);
// Skip codegen for unspecialized polymorphic procedures
if (is_type_polymorphic(entity->type) && !entity->Procedure.is_foreign) {
Type *bt = base_type(entity->type);
if (bt->kind == Type_Proc && bt->Proc.is_polymorphic && !bt->Proc.is_poly_specialized) {
// Do not generate code for unspecialized polymorphic procedures
return nullptr;
}
}
if (!entity->Procedure.is_foreign) { if (!entity->Procedure.is_foreign) {
if ((entity->flags & EntityFlag_ProcBodyChecked) == 0) { if ((entity->flags & EntityFlag_ProcBodyChecked) == 0) {
GB_PANIC("%.*s :: %s (was parapoly: %d %d)", LIT(entity->token.string), type_to_string(entity->type), is_type_polymorphic(entity->type, true), is_type_polymorphic(entity->type, false)); GB_PANIC("%.*s :: %s (was parapoly: %d %d)", LIT(entity->token.string), type_to_string(entity->type), is_type_polymorphic(entity->type, true), is_type_polymorphic(entity->type, false));
@@ -783,8 +791,7 @@ gb_internal void lb_end_procedure_body(lbProcedure *p) {
p->curr_block = nullptr; p->curr_block = nullptr;
p->state_flags = 0; p->state_flags = 0;
}
gb_internal void lb_end_procedure(lbProcedure *p) {
LLVMDisposeBuilder(p->builder); LLVMDisposeBuilder(p->builder);
} }
@@ -817,6 +824,10 @@ gb_internal void lb_build_nested_proc(lbProcedure *p, AstProcLit *pd, Entity *e)
e->Procedure.link_name = name; e->Procedure.link_name = name;
lbProcedure *nested_proc = lb_create_procedure(p->module, e); lbProcedure *nested_proc = lb_create_procedure(p->module, e);
if (nested_proc == nullptr) {
// This is an unspecialized polymorphic procedure, skip codegen
return;
}
e->code_gen_procedure = nested_proc; e->code_gen_procedure = nested_proc;
lbValue value = {}; lbValue value = {};
@@ -2235,6 +2246,68 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
return lb_emit_load(p, tuple); return lb_emit_load(p, tuple);
} }
case BuiltinProc_compress_values: {
isize value_count = 0;
for (Ast *arg : ce->args) {
Type *t = arg->tav.type;
if (is_type_tuple(t)) {
value_count += t->Tuple.variables.count;
} else {
value_count += 1;
}
}
if (value_count == 1) {
lbValue x = lb_build_expr(p, ce->args[0]);
x = lb_emit_conv(p, x, tv.type);
return x;
}
Type *dt = base_type(tv.type);
lbAddr addr = lb_add_local_generated(p, tv.type, true);
if (is_type_struct(dt) || is_type_tuple(dt)) {
i32 index = 0;
for (Ast *arg : ce->args) {
lbValue x = lb_build_expr(p, arg);
if (is_type_tuple(x.type)) {
for (isize i = 0; i < x.type->Tuple.variables.count; i++) {
lbValue y = lb_emit_tuple_ev(p, x, cast(i32)i);
lbValue ptr = lb_emit_struct_ep(p, addr.addr, index++);
y = lb_emit_conv(p, y, type_deref(ptr.type));
lb_emit_store(p, ptr, y);
}
} else {
lbValue ptr = lb_emit_struct_ep(p, addr.addr, index++);
x = lb_emit_conv(p, x, type_deref(ptr.type));
lb_emit_store(p, ptr, x);
}
}
GB_ASSERT(index == value_count);
} else if (is_type_array_like(dt)) {
i32 index = 0;
for (Ast *arg : ce->args) {
lbValue x = lb_build_expr(p, arg);
if (is_type_tuple(x.type)) {
for (isize i = 0; i < x.type->Tuple.variables.count; i++) {
lbValue y = lb_emit_tuple_ev(p, x, cast(i32)i);
lbValue ptr = lb_emit_array_epi(p, addr.addr, index++);
y = lb_emit_conv(p, y, type_deref(ptr.type));
lb_emit_store(p, ptr, y);
}
} else {
lbValue ptr = lb_emit_array_epi(p, addr.addr, index++);
x = lb_emit_conv(p, x, type_deref(ptr.type));
lb_emit_store(p, ptr, x);
}
}
GB_ASSERT(index == value_count);
} else {
GB_PANIC("TODO(bill): compress_values -> %s", type_to_string(tv.type));
}
return lb_addr_load(p, addr);
}
case BuiltinProc_min: { case BuiltinProc_min: {
Type *t = type_of_expr(expr); Type *t = type_of_expr(expr);
if (ce->args.count == 2) { if (ce->args.count == 2) {
@@ -3375,6 +3448,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
case BuiltinProc_objc_find_class: return lb_handle_objc_find_class(p, expr); case BuiltinProc_objc_find_class: return lb_handle_objc_find_class(p, expr);
case BuiltinProc_objc_register_selector: return lb_handle_objc_register_selector(p, expr); case BuiltinProc_objc_register_selector: return lb_handle_objc_register_selector(p, expr);
case BuiltinProc_objc_register_class: return lb_handle_objc_register_class(p, expr); case BuiltinProc_objc_register_class: return lb_handle_objc_register_class(p, expr);
case BuiltinProc_objc_ivar_get: return lb_handle_objc_ivar_get(p, expr);
case BuiltinProc_constant_utf16_cstring: case BuiltinProc_constant_utf16_cstring:

Some files were not shown because too many files have changed in this diff Show More